@neat.is/core 0.5.2 → 0.5.4-dev.20260721

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 path53 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
- if (exactUnauthPaths.has(path53) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path53)) {
64
+ const path54 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
+ if (exactUnauthPaths.has(path54) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path54)) {
66
66
  done();
67
67
  return;
68
68
  }
@@ -194,8 +194,8 @@ function reshapeGrpcRequest(req2) {
194
194
  };
195
195
  }
196
196
  function resolveProtoRoot() {
197
- const here = import_node_path47.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
198
- return import_node_path47.default.resolve(here, "..", "proto");
197
+ const here = import_node_path48.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
198
+ return import_node_path48.default.resolve(here, "..", "proto");
199
199
  }
200
200
  function loadTraceService() {
201
201
  const protoRoot = resolveProtoRoot();
@@ -263,13 +263,13 @@ async function startOtelGrpcReceiver(opts) {
263
263
  })
264
264
  };
265
265
  }
266
- var import_node_url, import_node_path47, import_node_crypto2, grpc, protoLoader;
266
+ var import_node_url, import_node_path48, import_node_crypto2, grpc, protoLoader;
267
267
  var init_otel_grpc = __esm({
268
268
  "src/otel-grpc.ts"() {
269
269
  "use strict";
270
270
  init_cjs_shims();
271
271
  import_node_url = require("url");
272
- import_node_path47 = __toESM(require("path"), 1);
272
+ import_node_path48 = __toESM(require("path"), 1);
273
273
  import_node_crypto2 = require("crypto");
274
274
  grpc = __toESM(require("@grpc/grpc-js"), 1);
275
275
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
@@ -362,8 +362,8 @@ function websocketChannelPathOf(attrs) {
362
362
  const v = attrs[key];
363
363
  if (typeof v === "string" && v.length > 0) {
364
364
  const q = v.indexOf("?");
365
- const path53 = q === -1 ? v : v.slice(0, q);
366
- if (path53.length > 0) return path53;
365
+ const path54 = q === -1 ? v : v.slice(0, q);
366
+ if (path54.length > 0) return path54;
367
367
  }
368
368
  }
369
369
  return void 0;
@@ -398,6 +398,7 @@ function parseOtlpRequest(body) {
398
398
  attributes: attrs,
399
399
  dbSystem: typeof attrs["db.system"] === "string" ? attrs["db.system"] : void 0,
400
400
  dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
401
+ dbCollection: typeof attrs["db.collection.name"] === "string" ? attrs["db.collection.name"] : typeof attrs["db.mongodb.collection"] === "string" ? attrs["db.mongodb.collection"] : void 0,
401
402
  messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
402
403
  messagingDestination: messagingDestinationOf(attrs),
403
404
  graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
@@ -417,10 +418,10 @@ function parseOtlpRequest(body) {
417
418
  return out;
418
419
  }
419
420
  function loadProtoRoot() {
420
- const here = import_node_path48.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
421
- const protoRoot = import_node_path48.default.resolve(here, "..", "proto");
421
+ const here = import_node_path49.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
422
+ const protoRoot = import_node_path49.default.resolve(here, "..", "proto");
422
423
  const root = new import_protobufjs.default.Root();
423
- root.resolvePath = (_origin, target) => import_node_path48.default.resolve(protoRoot, target);
424
+ root.resolvePath = (_origin, target) => import_node_path49.default.resolve(protoRoot, target);
424
425
  root.loadSync(
425
426
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
426
427
  { keepCase: true }
@@ -648,12 +649,12 @@ async function listenSteppingOtlp(app, requestedPort, host) {
648
649
  }
649
650
  }
650
651
  }
651
- var import_node_path48, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
652
+ var import_node_path49, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
652
653
  var init_otel = __esm({
653
654
  "src/otel.ts"() {
654
655
  "use strict";
655
656
  init_cjs_shims();
656
- import_node_path48 = __toESM(require("path"), 1);
657
+ import_node_path49 = __toESM(require("path"), 1);
657
658
  import_node_url2 = require("url");
658
659
  import_fastify2 = __toESM(require("fastify"), 1);
659
660
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -677,13 +678,13 @@ __export(neatd_exports, {
677
678
  module.exports = __toCommonJS(neatd_exports);
678
679
  init_cjs_shims();
679
680
  var import_node_fs33 = require("fs");
680
- var import_node_path52 = __toESM(require("path"), 1);
681
+ var import_node_path53 = __toESM(require("path"), 1);
681
682
  var import_node_module2 = require("module");
682
683
 
683
684
  // src/daemon.ts
684
685
  init_cjs_shims();
685
686
  var import_node_fs31 = require("fs");
686
- var import_node_path50 = __toESM(require("path"), 1);
687
+ var import_node_path51 = __toESM(require("path"), 1);
687
688
  var import_node_module = require("module");
688
689
 
689
690
  // src/graph.ts
@@ -1213,19 +1214,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1213
1214
  function longestIncomingWalk(graph, start, maxDepth) {
1214
1215
  let best = { path: [start], edges: [] };
1215
1216
  const visited = /* @__PURE__ */ new Set([start]);
1216
- function step(node, path53, edges) {
1217
- if (path53.length > best.path.length) {
1218
- best = { path: [...path53], edges: [...edges] };
1217
+ function step(node, path54, edges) {
1218
+ if (path54.length > best.path.length) {
1219
+ best = { path: [...path54], edges: [...edges] };
1219
1220
  }
1220
- if (path53.length - 1 >= maxDepth) return;
1221
+ if (path54.length - 1 >= maxDepth) return;
1221
1222
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1222
1223
  for (const [srcId, edge] of incoming) {
1223
1224
  if (visited.has(srcId)) continue;
1224
1225
  visited.add(srcId);
1225
- path53.push(srcId);
1226
+ path54.push(srcId);
1226
1227
  edges.push(edge);
1227
- step(srcId, path53, edges);
1228
- path53.pop();
1228
+ step(srcId, path54, edges);
1229
+ path54.pop();
1229
1230
  edges.pop();
1230
1231
  visited.delete(srcId);
1231
1232
  }
@@ -1419,26 +1420,26 @@ function dominantFailingCall(graph, serviceId6, visited) {
1419
1420
  return best;
1420
1421
  }
1421
1422
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1422
- const path53 = [originServiceId];
1423
+ const path54 = [originServiceId];
1423
1424
  const edges = [];
1424
1425
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1425
1426
  let current = originServiceId;
1426
1427
  for (let depth = 0; depth < maxDepth; depth++) {
1427
1428
  const hop = dominantFailingCall(graph, current, visited);
1428
1429
  if (!hop) break;
1429
- path53.push(hop.nextService);
1430
+ path54.push(hop.nextService);
1430
1431
  edges.push(hop.edge);
1431
1432
  visited.add(hop.nextService);
1432
1433
  current = hop.nextService;
1433
1434
  }
1434
1435
  if (edges.length === 0) return null;
1435
- return { path: path53, edges, culprit: current };
1436
+ return { path: path54, edges, culprit: current };
1436
1437
  }
1437
1438
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1438
1439
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1439
1440
  if (!chain) return null;
1440
1441
  const culprit = chain.culprit;
1441
- const path53 = [...chain.path];
1442
+ const path54 = [...chain.path];
1442
1443
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1443
1444
  const baseConfidence = confidenceFromMix(chain.edges);
1444
1445
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1446,14 +1447,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1446
1447
  if (loc) {
1447
1448
  let rootCauseNode = culprit;
1448
1449
  if (loc.fileNode) {
1449
- path53.push(loc.fileNode);
1450
+ path54.push(loc.fileNode);
1450
1451
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1451
1452
  rootCauseNode = loc.fileNode;
1452
1453
  }
1453
1454
  return import_types.RootCauseResultSchema.parse({
1454
1455
  rootCauseNode,
1455
1456
  rootCauseReason: loc.rootCauseReason,
1456
- traversalPath: path53,
1457
+ traversalPath: path54,
1457
1458
  edgeProvenances,
1458
1459
  confidence,
1459
1460
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1465,7 +1466,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1465
1466
  return import_types.RootCauseResultSchema.parse({
1466
1467
  rootCauseNode: culprit,
1467
1468
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1468
- traversalPath: path53,
1469
+ traversalPath: path54,
1469
1470
  edgeProvenances,
1470
1471
  confidence,
1471
1472
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2892,6 +2893,18 @@ async function handleSpan(ctx, span) {
2892
2893
  callSiteEvidence
2893
2894
  );
2894
2895
  if (result) affectedNode = targetId;
2896
+ if (span.dbSystem === "mongodb" && span.dbCollection) {
2897
+ const collectionId = ensureInfraNode(ctx.graph, "mongodb-collection", span.dbCollection, "self");
2898
+ upsertObservedEdge(
2899
+ ctx.graph,
2900
+ import_types3.EdgeType.CALLS,
2901
+ observedSource(),
2902
+ collectionId,
2903
+ ts,
2904
+ isError,
2905
+ callSiteEvidence
2906
+ );
2907
+ }
2895
2908
  }
2896
2909
  } else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
2897
2910
  const targetId = ensureMessagingDestinationNode(
@@ -4633,14 +4646,21 @@ function engineFromImage(image) {
4633
4646
  // src/extract/databases/dotenv.ts
4634
4647
  var CONNECTION_KEYS = /* @__PURE__ */ new Set([
4635
4648
  "DATABASE_URL",
4649
+ "DATABASE_URI",
4636
4650
  "DB_URL",
4651
+ "DB_URI",
4637
4652
  "POSTGRES_URL",
4653
+ "POSTGRES_URI",
4638
4654
  "POSTGRESQL_URL",
4655
+ "POSTGRESQL_URI",
4639
4656
  "MYSQL_URL",
4657
+ "MYSQL_URI",
4658
+ "MONGODB_URL",
4640
4659
  "MONGODB_URI",
4641
4660
  "MONGO_URL",
4642
4661
  "MONGO_URI",
4643
- "REDIS_URL"
4662
+ "REDIS_URL",
4663
+ "REDIS_URI"
4644
4664
  ]);
4645
4665
  function parseDotenvLine(line) {
4646
4666
  const trimmed = line.trim();
@@ -5729,7 +5749,7 @@ async function addGrpcMethods(graph, services) {
5729
5749
 
5730
5750
  // src/extract/calls/index.ts
5731
5751
  init_cjs_shims();
5732
- var import_types20 = require("@neat.is/types");
5752
+ var import_types21 = require("@neat.is/types");
5733
5753
 
5734
5754
  // src/extract/calls/http.ts
5735
5755
  init_cjs_shims();
@@ -6442,15 +6462,311 @@ function supabaseEndpointsFromFile(file, serviceDir) {
6442
6462
  return out;
6443
6463
  }
6444
6464
 
6465
+ // src/extract/calls/mongoose.ts
6466
+ init_cjs_shims();
6467
+ var import_node_path31 = __toESM(require("path"), 1);
6468
+ var import_types20 = require("@neat.is/types");
6469
+ var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
6470
+ var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
6471
+ var PLURALIZE_DISABLED_RE = /\bpluralize\s*\(\s*(?:null|false)\s*\)/;
6472
+ var UNCOUNTABLES = /* @__PURE__ */ new Set([
6473
+ "advice",
6474
+ "energy",
6475
+ "excretion",
6476
+ "digestion",
6477
+ "cooperation",
6478
+ "health",
6479
+ "justice",
6480
+ "labour",
6481
+ "machinery",
6482
+ "equipment",
6483
+ "information",
6484
+ "pollution",
6485
+ "sewage",
6486
+ "paper",
6487
+ "money",
6488
+ "species",
6489
+ "series",
6490
+ "rain",
6491
+ "rice",
6492
+ "fish",
6493
+ "sheep",
6494
+ "moose",
6495
+ "deer",
6496
+ "news",
6497
+ "expertise",
6498
+ "status",
6499
+ "media"
6500
+ ]);
6501
+ var PLURALIZE_RULES = [
6502
+ [/human$/gi, "humans"],
6503
+ [/(m)an$/gi, "$1en"],
6504
+ [/(pe)rson$/gi, "$1ople"],
6505
+ [/(child)$/gi, "$1ren"],
6506
+ [/^(ox)$/gi, "$1en"],
6507
+ [/(ax|test)is$/gi, "$1es"],
6508
+ [/(octop|vir)us$/gi, "$1i"],
6509
+ [/(alias|status)$/gi, "$1es"],
6510
+ [/(bu)s$/gi, "$1ses"],
6511
+ [/(buffal|tomat|potat)o$/gi, "$1oes"],
6512
+ [/([ti])um$/gi, "$1a"],
6513
+ [/sis$/gi, "ses"],
6514
+ [/(?:([^f])fe|([lr])f)$/gi, "$1$2ves"],
6515
+ [/(hive)$/gi, "$1s"],
6516
+ [/([^aeiouy]|qu)y$/gi, "$1ies"],
6517
+ [/(x|ch|ss|sh)$/gi, "$1es"],
6518
+ [/(matr|vert|ind)ix|ex$/gi, "$1ices"],
6519
+ [/([m|l])ouse$/gi, "$1ice"],
6520
+ [/(kn|w|l)ife$/gi, "$1ives"],
6521
+ [/(quiz)$/gi, "$1zes"],
6522
+ [/s$/gi, "s"],
6523
+ [/([^a-z])$/, "$1"],
6524
+ [/$/gi, "s"]
6525
+ ];
6526
+ function pluralizeCollection(name) {
6527
+ const str = name.toLowerCase();
6528
+ if (UNCOUNTABLES.has(str)) return str;
6529
+ for (const [re, repl] of PLURALIZE_RULES) {
6530
+ if (str.match(re)) return str.replace(re, repl);
6531
+ }
6532
+ return str;
6533
+ }
6534
+ var MODEL_METHODS = [
6535
+ "find",
6536
+ "findOne",
6537
+ "findById",
6538
+ "findByIdAndUpdate",
6539
+ "findByIdAndDelete",
6540
+ "findOneAndUpdate",
6541
+ "findOneAndDelete",
6542
+ "findOneAndReplace",
6543
+ "countDocuments",
6544
+ "estimatedDocumentCount",
6545
+ "distinct",
6546
+ "aggregate",
6547
+ "exists",
6548
+ "where",
6549
+ "populate",
6550
+ "watch",
6551
+ "hydrate",
6552
+ "create",
6553
+ "insertMany",
6554
+ "insertOne",
6555
+ "updateOne",
6556
+ "updateMany",
6557
+ "replaceOne",
6558
+ "deleteOne",
6559
+ "deleteMany",
6560
+ "bulkWrite",
6561
+ "bulkSave",
6562
+ "save",
6563
+ // pre-v7, removed in current mongoose but present in older codebases:
6564
+ "count",
6565
+ "update",
6566
+ "remove",
6567
+ "findOneAndRemove",
6568
+ "findByIdAndRemove"
6569
+ ];
6570
+ var MODEL_METHODS_ALT = MODEL_METHODS.join("|");
6571
+ function schemaCollectionVars(content) {
6572
+ const out = /* @__PURE__ */ new Map();
6573
+ const re = /(?:const|let|var)\s+(\w+)\s*=\s*new\s+(?:mongoose\s*\.\s*)?Schema\s*\(/g;
6574
+ let m;
6575
+ while ((m = re.exec(content)) !== null) {
6576
+ const windowText = content.slice(m.index, m.index + 2e3);
6577
+ const opt = /\bcollection\s*:\s*['"`]([\w.-]+)['"`]/.exec(windowText);
6578
+ if (opt) out.set(m[1], opt[1]);
6579
+ }
6580
+ return out;
6581
+ }
6582
+ function resolveModel(modelName, rest, schemaColl, pluralizeOn) {
6583
+ const trimmed = rest.trim();
6584
+ const literalThird = /,\s*['"`]([\w.-]+)['"`]\s*$/.exec(trimmed);
6585
+ if (literalThird) return { name: literalThird[1], kind: "mongodb-collection" };
6586
+ const firstIdent = /^([A-Za-z_$][\w$]*)/.exec(trimmed)?.[1];
6587
+ if (firstIdent && schemaColl.has(firstIdent)) {
6588
+ return { name: schemaColl.get(firstIdent), kind: "mongodb-collection" };
6589
+ }
6590
+ if (/,\s*[A-Za-z_$][\w$]*\s*$/.test(trimmed)) {
6591
+ return { name: modelName, kind: "mongodb-model" };
6592
+ }
6593
+ return { name: pluralizeOn ? pluralizeCollection(modelName) : modelName, kind: "mongodb-collection" };
6594
+ }
6595
+ function collectModelDefs(content, pluralizeOn) {
6596
+ const schemaColl = schemaCollectionVars(content);
6597
+ const out = [];
6598
+ const re = /(?:(?:const|let|var)\s+(\w+)\s*=\s*)?(?:await\s+)?(?:\b\w+\s*\.\s*)?\bmodel\s*\(\s*['"`]([\w$]+)['"`]\s*(?:,\s*([\s\S]{0,300}?))?\)/g;
6599
+ let m;
6600
+ while ((m = re.exec(content)) !== null) {
6601
+ out.push({
6602
+ modelName: m[2],
6603
+ varName: m[1] ?? null,
6604
+ resolved: resolveModel(m[2], m[3] ?? "", schemaColl, pluralizeOn),
6605
+ matchText: m[0]
6606
+ });
6607
+ }
6608
+ return out;
6609
+ }
6610
+ function endpoint(r, file, serviceDir, matchText) {
6611
+ const line = lineOf(file.content, matchText);
6612
+ return {
6613
+ infraId: (0, import_types20.infraId)(r.kind, r.name),
6614
+ name: r.name,
6615
+ kind: r.kind,
6616
+ edgeType: "CALLS",
6617
+ confidenceKind: "verified-call-site",
6618
+ evidence: { file: import_node_path31.default.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
6619
+ };
6620
+ }
6621
+ function mongooseEndpointsFromFile(file, serviceDir) {
6622
+ const hasMongoose = MONGOOSE_IMPORT_RE.test(file.content);
6623
+ const hasMongodb = MONGODB_IMPORT_RE.test(file.content);
6624
+ if (!hasMongoose && !hasMongodb) return [];
6625
+ const content = file.content;
6626
+ const out = [];
6627
+ const seen = /* @__PURE__ */ new Set();
6628
+ const push = (r, matchText) => {
6629
+ const key = `${r.kind}/${r.name}`;
6630
+ if (seen.has(key)) return;
6631
+ seen.add(key);
6632
+ out.push(endpoint(r, file, serviceDir, matchText));
6633
+ };
6634
+ if (hasMongoose) {
6635
+ const pluralizeOn = !PLURALIZE_DISABLED_RE.test(content);
6636
+ for (const def of collectModelDefs(content, pluralizeOn)) push(def.resolved, def.matchText);
6637
+ }
6638
+ const collRe = /\.\s*collection\s*\(\s*['"`]([\w.$-]+)['"`]\s*\)/g;
6639
+ let c;
6640
+ while ((c = collRe.exec(content)) !== null) {
6641
+ push({ name: c[1], kind: "mongodb-collection" }, c[0]);
6642
+ }
6643
+ return out;
6644
+ }
6645
+ function parseDestructure(inner, specifier, out) {
6646
+ for (const raw of inner.split(",")) {
6647
+ const part = raw.trim();
6648
+ if (!part) continue;
6649
+ const asM = /^(\w+)\s+as\s+(\w+)$/.exec(part);
6650
+ const colonM = /^(\w+)\s*:\s*(\w+)$/.exec(part);
6651
+ if (asM) out.push({ local: asM[2], kind: "named", exportName: asM[1], specifier });
6652
+ else if (colonM) out.push({ local: colonM[2], kind: "named", exportName: colonM[1], specifier });
6653
+ else {
6654
+ const id = /^(\w+)$/.exec(part);
6655
+ if (id) out.push({ local: id[1], kind: "named", exportName: id[1], specifier });
6656
+ }
6657
+ }
6658
+ }
6659
+ function parseImportBindings(content) {
6660
+ const out = [];
6661
+ const esm = /import\s+([^;'"`\n]+?)\s+from\s*['"`]([^'"`]+)['"`]/g;
6662
+ let m;
6663
+ while ((m = esm.exec(content)) !== null) {
6664
+ const clause = m[1].trim();
6665
+ const spec = m[2];
6666
+ const ns = /^\*\s+as\s+(\w+)$/.exec(clause);
6667
+ if (ns) {
6668
+ out.push({ local: ns[1], kind: "namespace", specifier: spec });
6669
+ continue;
6670
+ }
6671
+ const named = /\{([^}]*)\}/.exec(clause);
6672
+ if (named) parseDestructure(named[1], spec, out);
6673
+ const def = /^(\w+)\s*(?:,|$)/.exec(clause);
6674
+ if (def && !clause.startsWith("{")) out.push({ local: def[1], kind: "default", specifier: spec });
6675
+ }
6676
+ const cjs = /(?:const|let|var)\s+(\{[^}]*\}|\w+)\s*=\s*require\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
6677
+ while ((m = cjs.exec(content)) !== null) {
6678
+ const lhs = m[1];
6679
+ const spec = m[2];
6680
+ if (lhs.startsWith("{")) parseDestructure(lhs.slice(1, -1), spec, out);
6681
+ else out.push({ local: lhs, kind: "whole", specifier: spec });
6682
+ }
6683
+ return out;
6684
+ }
6685
+ function fileExportsOf(content, pluralizeOn) {
6686
+ const defs = collectModelDefs(content, pluralizeOn).filter((d) => d.resolved.kind === "mongodb-collection");
6687
+ if (defs.length === 0) return null;
6688
+ const byVar = /* @__PURE__ */ new Map();
6689
+ for (const d of defs) if (d.varName) byVar.set(d.varName, d.resolved.name);
6690
+ const byName = new Map(byVar);
6691
+ let def;
6692
+ const named = /(?:module\.exports|export\s+default)\s*=\s*(\w+)\b/.exec(content);
6693
+ if (named && byVar.has(named[1])) def = byVar.get(named[1]);
6694
+ if (!def) {
6695
+ const inline = /(?:module\.exports|export\s+default)\s*=\s*(?:await\s+)?(?:\w+\s*\.\s*)?model\s*\(\s*['"`]([\w$]+)['"`]/.exec(content);
6696
+ if (inline) def = pluralizeOn ? pluralizeCollection(inline[1]) : inline[1];
6697
+ }
6698
+ if (!def && byVar.size === 1) def = [...byVar.values()][0];
6699
+ return { byName, ...def ? { default: def } : {} };
6700
+ }
6701
+ async function mongooseCrossFileEndpoints(files, serviceDir) {
6702
+ const mongooseFiles = files.filter((f) => MONGOOSE_IMPORT_RE.test(f.content));
6703
+ if (mongooseFiles.length === 0) return [];
6704
+ const pluralizeOn = !files.some((f) => PLURALIZE_DISABLED_RE.test(f.content));
6705
+ const registry = /* @__PURE__ */ new Map();
6706
+ for (const f of mongooseFiles) {
6707
+ const fx = fileExportsOf(f.content, pluralizeOn);
6708
+ if (fx) registry.set(toPosix2(import_node_path31.default.relative(serviceDir, f.path)), fx);
6709
+ }
6710
+ if (registry.size === 0) return [];
6711
+ const out = [];
6712
+ const seen = /* @__PURE__ */ new Set();
6713
+ for (const f of files) {
6714
+ const bindings = parseImportBindings(f.content);
6715
+ if (bindings.length === 0) continue;
6716
+ const directColl = /* @__PURE__ */ new Map();
6717
+ const nsExports = /* @__PURE__ */ new Map();
6718
+ for (const b of bindings) {
6719
+ const resolvedRel = await resolveJsImport(b.specifier, import_node_path31.default.dirname(f.path), serviceDir, null);
6720
+ if (!resolvedRel) continue;
6721
+ const fx = registry.get(resolvedRel);
6722
+ if (!fx) continue;
6723
+ if (b.kind === "named" && b.exportName) {
6724
+ const coll = fx.byName.get(b.exportName);
6725
+ if (coll) directColl.set(b.local, coll);
6726
+ } else if (b.kind === "default") {
6727
+ if (fx.default) directColl.set(b.local, fx.default);
6728
+ } else {
6729
+ if (fx.default) directColl.set(b.local, fx.default);
6730
+ nsExports.set(b.local, fx);
6731
+ }
6732
+ }
6733
+ if (directColl.size === 0 && nsExports.size === 0) continue;
6734
+ const localDefs = new Set(
6735
+ collectModelDefs(f.content, pluralizeOn).map((d) => d.varName).filter((v) => v !== null)
6736
+ );
6737
+ const emit = (collection, matchText) => {
6738
+ const key = `${f.path}::${collection}`;
6739
+ if (seen.has(key)) return;
6740
+ seen.add(key);
6741
+ out.push(endpoint({ name: collection, kind: "mongodb-collection" }, f, serviceDir, matchText));
6742
+ };
6743
+ for (const [local, collection] of directColl) {
6744
+ if (localDefs.has(local)) continue;
6745
+ const qre = new RegExp(`\\b${local}\\s*\\.\\s*(?:${MODEL_METHODS_ALT})\\s*\\(`, "g");
6746
+ let qm;
6747
+ while ((qm = qre.exec(f.content)) !== null) emit(collection, qm[0]);
6748
+ }
6749
+ for (const [local, fx] of nsExports) {
6750
+ const qre = new RegExp(`\\b${local}\\s*\\.\\s*(\\w+)\\s*\\.\\s*(?:${MODEL_METHODS_ALT})\\s*\\(`, "g");
6751
+ let qm;
6752
+ while ((qm = qre.exec(f.content)) !== null) {
6753
+ const coll = fx.byName.get(qm[1]);
6754
+ if (coll) emit(coll, qm[0]);
6755
+ }
6756
+ }
6757
+ }
6758
+ return out;
6759
+ }
6760
+
6445
6761
  // src/extract/calls/index.ts
6446
6762
  function edgeTypeFromEndpoint(ep) {
6447
6763
  switch (ep.edgeType) {
6448
6764
  case "PUBLISHES_TO":
6449
- return import_types20.EdgeType.PUBLISHES_TO;
6765
+ return import_types21.EdgeType.PUBLISHES_TO;
6450
6766
  case "CONSUMES_FROM":
6451
- return import_types20.EdgeType.CONSUMES_FROM;
6767
+ return import_types21.EdgeType.CONSUMES_FROM;
6452
6768
  default:
6453
- return import_types20.EdgeType.CALLS;
6769
+ return import_types21.EdgeType.CALLS;
6454
6770
  }
6455
6771
  }
6456
6772
  function isAwsKind(kind) {
@@ -6462,23 +6778,27 @@ async function addExternalEndpointEdges(graph, services) {
6462
6778
  for (const service of services) {
6463
6779
  const files = await loadSourceFiles(service.dir);
6464
6780
  const endpoints = [];
6781
+ const maskedFiles = [];
6465
6782
  for (const file of files) {
6466
6783
  if (isTestPath(file.path)) continue;
6467
6784
  const masked = maskCommentsInSource(file.content);
6468
6785
  const maskedFile = { path: file.path, content: masked };
6786
+ maskedFiles.push(maskedFile);
6469
6787
  endpoints.push(...kafkaEndpointsFromFile(maskedFile, service.dir));
6470
6788
  endpoints.push(...redisEndpointsFromFile(maskedFile, service.dir));
6471
6789
  endpoints.push(...awsEndpointsFromFile(maskedFile, service.dir));
6472
6790
  endpoints.push(...grpcEndpointsFromFile(maskedFile, service.dir));
6473
6791
  endpoints.push(...supabaseEndpointsFromFile(maskedFile, service.dir));
6792
+ endpoints.push(...mongooseEndpointsFromFile(maskedFile, service.dir));
6474
6793
  }
6794
+ endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
6475
6795
  if (endpoints.length === 0) continue;
6476
6796
  const seenEdges = /* @__PURE__ */ new Set();
6477
6797
  for (const ep of endpoints) {
6478
6798
  if (!graph.hasNode(ep.infraId)) {
6479
6799
  const node = {
6480
6800
  id: ep.infraId,
6481
- type: import_types20.NodeType.InfraNode,
6801
+ type: import_types21.NodeType.InfraNode,
6482
6802
  name: ep.name,
6483
6803
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
6484
6804
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -6490,7 +6810,7 @@ async function addExternalEndpointEdges(graph, services) {
6490
6810
  nodesAdded++;
6491
6811
  }
6492
6812
  const edgeType = edgeTypeFromEndpoint(ep);
6493
- const confidence = (0, import_types20.confidenceForExtracted)(ep.confidenceKind);
6813
+ const confidence = (0, import_types21.confidenceForExtracted)(ep.confidenceKind);
6494
6814
  const relFile = toPosix2(ep.evidence.file);
6495
6815
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
6496
6816
  graph,
@@ -6500,7 +6820,7 @@ async function addExternalEndpointEdges(graph, services) {
6500
6820
  );
6501
6821
  nodesAdded += n;
6502
6822
  edgesAdded += e;
6503
- if (!(0, import_types20.passesExtractedFloor)(confidence)) {
6823
+ if (!(0, import_types21.passesExtractedFloor)(confidence)) {
6504
6824
  noteExtractedDropped({
6505
6825
  source: fileNodeId,
6506
6826
  target: ep.infraId,
@@ -6520,7 +6840,7 @@ async function addExternalEndpointEdges(graph, services) {
6520
6840
  source: fileNodeId,
6521
6841
  target: ep.infraId,
6522
6842
  type: edgeType,
6523
- provenance: import_types20.Provenance.EXTRACTED,
6843
+ provenance: import_types21.Provenance.EXTRACTED,
6524
6844
  confidence,
6525
6845
  evidence: ep.evidence
6526
6846
  };
@@ -6546,16 +6866,16 @@ init_cjs_shims();
6546
6866
 
6547
6867
  // src/extract/infra/docker-compose.ts
6548
6868
  init_cjs_shims();
6549
- var import_node_path31 = __toESM(require("path"), 1);
6550
- var import_types22 = require("@neat.is/types");
6869
+ var import_node_path32 = __toESM(require("path"), 1);
6870
+ var import_types23 = require("@neat.is/types");
6551
6871
 
6552
6872
  // src/extract/infra/shared.ts
6553
6873
  init_cjs_shims();
6554
- var import_types21 = require("@neat.is/types");
6874
+ var import_types22 = require("@neat.is/types");
6555
6875
  function makeInfraNode(kind, name, provider = "self", extras) {
6556
6876
  return {
6557
- id: (0, import_types21.infraId)(kind, name),
6558
- type: import_types21.NodeType.InfraNode,
6877
+ id: (0, import_types22.infraId)(kind, name),
6878
+ type: import_types22.NodeType.InfraNode,
6559
6879
  name,
6560
6880
  provider,
6561
6881
  kind,
@@ -6599,8 +6919,8 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
6599
6919
  source: anchorId,
6600
6920
  target: node.id,
6601
6921
  type: edgeType,
6602
- provenance: import_types21.Provenance.EXTRACTED,
6603
- confidence: (0, import_types21.confidenceForExtracted)("structural"),
6922
+ provenance: import_types22.Provenance.EXTRACTED,
6923
+ confidence: (0, import_types22.confidenceForExtracted)("structural"),
6604
6924
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
6605
6925
  };
6606
6926
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6617,7 +6937,7 @@ function dependsOnList(value) {
6617
6937
  }
6618
6938
  function serviceNameToServiceNode(name, services) {
6619
6939
  for (const s of services) {
6620
- if (s.node.name === name || import_node_path31.default.basename(s.dir) === name) return s.node.id;
6940
+ if (s.node.name === name || import_node_path32.default.basename(s.dir) === name) return s.node.id;
6621
6941
  }
6622
6942
  return null;
6623
6943
  }
@@ -6626,7 +6946,7 @@ async function addComposeInfra(graph, scanPath, services) {
6626
6946
  let edgesAdded = 0;
6627
6947
  let composePath = null;
6628
6948
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
6629
- const abs = import_node_path31.default.join(scanPath, name);
6949
+ const abs = import_node_path32.default.join(scanPath, name);
6630
6950
  if (await exists(abs)) {
6631
6951
  composePath = abs;
6632
6952
  break;
@@ -6639,13 +6959,13 @@ async function addComposeInfra(graph, scanPath, services) {
6639
6959
  } catch (err) {
6640
6960
  recordExtractionError(
6641
6961
  "infra docker-compose",
6642
- import_node_path31.default.relative(scanPath, composePath),
6962
+ import_node_path32.default.relative(scanPath, composePath),
6643
6963
  err
6644
6964
  );
6645
6965
  return { nodesAdded, edgesAdded };
6646
6966
  }
6647
6967
  if (!compose?.services) return { nodesAdded, edgesAdded };
6648
- const evidenceFile = import_node_path31.default.relative(scanPath, composePath).split(import_node_path31.default.sep).join("/");
6968
+ const evidenceFile = import_node_path32.default.relative(scanPath, composePath).split(import_node_path32.default.sep).join("/");
6649
6969
  const composeNameToNodeId = /* @__PURE__ */ new Map();
6650
6970
  for (const [composeName, svc] of Object.entries(compose.services)) {
6651
6971
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -6667,15 +6987,15 @@ async function addComposeInfra(graph, scanPath, services) {
6667
6987
  for (const dep of dependsOnList(svc.depends_on)) {
6668
6988
  const targetId = composeNameToNodeId.get(dep);
6669
6989
  if (!targetId) continue;
6670
- const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types22.EdgeType.DEPENDS_ON);
6990
+ const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types23.EdgeType.DEPENDS_ON);
6671
6991
  if (graph.hasEdge(edgeId)) continue;
6672
6992
  const edge = {
6673
6993
  id: edgeId,
6674
6994
  source: sourceId,
6675
6995
  target: targetId,
6676
- type: import_types22.EdgeType.DEPENDS_ON,
6677
- provenance: import_types22.Provenance.EXTRACTED,
6678
- confidence: (0, import_types22.confidenceForExtracted)("structural"),
6996
+ type: import_types23.EdgeType.DEPENDS_ON,
6997
+ provenance: import_types23.Provenance.EXTRACTED,
6998
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
6679
6999
  evidence: { file: evidenceFile }
6680
7000
  };
6681
7001
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6687,9 +7007,9 @@ async function addComposeInfra(graph, scanPath, services) {
6687
7007
 
6688
7008
  // src/extract/infra/dockerfile.ts
6689
7009
  init_cjs_shims();
6690
- var import_node_path32 = __toESM(require("path"), 1);
7010
+ var import_node_path33 = __toESM(require("path"), 1);
6691
7011
  var import_node_fs16 = require("fs");
6692
- var import_types23 = require("@neat.is/types");
7012
+ var import_types24 = require("@neat.is/types");
6693
7013
  function readDockerfile(content) {
6694
7014
  let image = null;
6695
7015
  const ports = [];
@@ -6718,7 +7038,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6718
7038
  let nodesAdded = 0;
6719
7039
  let edgesAdded = 0;
6720
7040
  for (const service of services) {
6721
- const dockerfilePath = import_node_path32.default.join(service.dir, "Dockerfile");
7041
+ const dockerfilePath = import_node_path33.default.join(service.dir, "Dockerfile");
6722
7042
  if (!await exists(dockerfilePath)) continue;
6723
7043
  let content;
6724
7044
  try {
@@ -6726,7 +7046,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6726
7046
  } catch (err) {
6727
7047
  recordExtractionError(
6728
7048
  "infra dockerfile",
6729
- import_node_path32.default.relative(scanPath, dockerfilePath),
7049
+ import_node_path33.default.relative(scanPath, dockerfilePath),
6730
7050
  err
6731
7051
  );
6732
7052
  continue;
@@ -6738,8 +7058,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6738
7058
  graph.addNode(node.id, node);
6739
7059
  nodesAdded++;
6740
7060
  }
6741
- const relDockerfile = toPosix2(import_node_path32.default.relative(service.dir, dockerfilePath));
6742
- const evidenceFile = toPosix2(import_node_path32.default.relative(scanPath, dockerfilePath));
7061
+ const relDockerfile = toPosix2(import_node_path33.default.relative(service.dir, dockerfilePath));
7062
+ const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, dockerfilePath));
6743
7063
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
6744
7064
  graph,
6745
7065
  service.pkg.name,
@@ -6748,15 +7068,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6748
7068
  );
6749
7069
  nodesAdded += fn;
6750
7070
  edgesAdded += fe;
6751
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types23.EdgeType.RUNS_ON);
7071
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types24.EdgeType.RUNS_ON);
6752
7072
  if (!graph.hasEdge(edgeId)) {
6753
7073
  const edge = {
6754
7074
  id: edgeId,
6755
7075
  source: fileNodeId,
6756
7076
  target: node.id,
6757
- type: import_types23.EdgeType.RUNS_ON,
6758
- provenance: import_types23.Provenance.EXTRACTED,
6759
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
7077
+ type: import_types24.EdgeType.RUNS_ON,
7078
+ provenance: import_types24.Provenance.EXTRACTED,
7079
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
6760
7080
  evidence: {
6761
7081
  file: evidenceFile,
6762
7082
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -6771,15 +7091,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6771
7091
  graph.addNode(portNode.id, portNode);
6772
7092
  nodesAdded++;
6773
7093
  }
6774
- const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types23.EdgeType.CONNECTS_TO);
7094
+ const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types24.EdgeType.CONNECTS_TO);
6775
7095
  if (graph.hasEdge(portEdgeId)) continue;
6776
7096
  const portEdge = {
6777
7097
  id: portEdgeId,
6778
7098
  source: fileNodeId,
6779
7099
  target: portNode.id,
6780
- type: import_types23.EdgeType.CONNECTS_TO,
6781
- provenance: import_types23.Provenance.EXTRACTED,
6782
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
7100
+ type: import_types24.EdgeType.CONNECTS_TO,
7101
+ provenance: import_types24.Provenance.EXTRACTED,
7102
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
6783
7103
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
6784
7104
  };
6785
7105
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -6792,8 +7112,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6792
7112
  // src/extract/infra/terraform.ts
6793
7113
  init_cjs_shims();
6794
7114
  var import_node_fs17 = require("fs");
6795
- var import_node_path33 = __toESM(require("path"), 1);
6796
- var import_types24 = require("@neat.is/types");
7115
+ var import_node_path34 = __toESM(require("path"), 1);
7116
+ var import_types25 = require("@neat.is/types");
6797
7117
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
6798
7118
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
6799
7119
  async function walkTfFiles(start, depth = 0, max = 5) {
@@ -6803,11 +7123,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
6803
7123
  for (const entry2 of entries) {
6804
7124
  if (entry2.isDirectory()) {
6805
7125
  if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
6806
- const child = import_node_path33.default.join(start, entry2.name);
7126
+ const child = import_node_path34.default.join(start, entry2.name);
6807
7127
  if (await isPythonVenvDir(child)) continue;
6808
7128
  out.push(...await walkTfFiles(child, depth + 1, max));
6809
7129
  } else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
6810
- out.push(import_node_path33.default.join(start, entry2.name));
7130
+ out.push(import_node_path34.default.join(start, entry2.name));
6811
7131
  }
6812
7132
  }
6813
7133
  return out;
@@ -6839,7 +7159,7 @@ async function addTerraformResources(graph, scanPath) {
6839
7159
  const files = await walkTfFiles(scanPath);
6840
7160
  for (const file of files) {
6841
7161
  const content = await import_node_fs17.promises.readFile(file, "utf8");
6842
- const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, file));
7162
+ const evidenceFile = toPosix2(import_node_path34.default.relative(scanPath, file));
6843
7163
  const resources = [];
6844
7164
  const byKey = /* @__PURE__ */ new Map();
6845
7165
  RESOURCE_RE.lastIndex = 0;
@@ -6874,16 +7194,16 @@ async function addTerraformResources(graph, scanPath) {
6874
7194
  if (!target) continue;
6875
7195
  if (seen.has(target.nodeId)) continue;
6876
7196
  seen.add(target.nodeId);
6877
- const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types24.EdgeType.DEPENDS_ON);
7197
+ const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types25.EdgeType.DEPENDS_ON);
6878
7198
  if (graph.hasEdge(edgeId)) continue;
6879
7199
  const line = lineAt2(content, resource.bodyOffset + ref.index);
6880
7200
  const edge = {
6881
7201
  id: edgeId,
6882
7202
  source: resource.nodeId,
6883
7203
  target: target.nodeId,
6884
- type: import_types24.EdgeType.DEPENDS_ON,
6885
- provenance: import_types24.Provenance.EXTRACTED,
6886
- confidence: (0, import_types24.confidenceForExtracted)("structural"),
7204
+ type: import_types25.EdgeType.DEPENDS_ON,
7205
+ provenance: import_types25.Provenance.EXTRACTED,
7206
+ confidence: (0, import_types25.confidenceForExtracted)("structural"),
6887
7207
  evidence: { file: evidenceFile, line, snippet: key }
6888
7208
  };
6889
7209
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6897,7 +7217,7 @@ async function addTerraformResources(graph, scanPath) {
6897
7217
  // src/extract/infra/k8s.ts
6898
7218
  init_cjs_shims();
6899
7219
  var import_node_fs18 = require("fs");
6900
- var import_node_path34 = __toESM(require("path"), 1);
7220
+ var import_node_path35 = __toESM(require("path"), 1);
6901
7221
  var import_yaml3 = require("yaml");
6902
7222
  var K8S_KIND_TO_INFRA_KIND = {
6903
7223
  Service: "k8s-service",
@@ -6915,11 +7235,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
6915
7235
  for (const entry2 of entries) {
6916
7236
  if (entry2.isDirectory()) {
6917
7237
  if (IGNORED_DIRS.has(entry2.name)) continue;
6918
- const child = import_node_path34.default.join(start, entry2.name);
7238
+ const child = import_node_path35.default.join(start, entry2.name);
6919
7239
  if (await isPythonVenvDir(child)) continue;
6920
7240
  out.push(...await walkYamlFiles2(child, depth + 1, max));
6921
- } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path34.default.extname(entry2.name))) {
6922
- out.push(import_node_path34.default.join(start, entry2.name));
7241
+ } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path35.default.extname(entry2.name))) {
7242
+ out.push(import_node_path35.default.join(start, entry2.name));
6923
7243
  }
6924
7244
  }
6925
7245
  return out;
@@ -6953,13 +7273,13 @@ async function addK8sResources(graph, scanPath) {
6953
7273
  // src/extract/infra/cloudflare.ts
6954
7274
  init_cjs_shims();
6955
7275
  var import_node_fs19 = require("fs");
6956
- var import_node_path35 = __toESM(require("path"), 1);
7276
+ var import_node_path36 = __toESM(require("path"), 1);
6957
7277
  var import_smol_toml2 = require("smol-toml");
6958
- var import_types25 = require("@neat.is/types");
7278
+ var import_types26 = require("@neat.is/types");
6959
7279
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
6960
7280
  async function readWranglerConfig(dir) {
6961
7281
  for (const filename of WRANGLER_FILENAMES) {
6962
- const abs = import_node_path35.default.join(dir, filename);
7282
+ const abs = import_node_path36.default.join(dir, filename);
6963
7283
  if (!await exists(abs)) continue;
6964
7284
  const raw = await import_node_fs19.promises.readFile(abs, "utf8");
6965
7285
  const config = filename === "wrangler.toml" ? (0, import_smol_toml2.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -7003,8 +7323,8 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
7003
7323
  source: anchorId,
7004
7324
  target: node.id,
7005
7325
  type: edgeType,
7006
- provenance: import_types25.Provenance.EXTRACTED,
7007
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7326
+ provenance: import_types26.Provenance.EXTRACTED,
7327
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7008
7328
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
7009
7329
  };
7010
7330
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7022,11 +7342,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7022
7342
  try {
7023
7343
  read = await readWranglerConfig(service.dir);
7024
7344
  } catch (err) {
7025
- recordExtractionError("infra cloudflare", import_node_path35.default.relative(scanPath, service.dir), err);
7345
+ recordExtractionError("infra cloudflare", import_node_path36.default.relative(scanPath, service.dir), err);
7026
7346
  continue;
7027
7347
  }
7028
7348
  if (!read || !read.config.name) continue;
7029
- const evidenceFile = toPosix2(import_node_path35.default.relative(scanPath, import_node_path35.default.join(service.dir, read.relFile)));
7349
+ const evidenceFile = toPosix2(import_node_path36.default.relative(scanPath, import_node_path36.default.join(service.dir, read.relFile)));
7030
7350
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
7031
7351
  }
7032
7352
  for (const worker of discovered) {
@@ -7038,7 +7358,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7038
7358
  }
7039
7359
  let anchorId = service.node.id;
7040
7360
  if (config.main) {
7041
- const entryRelPath = toPosix2(import_node_path35.default.normalize(config.main));
7361
+ const entryRelPath = toPosix2(import_node_path36.default.normalize(config.main));
7042
7362
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
7043
7363
  graph,
7044
7364
  service.pkg.name,
@@ -7065,15 +7385,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7065
7385
  nodesAdded++;
7066
7386
  }
7067
7387
  if (runtimeNode.id !== anchorId) {
7068
- const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id, import_types25.EdgeType.RUNS_ON);
7388
+ const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id, import_types26.EdgeType.RUNS_ON);
7069
7389
  if (!graph.hasEdge(runsOnId)) {
7070
7390
  const edge = {
7071
7391
  id: runsOnId,
7072
7392
  source: anchorId,
7073
7393
  target: runtimeNode.id,
7074
- type: import_types25.EdgeType.RUNS_ON,
7075
- provenance: import_types25.Provenance.EXTRACTED,
7076
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7394
+ type: import_types26.EdgeType.RUNS_ON,
7395
+ provenance: import_types26.Provenance.EXTRACTED,
7396
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7077
7397
  evidence: {
7078
7398
  file: evidenceFile,
7079
7399
  ...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
@@ -7087,7 +7407,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7087
7407
  const result = addResourceEdge(
7088
7408
  graph,
7089
7409
  anchorId,
7090
- import_types25.EdgeType.CONNECTS_TO,
7410
+ import_types26.EdgeType.CONNECTS_TO,
7091
7411
  "cloudflare-route",
7092
7412
  route,
7093
7413
  evidenceFile,
@@ -7111,7 +7431,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7111
7431
  const result = addResourceEdge(
7112
7432
  graph,
7113
7433
  anchorId,
7114
- import_types25.EdgeType.DEPENDS_ON,
7434
+ import_types26.EdgeType.DEPENDS_ON,
7115
7435
  group.kind,
7116
7436
  name,
7117
7437
  evidenceFile,
@@ -7125,7 +7445,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7125
7445
  const result = addResourceEdge(
7126
7446
  graph,
7127
7447
  anchorId,
7128
- import_types25.EdgeType.DEPENDS_ON,
7448
+ import_types26.EdgeType.DEPENDS_ON,
7129
7449
  "cloudflare-cron",
7130
7450
  cron,
7131
7451
  evidenceFile,
@@ -7138,7 +7458,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7138
7458
  const result = addResourceEdge(
7139
7459
  graph,
7140
7460
  anchorId,
7141
- import_types25.EdgeType.DEPENDS_ON,
7461
+ import_types26.EdgeType.DEPENDS_ON,
7142
7462
  "cloudflare-env-var",
7143
7463
  varName,
7144
7464
  evidenceFile,
@@ -7151,15 +7471,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7151
7471
  if (!svc.service) continue;
7152
7472
  const target = workerIndex.get(svc.service);
7153
7473
  if (target && target.anchorId !== anchorId) {
7154
- const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId, import_types25.EdgeType.CALLS);
7474
+ const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId, import_types26.EdgeType.CALLS);
7155
7475
  if (!graph.hasEdge(edgeId)) {
7156
7476
  const edge = {
7157
7477
  id: edgeId,
7158
7478
  source: anchorId,
7159
7479
  target: target.anchorId,
7160
- type: import_types25.EdgeType.CALLS,
7161
- provenance: import_types25.Provenance.EXTRACTED,
7162
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7480
+ type: import_types26.EdgeType.CALLS,
7481
+ provenance: import_types26.Provenance.EXTRACTED,
7482
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7163
7483
  evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
7164
7484
  };
7165
7485
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7170,7 +7490,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7170
7490
  const result = addResourceEdge(
7171
7491
  graph,
7172
7492
  anchorId,
7173
- import_types25.EdgeType.DEPENDS_ON,
7493
+ import_types26.EdgeType.DEPENDS_ON,
7174
7494
  "cloudflare-service-binding",
7175
7495
  svc.service,
7176
7496
  evidenceFile,
@@ -7186,12 +7506,12 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7186
7506
  // src/extract/infra/vercel.ts
7187
7507
  init_cjs_shims();
7188
7508
  var import_node_fs20 = require("fs");
7189
- var import_node_path36 = __toESM(require("path"), 1);
7190
- var import_types26 = require("@neat.is/types");
7509
+ var import_node_path37 = __toESM(require("path"), 1);
7510
+ var import_types27 = require("@neat.is/types");
7191
7511
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
7192
7512
  async function readVercelConfig(dir) {
7193
7513
  for (const filename of VERCEL_CONFIG_FILENAMES) {
7194
- const abs = import_node_path36.default.join(dir, filename);
7514
+ const abs = import_node_path37.default.join(dir, filename);
7195
7515
  if (!await exists(abs)) continue;
7196
7516
  const raw = await import_node_fs20.promises.readFile(abs, "utf8");
7197
7517
  const config = JSON.parse(maskCommentsInSource(raw));
@@ -7200,7 +7520,7 @@ async function readVercelConfig(dir) {
7200
7520
  return null;
7201
7521
  }
7202
7522
  async function readLinkedProjectName(dir) {
7203
- const abs = import_node_path36.default.join(dir, ".vercel", "project.json");
7523
+ const abs = import_node_path37.default.join(dir, ".vercel", "project.json");
7204
7524
  if (!await exists(abs)) return void 0;
7205
7525
  const parsed = JSON.parse(await import_node_fs20.promises.readFile(abs, "utf8"));
7206
7526
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
@@ -7218,7 +7538,7 @@ async function addVercelServices(graph, services, scanPath) {
7218
7538
  read = await readVercelConfig(service.dir);
7219
7539
  projectName = await readLinkedProjectName(service.dir);
7220
7540
  } catch (err) {
7221
- recordExtractionError("infra vercel", import_node_path36.default.relative(scanPath, service.dir), err);
7541
+ recordExtractionError("infra vercel", import_node_path37.default.relative(scanPath, service.dir), err);
7222
7542
  continue;
7223
7543
  }
7224
7544
  if (!read && !projectName) continue;
@@ -7234,7 +7554,7 @@ async function addVercelServices(graph, services, scanPath) {
7234
7554
  const anchorId = service.node.id;
7235
7555
  if (!read) continue;
7236
7556
  const { config, relFile, raw } = read;
7237
- const evidenceFile = toPosix2(import_node_path36.default.relative(scanPath, import_node_path36.default.join(service.dir, relFile)));
7557
+ const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, relFile)));
7238
7558
  const add = (edgeType, kind, name) => {
7239
7559
  if (!name) return;
7240
7560
  const result = emitPlatformResourceEdge(
@@ -7250,12 +7570,12 @@ async function addVercelServices(graph, services, scanPath) {
7250
7570
  nodesAdded += result.nodesAdded;
7251
7571
  edgesAdded += result.edgesAdded;
7252
7572
  };
7253
- add(import_types26.EdgeType.RUNS_ON, "vercel", "vercel");
7254
- for (const cron of config.crons ?? []) add(import_types26.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
7255
- for (const varName of Object.keys(config.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7256
- for (const varName of Object.keys(config.build?.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7573
+ add(import_types27.EdgeType.RUNS_ON, "vercel", "vercel");
7574
+ for (const cron of config.crons ?? []) add(import_types27.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
7575
+ for (const varName of Object.keys(config.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7576
+ for (const varName of Object.keys(config.build?.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7257
7577
  for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
7258
- add(import_types26.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7578
+ add(import_types27.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7259
7579
  }
7260
7580
  }
7261
7581
  return { nodesAdded, edgesAdded };
@@ -7264,13 +7584,13 @@ async function addVercelServices(graph, services, scanPath) {
7264
7584
  // src/extract/infra/railway.ts
7265
7585
  init_cjs_shims();
7266
7586
  var import_node_fs21 = require("fs");
7267
- var import_node_path37 = __toESM(require("path"), 1);
7587
+ var import_node_path38 = __toESM(require("path"), 1);
7268
7588
  var import_smol_toml3 = require("smol-toml");
7269
- var import_types27 = require("@neat.is/types");
7589
+ var import_types28 = require("@neat.is/types");
7270
7590
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
7271
7591
  async function readRailwayConfig(dir) {
7272
7592
  for (const filename of RAILWAY_FILENAMES) {
7273
- const abs = import_node_path37.default.join(dir, filename);
7593
+ const abs = import_node_path38.default.join(dir, filename);
7274
7594
  if (!await exists(abs)) continue;
7275
7595
  const raw = await import_node_fs21.promises.readFile(abs, "utf8");
7276
7596
  const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -7286,7 +7606,7 @@ async function addRailwayServices(graph, services, scanPath) {
7286
7606
  try {
7287
7607
  read = await readRailwayConfig(service.dir);
7288
7608
  } catch (err) {
7289
- recordExtractionError("infra railway", import_node_path37.default.relative(scanPath, service.dir), err);
7609
+ recordExtractionError("infra railway", import_node_path38.default.relative(scanPath, service.dir), err);
7290
7610
  continue;
7291
7611
  }
7292
7612
  if (!read) continue;
@@ -7296,7 +7616,7 @@ async function addRailwayServices(graph, services, scanPath) {
7296
7616
  }
7297
7617
  const anchorId = service.node.id;
7298
7618
  const { config, relFile, raw } = read;
7299
- const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, relFile)));
7619
+ const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
7300
7620
  const add = (edgeType, kind, name) => {
7301
7621
  if (!name) return;
7302
7622
  const result = emitPlatformResourceEdge(
@@ -7312,9 +7632,9 @@ async function addRailwayServices(graph, services, scanPath) {
7312
7632
  nodesAdded += result.nodesAdded;
7313
7633
  edgesAdded += result.edgesAdded;
7314
7634
  };
7315
- add(import_types27.EdgeType.RUNS_ON, "railway", "railway");
7316
- add(import_types27.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
7317
- add(import_types27.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
7635
+ add(import_types28.EdgeType.RUNS_ON, "railway", "railway");
7636
+ add(import_types28.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
7637
+ add(import_types28.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
7318
7638
  }
7319
7639
  return { nodesAdded, edgesAdded };
7320
7640
  }
@@ -7322,12 +7642,12 @@ async function addRailwayServices(graph, services, scanPath) {
7322
7642
  // src/extract/infra/supabase.ts
7323
7643
  init_cjs_shims();
7324
7644
  var import_node_fs22 = require("fs");
7325
- var import_node_path38 = __toESM(require("path"), 1);
7645
+ var import_node_path39 = __toESM(require("path"), 1);
7326
7646
  var import_smol_toml4 = require("smol-toml");
7327
- var import_types28 = require("@neat.is/types");
7647
+ var import_types29 = require("@neat.is/types");
7328
7648
  async function readSupabaseConfig(dir) {
7329
- const relFile = import_node_path38.default.join("supabase", "config.toml");
7330
- const abs = import_node_path38.default.join(dir, relFile);
7649
+ const relFile = import_node_path39.default.join("supabase", "config.toml");
7650
+ const abs = import_node_path39.default.join(dir, relFile);
7331
7651
  if (!await exists(abs)) return null;
7332
7652
  const raw = await import_node_fs22.promises.readFile(abs, "utf8");
7333
7653
  const config = (0, import_smol_toml4.parse)(raw);
@@ -7341,7 +7661,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
7341
7661
  try {
7342
7662
  read = await readSupabaseConfig(service.dir);
7343
7663
  } catch (err) {
7344
- recordExtractionError("infra supabase", import_node_path38.default.relative(scanPath, service.dir), err);
7664
+ recordExtractionError("infra supabase", import_node_path39.default.relative(scanPath, service.dir), err);
7345
7665
  continue;
7346
7666
  }
7347
7667
  if (!read) continue;
@@ -7356,7 +7676,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
7356
7676
  });
7357
7677
  }
7358
7678
  const anchorId = service.node.id;
7359
- const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
7679
+ const evidenceFile = toPosix2(import_node_path39.default.relative(scanPath, import_node_path39.default.join(service.dir, relFile)));
7360
7680
  const add = (edgeType, kind, name) => {
7361
7681
  if (!name) return;
7362
7682
  const result = emitPlatformResourceEdge(
@@ -7372,10 +7692,10 @@ async function addSupabaseProjects(graph, services, scanPath) {
7372
7692
  nodesAdded += result.nodesAdded;
7373
7693
  edgesAdded += result.edgesAdded;
7374
7694
  };
7375
- add(import_types28.EdgeType.RUNS_ON, "supabase", "supabase");
7376
- for (const fn of Object.keys(config.functions ?? {})) add(import_types28.EdgeType.DEPENDS_ON, "supabase-function", fn);
7377
- if (config.storage) add(import_types28.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
7378
- if (config.auth) add(import_types28.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
7695
+ add(import_types29.EdgeType.RUNS_ON, "supabase", "supabase");
7696
+ for (const fn of Object.keys(config.functions ?? {})) add(import_types29.EdgeType.DEPENDS_ON, "supabase-function", fn);
7697
+ if (config.storage) add(import_types29.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
7698
+ if (config.auth) add(import_types29.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
7379
7699
  }
7380
7700
  return { nodesAdded, edgesAdded };
7381
7701
  }
@@ -7397,17 +7717,17 @@ async function addInfra(graph, scanPath, services) {
7397
7717
  }
7398
7718
 
7399
7719
  // src/extract/index.ts
7400
- var import_node_path40 = __toESM(require("path"), 1);
7720
+ var import_node_path41 = __toESM(require("path"), 1);
7401
7721
 
7402
7722
  // src/extract/retire.ts
7403
7723
  init_cjs_shims();
7404
7724
  var import_node_fs23 = require("fs");
7405
- var import_node_path39 = __toESM(require("path"), 1);
7406
- var import_types29 = require("@neat.is/types");
7725
+ var import_node_path40 = __toESM(require("path"), 1);
7726
+ var import_types30 = require("@neat.is/types");
7407
7727
  function dropOrphanedFileNodes(graph) {
7408
7728
  const orphans = [];
7409
7729
  graph.forEachNode((id, attrs) => {
7410
- if (attrs.type !== import_types29.NodeType.FileNode) return;
7730
+ if (attrs.type !== import_types30.NodeType.FileNode) return;
7411
7731
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
7412
7732
  orphans.push(id);
7413
7733
  }
@@ -7420,14 +7740,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
7420
7740
  const bases = [scanPath, ...serviceDirs];
7421
7741
  graph.forEachEdge((id, attrs) => {
7422
7742
  const edge = attrs;
7423
- if (edge.provenance !== import_types29.Provenance.EXTRACTED) return;
7743
+ if (edge.provenance !== import_types30.Provenance.EXTRACTED) return;
7424
7744
  const evidenceFile = edge.evidence?.file;
7425
7745
  if (!evidenceFile) return;
7426
- if (import_node_path39.default.isAbsolute(evidenceFile)) {
7746
+ if (import_node_path40.default.isAbsolute(evidenceFile)) {
7427
7747
  if (!(0, import_node_fs23.existsSync)(evidenceFile)) toDrop.push(id);
7428
7748
  return;
7429
7749
  }
7430
- const found = bases.some((base) => (0, import_node_fs23.existsSync)(import_node_path39.default.join(base, evidenceFile)));
7750
+ const found = bases.some((base) => (0, import_node_fs23.existsSync)(import_node_path40.default.join(base, evidenceFile)));
7431
7751
  if (!found) toDrop.push(id);
7432
7752
  });
7433
7753
  for (const id of toDrop) graph.dropEdge(id);
@@ -7469,7 +7789,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7469
7789
  }
7470
7790
  const droppedEntries = drainDroppedExtracted();
7471
7791
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
7472
- const rejectedPath = import_node_path40.default.join(import_node_path40.default.dirname(opts.errorsPath), "rejected.ndjson");
7792
+ const rejectedPath = import_node_path41.default.join(import_node_path41.default.dirname(opts.errorsPath), "rejected.ndjson");
7473
7793
  try {
7474
7794
  await writeRejectedExtracted(droppedEntries, rejectedPath);
7475
7795
  } catch (err) {
@@ -7504,8 +7824,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7504
7824
  // src/persist.ts
7505
7825
  init_cjs_shims();
7506
7826
  var import_node_fs24 = require("fs");
7507
- var import_node_path41 = __toESM(require("path"), 1);
7508
- var import_types30 = require("@neat.is/types");
7827
+ var import_node_path42 = __toESM(require("path"), 1);
7828
+ var import_types31 = require("@neat.is/types");
7509
7829
  var SCHEMA_VERSION = 4;
7510
7830
  function migrateV1ToV2(payload) {
7511
7831
  const nodes = payload.graph.nodes;
@@ -7527,12 +7847,12 @@ function migrateV2ToV3(payload) {
7527
7847
  for (const edge of edges) {
7528
7848
  const attrs = edge.attributes;
7529
7849
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
7530
- attrs.provenance = import_types30.Provenance.OBSERVED;
7850
+ attrs.provenance = import_types31.Provenance.OBSERVED;
7531
7851
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
7532
7852
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
7533
7853
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
7534
7854
  if (type && source && target) {
7535
- const newId = (0, import_types30.observedEdgeId)(source, target, type);
7855
+ const newId = (0, import_types31.observedEdgeId)(source, target, type);
7536
7856
  attrs.id = newId;
7537
7857
  if (edge.key) edge.key = newId;
7538
7858
  }
@@ -7541,7 +7861,7 @@ function migrateV2ToV3(payload) {
7541
7861
  return { ...payload, schemaVersion: 3 };
7542
7862
  }
7543
7863
  async function ensureDir(filePath) {
7544
- await import_node_fs24.promises.mkdir(import_node_path41.default.dirname(filePath), { recursive: true });
7864
+ await import_node_fs24.promises.mkdir(import_node_path42.default.dirname(filePath), { recursive: true });
7545
7865
  }
7546
7866
  async function saveGraphToDisk(graph, outPath) {
7547
7867
  await ensureDir(outPath);
@@ -7622,23 +7942,23 @@ function startPersistLoop(graph, outPath, opts = {}) {
7622
7942
 
7623
7943
  // src/projects.ts
7624
7944
  init_cjs_shims();
7625
- var import_node_path42 = __toESM(require("path"), 1);
7945
+ var import_node_path43 = __toESM(require("path"), 1);
7626
7946
  function pathsForProject(project, baseDir) {
7627
7947
  if (project === DEFAULT_PROJECT) {
7628
7948
  return {
7629
- snapshotPath: import_node_path42.default.join(baseDir, "graph.json"),
7630
- errorsPath: import_node_path42.default.join(baseDir, "errors.ndjson"),
7631
- staleEventsPath: import_node_path42.default.join(baseDir, "stale-events.ndjson"),
7632
- embeddingsCachePath: import_node_path42.default.join(baseDir, "embeddings.json"),
7633
- policyViolationsPath: import_node_path42.default.join(baseDir, "policy-violations.ndjson")
7949
+ snapshotPath: import_node_path43.default.join(baseDir, "graph.json"),
7950
+ errorsPath: import_node_path43.default.join(baseDir, "errors.ndjson"),
7951
+ staleEventsPath: import_node_path43.default.join(baseDir, "stale-events.ndjson"),
7952
+ embeddingsCachePath: import_node_path43.default.join(baseDir, "embeddings.json"),
7953
+ policyViolationsPath: import_node_path43.default.join(baseDir, "policy-violations.ndjson")
7634
7954
  };
7635
7955
  }
7636
7956
  return {
7637
- snapshotPath: import_node_path42.default.join(baseDir, `${project}.json`),
7638
- errorsPath: import_node_path42.default.join(baseDir, `errors.${project}.ndjson`),
7639
- staleEventsPath: import_node_path42.default.join(baseDir, `stale-events.${project}.ndjson`),
7640
- embeddingsCachePath: import_node_path42.default.join(baseDir, `embeddings.${project}.json`),
7641
- policyViolationsPath: import_node_path42.default.join(baseDir, `policy-violations.${project}.ndjson`)
7957
+ snapshotPath: import_node_path43.default.join(baseDir, `${project}.json`),
7958
+ errorsPath: import_node_path43.default.join(baseDir, `errors.${project}.ndjson`),
7959
+ staleEventsPath: import_node_path43.default.join(baseDir, `stale-events.${project}.ndjson`),
7960
+ embeddingsCachePath: import_node_path43.default.join(baseDir, `embeddings.${project}.json`),
7961
+ policyViolationsPath: import_node_path43.default.join(baseDir, `policy-violations.${project}.ndjson`)
7642
7962
  };
7643
7963
  }
7644
7964
  var Projects = class {
@@ -7676,19 +7996,19 @@ var Projects = class {
7676
7996
  init_cjs_shims();
7677
7997
  var import_fastify = __toESM(require("fastify"), 1);
7678
7998
  var import_cors = __toESM(require("@fastify/cors"), 1);
7679
- var import_types33 = require("@neat.is/types");
7999
+ var import_types34 = require("@neat.is/types");
7680
8000
 
7681
8001
  // src/extend/index.ts
7682
8002
  init_cjs_shims();
7683
8003
  var import_node_fs26 = require("fs");
7684
- var import_node_path44 = __toESM(require("path"), 1);
8004
+ var import_node_path45 = __toESM(require("path"), 1);
7685
8005
  var import_node_os2 = __toESM(require("os"), 1);
7686
8006
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
7687
8007
 
7688
8008
  // src/installers/package-manager.ts
7689
8009
  init_cjs_shims();
7690
8010
  var import_node_fs25 = require("fs");
7691
- var import_node_path43 = __toESM(require("path"), 1);
8011
+ var import_node_path44 = __toESM(require("path"), 1);
7692
8012
  var import_node_child_process = require("child_process");
7693
8013
  var LOCKFILE_PRIORITY = [
7694
8014
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -7710,22 +8030,22 @@ async function exists2(p) {
7710
8030
  }
7711
8031
  }
7712
8032
  async function detectPackageManager(serviceDir) {
7713
- let dir = import_node_path43.default.resolve(serviceDir);
8033
+ let dir = import_node_path44.default.resolve(serviceDir);
7714
8034
  const stops = /* @__PURE__ */ new Set();
7715
8035
  for (let i = 0; i < 64; i++) {
7716
8036
  if (stops.has(dir)) break;
7717
8037
  stops.add(dir);
7718
8038
  for (const candidate of LOCKFILE_PRIORITY) {
7719
- const lockPath = import_node_path43.default.join(dir, candidate.lockfile);
8039
+ const lockPath = import_node_path44.default.join(dir, candidate.lockfile);
7720
8040
  if (await exists2(lockPath)) {
7721
8041
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
7722
8042
  }
7723
8043
  }
7724
- const parent = import_node_path43.default.dirname(dir);
8044
+ const parent = import_node_path44.default.dirname(dir);
7725
8045
  if (parent === dir) break;
7726
8046
  dir = parent;
7727
8047
  }
7728
- return { pm: "npm", cwd: import_node_path43.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
8048
+ return { pm: "npm", cwd: import_node_path44.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
7729
8049
  }
7730
8050
  async function runPackageManagerInstall(cmd) {
7731
8051
  return new Promise((resolve) => {
@@ -7774,22 +8094,53 @@ async function fileExists2(p) {
7774
8094
  }
7775
8095
  }
7776
8096
  async function readPackageJson(scanPath) {
7777
- const pkgPath = import_node_path44.default.join(scanPath, "package.json");
8097
+ const pkgPath = import_node_path45.default.join(scanPath, "package.json");
7778
8098
  const raw = await import_node_fs26.promises.readFile(pkgPath, "utf8");
7779
8099
  return JSON.parse(raw);
7780
8100
  }
8101
+ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
8102
+ "node_modules",
8103
+ "dist",
8104
+ "build",
8105
+ "out",
8106
+ "coverage",
8107
+ "neat-out"
8108
+ ]);
7781
8109
  async function findHookFiles(scanPath) {
7782
- const entries = await import_node_fs26.promises.readdir(scanPath);
7783
- return entries.filter(
7784
- (e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
7785
- ).sort();
8110
+ const found = [];
8111
+ const walk3 = async (dir) => {
8112
+ const entries = await import_node_fs26.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
8113
+ for (const entry2 of entries) {
8114
+ if (entry2.isDirectory()) {
8115
+ if (entry2.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry2.name)) continue;
8116
+ await walk3(import_node_path45.default.join(dir, entry2.name));
8117
+ } else if (entry2.isFile()) {
8118
+ if ((entry2.name.startsWith("instrumentation") || entry2.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry2.name)) {
8119
+ const rel = import_node_path45.default.relative(scanPath, import_node_path45.default.join(dir, entry2.name));
8120
+ found.push(rel.split(import_node_path45.default.sep).join("/"));
8121
+ }
8122
+ }
8123
+ }
8124
+ };
8125
+ await walk3(scanPath);
8126
+ return found.sort();
8127
+ }
8128
+ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
8129
+ let fallback = null;
8130
+ for (const file of hookFiles) {
8131
+ const content = await import_node_fs26.promises.readFile(import_node_path45.default.join(scanPath, file), "utf8");
8132
+ const patched = splicedContent(content, snippet2);
8133
+ if (patched !== null) return { file, content, patched };
8134
+ if (fallback === null) fallback = { file, content };
8135
+ }
8136
+ return { file: fallback.file, content: fallback.content, patched: null };
7786
8137
  }
7787
8138
  function extendLogPath() {
7788
- return process.env.NEAT_EXTEND_LOG ?? import_node_path44.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
8139
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path45.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
7789
8140
  }
7790
8141
  async function appendExtendLog(entry2) {
7791
8142
  const logPath = extendLogPath();
7792
- await import_node_fs26.promises.mkdir(import_node_path44.default.dirname(logPath), { recursive: true });
8143
+ await import_node_fs26.promises.mkdir(import_node_path45.default.dirname(logPath), { recursive: true });
7793
8144
  await import_node_fs26.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
7794
8145
  }
7795
8146
  function splicedContent(fileContent, snippet2) {
@@ -7848,7 +8199,7 @@ function lookupInstrumentation(library, installedVersion) {
7848
8199
  }
7849
8200
  async function describeProjectInstrumentation(ctx) {
7850
8201
  const hookFiles = await findHookFiles(ctx.scanPath);
7851
- const envNeat = await fileExists2(import_node_path44.default.join(ctx.scanPath, ".env.neat"));
8202
+ const envNeat = await fileExists2(import_node_path45.default.join(ctx.scanPath, ".env.neat"));
7852
8203
  const registryInstrPackages = new Set(
7853
8204
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
7854
8205
  );
@@ -7870,16 +8221,22 @@ async function applyExtension(ctx, args, options) {
7870
8221
  );
7871
8222
  }
7872
8223
  for (const file of hookFiles) {
7873
- const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, file), "utf8");
8224
+ const content = await import_node_fs26.promises.readFile(import_node_path45.default.join(ctx.scanPath, file), "utf8");
7874
8225
  if (content.includes(args.registration_snippet)) {
7875
8226
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
7876
8227
  }
7877
8228
  }
7878
- const primaryFile = hookFiles[0];
7879
- const primaryPath = import_node_path44.default.join(ctx.scanPath, primaryFile);
8229
+ const primary = await pickPrimaryHookFile(ctx.scanPath, hookFiles, args.registration_snippet);
8230
+ if (primary.patched === null) {
8231
+ throw new Error(
8232
+ `Could not find instrumentation insertion point in ${hookFiles.join(", ")}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
8233
+ );
8234
+ }
8235
+ const primaryFile = primary.file;
8236
+ const primaryPath = import_node_path45.default.join(ctx.scanPath, primaryFile);
7880
8237
  const filesTouched = [];
7881
8238
  const depsAdded = [];
7882
- const pkgPath = import_node_path44.default.join(ctx.scanPath, "package.json");
8239
+ const pkgPath = import_node_path45.default.join(ctx.scanPath, "package.json");
7883
8240
  const pkg = await readPackageJson(ctx.scanPath);
7884
8241
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
7885
8242
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
@@ -7887,14 +8244,7 @@ async function applyExtension(ctx, args, options) {
7887
8244
  filesTouched.push("package.json");
7888
8245
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
7889
8246
  }
7890
- const hookContent = await import_node_fs26.promises.readFile(primaryPath, "utf8");
7891
- const patched = splicedContent(hookContent, args.registration_snippet);
7892
- if (!patched) {
7893
- throw new Error(
7894
- `Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
7895
- );
7896
- }
7897
- await import_node_fs26.promises.writeFile(primaryPath, patched, "utf8");
8247
+ await import_node_fs26.promises.writeFile(primaryPath, primary.patched, "utf8");
7898
8248
  filesTouched.push(primaryFile);
7899
8249
  const cmd = await detectPackageManager(ctx.scanPath);
7900
8250
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -7925,7 +8275,7 @@ async function dryRunExtension(ctx, args) {
7925
8275
  };
7926
8276
  }
7927
8277
  for (const file of hookFiles) {
7928
- const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, file), "utf8");
8278
+ const content = await import_node_fs26.promises.readFile(import_node_path45.default.join(ctx.scanPath, file), "utf8");
7929
8279
  if (content.includes(args.registration_snippet)) {
7930
8280
  return {
7931
8281
  library: args.library,
@@ -7936,7 +8286,7 @@ async function dryRunExtension(ctx, args) {
7936
8286
  };
7937
8287
  }
7938
8288
  }
7939
- const primaryFile = hookFiles[0];
8289
+ const primary = await pickPrimaryHookFile(ctx.scanPath, hookFiles, args.registration_snippet);
7940
8290
  const filesTouched = [];
7941
8291
  const depsToAdd = [];
7942
8292
  let packageJsonPatch = {};
@@ -7947,10 +8297,8 @@ async function dryRunExtension(ctx, args) {
7947
8297
  depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
7948
8298
  filesTouched.push("package.json");
7949
8299
  }
7950
- const hookContent = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, primaryFile), "utf8");
7951
- const patched = splicedContent(hookContent, args.registration_snippet);
7952
- if (patched) {
7953
- filesTouched.push(primaryFile);
8300
+ if (primary.patched !== null) {
8301
+ filesTouched.push(primary.file);
7954
8302
  templatePatch = `+ ${args.registration_snippet}`;
7955
8303
  } else {
7956
8304
  templatePatch = "Could not find insertion point in hook file.";
@@ -7968,7 +8316,7 @@ async function rollbackExtension(ctx, args) {
7968
8316
  if (!match) {
7969
8317
  return { undone: false, message: "no apply found for library" };
7970
8318
  }
7971
- const pkgPath = import_node_path44.default.join(ctx.scanPath, "package.json");
8319
+ const pkgPath = import_node_path45.default.join(ctx.scanPath, "package.json");
7972
8320
  if (await fileExists2(pkgPath)) {
7973
8321
  const pkg = await readPackageJson(ctx.scanPath);
7974
8322
  if (pkg.dependencies?.[match.instrumentation_package]) {
@@ -7979,7 +8327,7 @@ async function rollbackExtension(ctx, args) {
7979
8327
  }
7980
8328
  const hookFiles = await findHookFiles(ctx.scanPath);
7981
8329
  for (const file of hookFiles) {
7982
- const filePath = import_node_path44.default.join(ctx.scanPath, file);
8330
+ const filePath = import_node_path45.default.join(ctx.scanPath, file);
7983
8331
  const content = await import_node_fs26.promises.readFile(filePath, "utf8");
7984
8332
  if (content.includes(match.registration_snippet)) {
7985
8333
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
@@ -7995,39 +8343,39 @@ async function rollbackExtension(ctx, args) {
7995
8343
 
7996
8344
  // src/divergences.ts
7997
8345
  init_cjs_shims();
7998
- var import_types31 = require("@neat.is/types");
8346
+ var import_types32 = require("@neat.is/types");
7999
8347
  function bucketKey(source, target, type) {
8000
8348
  return `${type}|${source}|${target}`;
8001
8349
  }
8002
8350
  function bucketSourceFor(graph, edge) {
8003
- if (edge.type !== import_types31.EdgeType.CONNECTS_TO) return edge.source;
8004
- const parsed = (0, import_types31.parseFileId)(edge.source);
8351
+ if (edge.type !== import_types32.EdgeType.CONNECTS_TO) return edge.source;
8352
+ const parsed = (0, import_types32.parseFileId)(edge.source);
8005
8353
  if (!parsed || !graph.hasNode(edge.target)) return edge.source;
8006
8354
  const target = graph.getNodeAttributes(edge.target);
8007
- if (target.type !== import_types31.NodeType.DatabaseNode) return edge.source;
8008
- return (0, import_types31.serviceId)(parsed.service);
8355
+ if (target.type !== import_types32.NodeType.DatabaseNode) return edge.source;
8356
+ return (0, import_types32.serviceId)(parsed.service);
8009
8357
  }
8010
8358
  function bucketEdges(graph) {
8011
8359
  const buckets2 = /* @__PURE__ */ new Map();
8012
8360
  graph.forEachEdge((id, attrs) => {
8013
8361
  const e = attrs;
8014
- const parsed = (0, import_types31.parseEdgeId)(id);
8362
+ const parsed = (0, import_types32.parseEdgeId)(id);
8015
8363
  const provenance = parsed?.provenance ?? e.provenance;
8016
8364
  const source = bucketSourceFor(graph, e);
8017
8365
  const key = bucketKey(source, e.target, e.type);
8018
8366
  const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
8019
8367
  switch (provenance) {
8020
- case import_types31.Provenance.EXTRACTED:
8368
+ case import_types32.Provenance.EXTRACTED:
8021
8369
  cur.extracted = e;
8022
8370
  break;
8023
- case import_types31.Provenance.OBSERVED:
8371
+ case import_types32.Provenance.OBSERVED:
8024
8372
  cur.observed = e;
8025
8373
  break;
8026
- case import_types31.Provenance.INFERRED:
8374
+ case import_types32.Provenance.INFERRED:
8027
8375
  cur.inferred = e;
8028
8376
  break;
8029
8377
  default:
8030
- if (e.provenance === import_types31.Provenance.STALE) cur.stale = e;
8378
+ if (e.provenance === import_types32.Provenance.STALE) cur.stale = e;
8031
8379
  }
8032
8380
  buckets2.set(key, cur);
8033
8381
  });
@@ -8036,12 +8384,12 @@ function bucketEdges(graph) {
8036
8384
  function nodeIsFrontier(graph, nodeId) {
8037
8385
  if (!graph.hasNode(nodeId)) return false;
8038
8386
  const attrs = graph.getNodeAttributes(nodeId);
8039
- return attrs.type === import_types31.NodeType.FrontierNode;
8387
+ return attrs.type === import_types32.NodeType.FrontierNode;
8040
8388
  }
8041
8389
  function nodeIsWebsocketChannel(graph, nodeId) {
8042
8390
  if (!graph.hasNode(nodeId)) return false;
8043
8391
  const attrs = graph.getNodeAttributes(nodeId);
8044
- return attrs.type === import_types31.NodeType.WebSocketChannelNode;
8392
+ return attrs.type === import_types32.NodeType.WebSocketChannelNode;
8045
8393
  }
8046
8394
  function clampConfidence(n) {
8047
8395
  if (!Number.isFinite(n)) return 0;
@@ -8061,14 +8409,14 @@ function gradedConfidence(edge) {
8061
8409
  return clampConfidence(confidenceForEdge(edge));
8062
8410
  }
8063
8411
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
8064
- import_types31.EdgeType.CALLS,
8065
- import_types31.EdgeType.CONNECTS_TO,
8066
- import_types31.EdgeType.PUBLISHES_TO,
8067
- import_types31.EdgeType.CONSUMES_FROM
8412
+ import_types32.EdgeType.CALLS,
8413
+ import_types32.EdgeType.CONNECTS_TO,
8414
+ import_types32.EdgeType.PUBLISHES_TO,
8415
+ import_types32.EdgeType.CONSUMES_FROM
8068
8416
  ]);
8069
8417
  function detectMissingDivergences(graph, bucket) {
8070
8418
  const out = [];
8071
- if (bucket.type === import_types31.EdgeType.CONTAINS) return out;
8419
+ if (bucket.type === import_types32.EdgeType.CONTAINS) return out;
8072
8420
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
8073
8421
  if (!nodeIsFrontier(graph, bucket.target)) {
8074
8422
  out.push({
@@ -8109,7 +8457,7 @@ function declaredHostFor(svc) {
8109
8457
  function hasExtractedConfiguredBy(graph, svcId) {
8110
8458
  for (const edgeId of graph.outboundEdges(svcId)) {
8111
8459
  const e = graph.getEdgeAttributes(edgeId);
8112
- if (e.type === import_types31.EdgeType.CONFIGURED_BY && e.provenance === import_types31.Provenance.EXTRACTED) {
8460
+ if (e.type === import_types32.EdgeType.CONFIGURED_BY && e.provenance === import_types32.Provenance.EXTRACTED) {
8113
8461
  return true;
8114
8462
  }
8115
8463
  }
@@ -8122,10 +8470,10 @@ function detectHostMismatch(graph, svcId, svc) {
8122
8470
  const out = [];
8123
8471
  for (const edgeId of graph.outboundEdges(svcId)) {
8124
8472
  const edge = graph.getEdgeAttributes(edgeId);
8125
- if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
8126
- if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
8473
+ if (edge.type !== import_types32.EdgeType.CONNECTS_TO) continue;
8474
+ if (edge.provenance !== import_types32.Provenance.OBSERVED) continue;
8127
8475
  const target = graph.getNodeAttributes(edge.target);
8128
- if (target.type !== import_types31.NodeType.DatabaseNode) continue;
8476
+ if (target.type !== import_types32.NodeType.DatabaseNode) continue;
8129
8477
  const observedHost = target.host?.trim();
8130
8478
  if (!observedHost) continue;
8131
8479
  if (observedHost === declaredHost) continue;
@@ -8147,10 +8495,10 @@ function detectCompatDivergences(graph, svcId, svc) {
8147
8495
  const deps = svc.dependencies ?? {};
8148
8496
  for (const edgeId of graph.outboundEdges(svcId)) {
8149
8497
  const edge = graph.getEdgeAttributes(edgeId);
8150
- if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
8151
- if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
8498
+ if (edge.type !== import_types32.EdgeType.CONNECTS_TO) continue;
8499
+ if (edge.provenance !== import_types32.Provenance.OBSERVED) continue;
8152
8500
  const target = graph.getNodeAttributes(edge.target);
8153
- if (target.type !== import_types31.NodeType.DatabaseNode) continue;
8501
+ if (target.type !== import_types32.NodeType.DatabaseNode) continue;
8154
8502
  for (const pair of compatPairs()) {
8155
8503
  if (pair.engine !== target.engine) continue;
8156
8504
  const declared = deps[pair.driver];
@@ -8209,7 +8557,7 @@ function suppressHostMismatchHalves(all) {
8209
8557
  for (const d of all) {
8210
8558
  if (d.type !== "host-mismatch") continue;
8211
8559
  observedHalf.add(`${d.source}->${d.target}`);
8212
- declaredHalf.add((0, import_types31.databaseId)(d.extractedHost));
8560
+ declaredHalf.add((0, import_types32.databaseId)(d.extractedHost));
8213
8561
  }
8214
8562
  if (observedHalf.size === 0) return all;
8215
8563
  return all.filter((d) => {
@@ -8228,7 +8576,7 @@ function computeDivergences(graph, opts = {}) {
8228
8576
  }
8229
8577
  graph.forEachNode((nodeId, attrs) => {
8230
8578
  const n = attrs;
8231
- if (n.type !== import_types31.NodeType.ServiceNode) return;
8579
+ if (n.type !== import_types32.NodeType.ServiceNode) return;
8232
8580
  const svc = n;
8233
8581
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
8234
8582
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -8262,7 +8610,7 @@ function computeDivergences(graph, opts = {}) {
8262
8610
  if (a.source !== b.source) return a.source.localeCompare(b.source);
8263
8611
  return a.target.localeCompare(b.target);
8264
8612
  });
8265
- return import_types31.DivergenceResultSchema.parse({
8613
+ return import_types32.DivergenceResultSchema.parse({
8266
8614
  divergences: filtered,
8267
8615
  totalAffected: filtered.length,
8268
8616
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -8395,23 +8743,23 @@ function canonicalJson(value) {
8395
8743
  init_cjs_shims();
8396
8744
  var import_node_fs28 = require("fs");
8397
8745
  var import_node_os3 = __toESM(require("os"), 1);
8398
- var import_node_path45 = __toESM(require("path"), 1);
8399
- var import_types32 = require("@neat.is/types");
8746
+ var import_node_path46 = __toESM(require("path"), 1);
8747
+ var import_types33 = require("@neat.is/types");
8400
8748
  var LOCK_TIMEOUT_MS = 5e3;
8401
8749
  var LOCK_RETRY_MS = 50;
8402
8750
  function neatHome() {
8403
8751
  const override = process.env.NEAT_HOME;
8404
- if (override && override.length > 0) return import_node_path45.default.resolve(override);
8405
- return import_node_path45.default.join(import_node_os3.default.homedir(), ".neat");
8752
+ if (override && override.length > 0) return import_node_path46.default.resolve(override);
8753
+ return import_node_path46.default.join(import_node_os3.default.homedir(), ".neat");
8406
8754
  }
8407
8755
  function registryPath() {
8408
- return import_node_path45.default.join(neatHome(), "projects.json");
8756
+ return import_node_path46.default.join(neatHome(), "projects.json");
8409
8757
  }
8410
8758
  function registryLockPath() {
8411
- return import_node_path45.default.join(neatHome(), "projects.json.lock");
8759
+ return import_node_path46.default.join(neatHome(), "projects.json.lock");
8412
8760
  }
8413
8761
  function daemonPidPath() {
8414
- return import_node_path45.default.join(neatHome(), "neatd.pid");
8762
+ return import_node_path46.default.join(neatHome(), "neatd.pid");
8415
8763
  }
8416
8764
  function isPidAliveDefault(pid) {
8417
8765
  try {
@@ -8471,7 +8819,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
8471
8819
  }
8472
8820
  }
8473
8821
  async function writeAtomically(target, contents) {
8474
- await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(target), { recursive: true });
8822
+ await import_node_fs28.promises.mkdir(import_node_path46.default.dirname(target), { recursive: true });
8475
8823
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
8476
8824
  const fd = await import_node_fs28.promises.open(tmp, "w");
8477
8825
  try {
@@ -8484,7 +8832,7 @@ async function writeAtomically(target, contents) {
8484
8832
  }
8485
8833
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
8486
8834
  const deadline = Date.now() + timeoutMs;
8487
- await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(lockPath), { recursive: true });
8835
+ await import_node_fs28.promises.mkdir(import_node_path46.default.dirname(lockPath), { recursive: true });
8488
8836
  let probedHolder = false;
8489
8837
  while (true) {
8490
8838
  try {
@@ -8537,10 +8885,10 @@ async function readRegistry() {
8537
8885
  throw err;
8538
8886
  }
8539
8887
  const parsed = JSON.parse(raw);
8540
- return import_types32.RegistryFileSchema.parse(parsed);
8888
+ return import_types33.RegistryFileSchema.parse(parsed);
8541
8889
  }
8542
8890
  async function writeRegistry(reg) {
8543
- const validated = import_types32.RegistryFileSchema.parse(reg);
8891
+ const validated = import_types33.RegistryFileSchema.parse(reg);
8544
8892
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
8545
8893
  }
8546
8894
  async function getProject(name) {
@@ -8685,7 +9033,7 @@ init_auth();
8685
9033
  // src/connectors-config.ts
8686
9034
  init_cjs_shims();
8687
9035
  var import_node_os4 = __toESM(require("os"), 1);
8688
- var import_node_path46 = __toESM(require("path"), 1);
9036
+ var import_node_path47 = __toESM(require("path"), 1);
8689
9037
  var import_node_fs29 = require("fs");
8690
9038
  var CONNECTORS_CONFIG_VERSION = 1;
8691
9039
  var EnvRefUnsetError = class extends Error {
@@ -8700,11 +9048,11 @@ var EnvRefUnsetError = class extends Error {
8700
9048
  };
8701
9049
  function neatHome2() {
8702
9050
  const override = process.env.NEAT_HOME;
8703
- if (override && override.length > 0) return import_node_path46.default.resolve(override);
8704
- return import_node_path46.default.join(import_node_os4.default.homedir(), ".neat");
9051
+ if (override && override.length > 0) return import_node_path47.default.resolve(override);
9052
+ return import_node_path47.default.join(import_node_os4.default.homedir(), ".neat");
8705
9053
  }
8706
9054
  function connectorsConfigPath(home = neatHome2()) {
8707
- return import_node_path46.default.join(home, "connectors.json");
9055
+ return import_node_path47.default.join(home, "connectors.json");
8708
9056
  }
8709
9057
  var MODE_MASK_LOOSER_THAN_0600 = 63;
8710
9058
  async function warnIfModeLooserThan0600(file) {
@@ -9040,11 +9388,11 @@ function registerRoutes(scope, ctx) {
9040
9388
  const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
9041
9389
  const parsed = [];
9042
9390
  for (const c of candidates) {
9043
- const r = import_types33.DivergenceTypeSchema.safeParse(c);
9391
+ const r = import_types34.DivergenceTypeSchema.safeParse(c);
9044
9392
  if (!r.success) {
9045
9393
  return reply.code(400).send({
9046
9394
  error: `unknown divergence type "${c}"`,
9047
- allowed: import_types33.DivergenceTypeSchema.options
9395
+ allowed: import_types34.DivergenceTypeSchema.options
9048
9396
  });
9049
9397
  }
9050
9398
  parsed.push(r.data);
@@ -9315,7 +9663,7 @@ function registerRoutes(scope, ctx) {
9315
9663
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
9316
9664
  let violations = await log.readAll();
9317
9665
  if (req2.query.severity) {
9318
- const sev = import_types33.PolicySeveritySchema.safeParse(req2.query.severity);
9666
+ const sev = import_types34.PolicySeveritySchema.safeParse(req2.query.severity);
9319
9667
  if (!sev.success) {
9320
9668
  return reply.code(400).send({
9321
9669
  error: "invalid severity",
@@ -9354,7 +9702,7 @@ function registerRoutes(scope, ctx) {
9354
9702
  scope.post("/policies/check", async (req2, reply) => {
9355
9703
  const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
9356
9704
  if (!proj) return;
9357
- const parsed = import_types33.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
9705
+ const parsed = import_types34.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
9358
9706
  if (!parsed.success) {
9359
9707
  return reply.code(400).send({
9360
9708
  error: "invalid /policies/check body",
@@ -9488,7 +9836,7 @@ function registerRoutes(scope, ctx) {
9488
9836
  });
9489
9837
  }
9490
9838
  async function buildApi(opts) {
9491
- const app = (0, import_fastify.default)({ logger: false });
9839
+ const app = (0, import_fastify.default)({ logger: false, routerOptions: { maxParamLength: 1024 } });
9492
9840
  await app.register(import_cors.default, { origin: true });
9493
9841
  const env = readAuthEnv();
9494
9842
  const authToken = opts.authToken ?? env.authToken;
@@ -9618,15 +9966,15 @@ init_otel();
9618
9966
 
9619
9967
  // src/connectors/index.ts
9620
9968
  init_cjs_shims();
9621
- var import_types34 = require("@neat.is/types");
9969
+ var import_types35 = require("@neat.is/types");
9622
9970
  var NO_ENV = "unknown";
9623
9971
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
9624
9972
  if (!graph.hasNode(targetNodeId)) return void 0;
9625
9973
  const sites = [];
9626
9974
  for (const edgeId of graph.inboundEdges(targetNodeId)) {
9627
9975
  const edge = graph.getEdgeAttributes(edgeId);
9628
- if (edge.provenance !== import_types34.Provenance.EXTRACTED) continue;
9629
- const parsed = (0, import_types34.parseFileId)(edge.source);
9976
+ if (edge.provenance !== import_types35.Provenance.EXTRACTED) continue;
9977
+ const parsed = (0, import_types35.parseFileId)(edge.source);
9630
9978
  if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
9631
9979
  const site = { relPath: edge.evidence.file };
9632
9980
  if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
@@ -9637,7 +9985,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
9637
9985
  function routeCallSiteFor(graph, targetNodeId) {
9638
9986
  if (!graph.hasNode(targetNodeId)) return void 0;
9639
9987
  const attrs = graph.getNodeAttributes(targetNodeId);
9640
- if (attrs.type !== import_types34.NodeType.RouteNode || !attrs.path) return void 0;
9988
+ if (attrs.type !== import_types35.NodeType.RouteNode || !attrs.path) return void 0;
9641
9989
  const site = { relPath: attrs.path };
9642
9990
  if (attrs.line !== void 0) site.line = attrs.line;
9643
9991
  return site;
@@ -9665,8 +10013,8 @@ async function runConnectorPoll(connector, ctx, graph, resolveTarget) {
9665
10013
  line: callSite.line
9666
10014
  } : void 0;
9667
10015
  const calls = Math.trunc(signal.callCount);
9668
- if (calls < 1) continue;
9669
- const errors = Math.min(Math.max(Math.trunc(signal.errorCount), 0), calls);
10016
+ if (!Number.isFinite(calls) || calls < 1) continue;
10017
+ const errors = Number.isFinite(signal.errorCount) ? Math.min(Math.max(Math.trunc(signal.errorCount), 0), calls) : 0;
9670
10018
  let created = false;
9671
10019
  let ok = true;
9672
10020
  for (let i = 0; i < calls; i++) {
@@ -9811,7 +10159,12 @@ var JUNCTION_DEFAULT_RATE_LIMITS = {
9811
10159
  supabase: { capacity: 30, refillMs: 1e4 },
9812
10160
  // Not a documented API limit at all — a self-imposed ceiling on the raw
9813
10161
  // pg_stat_statements connection (see module header above).
9814
- "supabase-postgres": { capacity: 20, refillMs: 3e3 }
10162
+ "supabase-postgres": { capacity: 20, refillMs: 3e3 },
10163
+ // Push provider (ADR-146): the Drains REST API is touched only by `neat
10164
+ // connector add/remove/test` (provision/deprovision/validate), never a poll
10165
+ // loop, so this bucket is exercised a handful of times per command. Kept
10166
+ // conservative pending a documented Drains-API rate limit.
10167
+ vercel: { capacity: 20, refillMs: 5e3 }
9815
10168
  };
9816
10169
  var JUNCTION_GENERIC_RATE_LIMIT = { capacity: 20, refillMs: 5e3 };
9817
10170
  function defaultRateLimitFor(provider) {
@@ -10103,17 +10456,20 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
10103
10456
  // src/connectors/supabase/map.ts
10104
10457
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
10105
10458
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
10106
- function targetFromRestPath(path53) {
10107
- const rpcMatch = REST_RPC_PATH_RE.exec(path53);
10459
+ function targetFromRestPath(path54) {
10460
+ const rpcMatch = REST_RPC_PATH_RE.exec(path54);
10108
10461
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
10109
- const tableMatch = REST_TABLE_PATH_RE.exec(path53);
10462
+ const tableMatch = REST_TABLE_PATH_RE.exec(path54);
10110
10463
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
10111
10464
  return null;
10112
10465
  }
10113
10466
  var ERROR_STATUS_THRESHOLD = 500;
10114
10467
  function mapEdgeLogRowsToSignals(rows) {
10115
10468
  const buckets2 = /* @__PURE__ */ new Map();
10469
+ if (!Array.isArray(rows)) return [];
10116
10470
  for (const row of rows) {
10471
+ if (!row || typeof row !== "object") continue;
10472
+ if (typeof row.path !== "string" || typeof row.timestamp !== "string") continue;
10117
10473
  const target = targetFromRestPath(row.path);
10118
10474
  if (!target) continue;
10119
10475
  const key = `${target.targetKind}:${target.name}`;
@@ -10154,9 +10510,12 @@ function tableNameFromQueryText(query) {
10154
10510
  function diffPgStatStatementsToSignals(rows, previous, nowIso2) {
10155
10511
  const signals = [];
10156
10512
  const seen = /* @__PURE__ */ new Set();
10513
+ if (!Array.isArray(rows)) return signals;
10157
10514
  for (const row of rows) {
10158
- seen.add(row.queryid);
10515
+ if (!row || typeof row !== "object" || typeof row.queryid !== "string") continue;
10159
10516
  const calls = Number(row.calls);
10517
+ if (!Number.isFinite(calls)) continue;
10518
+ seen.add(row.queryid);
10160
10519
  const prior = previous.get(row.queryid);
10161
10520
  previous.set(row.queryid, { calls });
10162
10521
  if (!prior || calls < prior.calls) continue;
@@ -10209,23 +10568,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
10209
10568
 
10210
10569
  // src/connectors/supabase/resolve.ts
10211
10570
  init_cjs_shims();
10212
- var import_types36 = require("@neat.is/types");
10571
+ var import_types37 = require("@neat.is/types");
10213
10572
  function createSupabaseResolveTarget(graph, config) {
10214
10573
  return (signal, _ctx) => {
10215
10574
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
10216
10575
  return null;
10217
10576
  }
10218
- const subResourceId = (0, import_types36.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
10577
+ const subResourceId = (0, import_types37.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
10219
10578
  if (graph.hasNode(subResourceId)) {
10220
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
10579
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10221
10580
  }
10222
- const bareResourceId = (0, import_types36.infraId)(signal.targetKind, signal.targetName);
10581
+ const bareResourceId = (0, import_types37.infraId)(signal.targetKind, signal.targetName);
10223
10582
  if (graph.hasNode(bareResourceId)) {
10224
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
10583
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10225
10584
  }
10226
- const projectLevelId = (0, import_types36.infraId)("supabase", config.nodeRef);
10585
+ const projectLevelId = (0, import_types37.infraId)("supabase", config.nodeRef);
10227
10586
  if (graph.hasNode(projectLevelId)) {
10228
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
10587
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10229
10588
  }
10230
10589
  return null;
10231
10590
  };
@@ -10318,7 +10677,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
10318
10677
 
10319
10678
  // src/connectors/railway/index.ts
10320
10679
  init_cjs_shims();
10321
- var import_types40 = require("@neat.is/types");
10680
+ var import_types41 = require("@neat.is/types");
10322
10681
 
10323
10682
  // src/connectors/railway/client.ts
10324
10683
  init_cjs_shims();
@@ -10444,7 +10803,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
10444
10803
  const out = [];
10445
10804
  graph.forEachNode((_id, attrs) => {
10446
10805
  const node = attrs;
10447
- if (node.type !== import_types40.NodeType.RouteNode) return;
10806
+ if (node.type !== import_types41.NodeType.RouteNode) return;
10448
10807
  const route = attrs;
10449
10808
  if (route.service !== serviceName) return;
10450
10809
  out.push({
@@ -10480,7 +10839,12 @@ function upsertBucket(buckets2, key, isError, timestamp, build) {
10480
10839
  }
10481
10840
  function mapRailwayHttpLogsToSignals(entries, routeIndex) {
10482
10841
  const buckets2 = /* @__PURE__ */ new Map();
10842
+ if (!Array.isArray(entries)) return [];
10483
10843
  for (const entry2 of entries) {
10844
+ if (!entry2 || typeof entry2 !== "object") continue;
10845
+ if (typeof entry2.method !== "string" || typeof entry2.path !== "string" || typeof entry2.timestamp !== "string") {
10846
+ continue;
10847
+ }
10484
10848
  const method = entry2.method.toUpperCase();
10485
10849
  const normalizedPath = normalizePathTemplate(entry2.path);
10486
10850
  const match = findRailwayRoute(routeIndex, method, normalizedPath);
@@ -10519,8 +10883,11 @@ function mapRailwayHttpLogsToSignals(entries, routeIndex) {
10519
10883
  }
10520
10884
  function mapRailwayNetworkFlowLogsToSignals(entries) {
10521
10885
  const buckets2 = /* @__PURE__ */ new Map();
10886
+ if (!Array.isArray(entries)) return [];
10522
10887
  for (const entry2 of entries) {
10523
- if (!entry2.peerServiceId) continue;
10888
+ if (!entry2 || typeof entry2 !== "object") continue;
10889
+ if (typeof entry2.peerServiceId !== "string" || entry2.peerServiceId.length === 0) continue;
10890
+ if (typeof entry2.timestamp !== "string") continue;
10524
10891
  const isError = entry2.dropCause !== null && entry2.dropCause !== "";
10525
10892
  upsertBucket(buckets2, entry2.peerServiceId, isError, entry2.timestamp, () => ({
10526
10893
  targetKind: PEER_SERVICE_TARGET_KIND,
@@ -10540,12 +10907,12 @@ function createRailwayResolveTarget(config) {
10540
10907
  const serviceName = config.serviceNameById[config.serviceId];
10541
10908
  if (!serviceName) return null;
10542
10909
  if (signal.targetKind === ROUTE_TARGET_KIND) {
10543
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types40.EdgeType.CALLS };
10910
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types41.EdgeType.CALLS };
10544
10911
  }
10545
10912
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
10546
10913
  const peerName = config.serviceNameById[signal.targetName];
10547
10914
  if (!peerName) return null;
10548
- return { targetNodeId: (0, import_types40.serviceId)(peerName), serviceName, edgeType: import_types40.EdgeType.CONNECTS_TO };
10915
+ return { targetNodeId: (0, import_types41.serviceId)(peerName), serviceName, edgeType: import_types41.EdgeType.CONNECTS_TO };
10549
10916
  }
10550
10917
  return null;
10551
10918
  };
@@ -10648,7 +11015,7 @@ async function fetchHttpRequestLogEntries(creds, sinceIso) {
10648
11015
  throw new Error(`Cloud Logging entries.list failed: ${res.status} ${res.statusText}`);
10649
11016
  }
10650
11017
  const json = await res.json();
10651
- out.push(...json.entries ?? []);
11018
+ if (Array.isArray(json.entries)) out.push(...json.entries);
10652
11019
  if (!json.nextPageToken) break;
10653
11020
  pageToken = json.nextPageToken;
10654
11021
  }
@@ -10669,9 +11036,9 @@ function parseFirebaseTargetName(targetName) {
10669
11036
  const secondSep = rest.indexOf(FIELD_SEP);
10670
11037
  if (secondSep === -1) return null;
10671
11038
  const method = rest.slice(0, secondSep);
10672
- const path53 = rest.slice(secondSep + 1);
10673
- if (!resourceName || !method || !path53) return null;
10674
- return { resourceName, method, path: path53 };
11039
+ const path54 = rest.slice(secondSep + 1);
11040
+ if (!resourceName || !method || !path54) return null;
11041
+ return { resourceName, method, path: path54 };
10675
11042
  }
10676
11043
  function resourceNameFor(type, labels) {
10677
11044
  if (!labels) return null;
@@ -10685,7 +11052,7 @@ function resourceNameFor(type, labels) {
10685
11052
  }
10686
11053
  }
10687
11054
  function pathFromRequestUrl(requestUrl) {
10688
- if (!requestUrl) return null;
11055
+ if (typeof requestUrl !== "string" || requestUrl.length === 0) return null;
10689
11056
  if (requestUrl.startsWith("/")) {
10690
11057
  const withoutQuery = requestUrl.split("?")[0];
10691
11058
  return withoutQuery && withoutQuery.length > 0 ? withoutQuery : "/";
@@ -10700,22 +11067,23 @@ function pathFromRequestUrl(requestUrl) {
10700
11067
  }
10701
11068
  var ERROR_STATUS_THRESHOLD2 = 500;
10702
11069
  function mapLogEntryToSignal(entry2) {
11070
+ if (!entry2 || typeof entry2 !== "object") return null;
10703
11071
  const resourceType = entry2.resource?.type;
10704
11072
  if (!resourceType || !isFirebaseResourceType(resourceType)) return null;
10705
11073
  const resourceName = resourceNameFor(resourceType, entry2.resource?.labels);
10706
11074
  if (!resourceName) return null;
10707
11075
  const req2 = entry2.httpRequest;
10708
11076
  if (!req2) return null;
10709
- if (!req2.requestMethod) return null;
11077
+ if (typeof req2.requestMethod !== "string" || req2.requestMethod.length === 0) return null;
10710
11078
  const method = req2.requestMethod.toUpperCase();
10711
- const path53 = pathFromRequestUrl(req2.requestUrl);
10712
- if (path53 === null) return null;
11079
+ const path54 = pathFromRequestUrl(req2.requestUrl);
11080
+ if (path54 === null) return null;
10713
11081
  const timestamp = entry2.timestamp;
10714
- if (!timestamp) return null;
11082
+ if (typeof timestamp !== "string" || timestamp.length === 0) return null;
10715
11083
  const isError = typeof req2.status === "number" && req2.status >= ERROR_STATUS_THRESHOLD2;
10716
11084
  return {
10717
11085
  targetKind: resourceType,
10718
- targetName: packFirebaseTargetName({ resourceName, method, path: path53 }),
11086
+ targetName: packFirebaseTargetName({ resourceName, method, path: path54 }),
10719
11087
  callCount: 1,
10720
11088
  errorCount: isError ? 1 : 0,
10721
11089
  lastObservedIso: timestamp
@@ -10732,7 +11100,7 @@ function mapLogEntriesToSignals(entries) {
10732
11100
 
10733
11101
  // src/connectors/firebase/resolve.ts
10734
11102
  init_cjs_shims();
10735
- var import_types41 = require("@neat.is/types");
11103
+ var import_types42 = require("@neat.is/types");
10736
11104
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
10737
11105
  switch (resourceType) {
10738
11106
  case "cloud_function":
@@ -10747,7 +11115,7 @@ function routeEntriesFor(graph, serviceName) {
10747
11115
  const entries = [];
10748
11116
  graph.forEachNode((_id, attrs) => {
10749
11117
  const node = attrs;
10750
- if (node.type !== import_types41.NodeType.RouteNode) return;
11118
+ if (node.type !== import_types42.NodeType.RouteNode) return;
10751
11119
  const route = attrs;
10752
11120
  if (route.service !== serviceName) return;
10753
11121
  entries.push({
@@ -10779,7 +11147,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
10779
11147
  return {
10780
11148
  targetNodeId: match.routeNodeId,
10781
11149
  serviceName,
10782
- edgeType: import_types41.EdgeType.CALLS
11150
+ edgeType: import_types42.EdgeType.CALLS
10783
11151
  };
10784
11152
  };
10785
11153
  }
@@ -10806,7 +11174,7 @@ init_cjs_shims();
10806
11174
 
10807
11175
  // src/connectors/cloudflare/connector.ts
10808
11176
  init_cjs_shims();
10809
- var import_types43 = require("@neat.is/types");
11177
+ var import_types44 = require("@neat.is/types");
10810
11178
 
10811
11179
  // src/connectors/cloudflare/client.ts
10812
11180
  init_cjs_shims();
@@ -10863,7 +11231,7 @@ async function queryWorkerInvocations(ctx, config, window, fetchImpl = fetch) {
10863
11231
  throw new Error(`cloudflare connector: telemetry query returned an error (${message})`);
10864
11232
  }
10865
11233
  const events = payload.result?.events?.events;
10866
- if (events === void 0) {
11234
+ if (!Array.isArray(events)) {
10867
11235
  console.warn(
10868
11236
  "[neat connector] cloudflare: telemetry query returned success:true but no result.events.events array \u2014 the response shape may have changed; treating as zero events this tick"
10869
11237
  );
@@ -10893,7 +11261,7 @@ var HTTP_METHODS = /* @__PURE__ */ new Set([
10893
11261
  ]);
10894
11262
  var LEADING_TOKEN_RE = /^(\S+)\s+\S/;
10895
11263
  function parseHttpMethodFromTrigger(trigger) {
10896
- if (!trigger) return null;
11264
+ if (typeof trigger !== "string") return null;
10897
11265
  const match = LEADING_TOKEN_RE.exec(trigger.trim());
10898
11266
  const token = match?.[1];
10899
11267
  if (!token) return null;
@@ -10909,25 +11277,28 @@ function parsePathFromTrigger(trigger) {
10909
11277
  }
10910
11278
  var ERROR_STATUS_THRESHOLD3 = 500;
10911
11279
  function mapEventToSignal(event) {
11280
+ if (!event || typeof event !== "object") return null;
10912
11281
  const metadata = event.$metadata;
10913
11282
  const workers = event.$workers;
10914
11283
  const method = parseHttpMethodFromTrigger(metadata?.trigger);
10915
11284
  if (!method) return null;
10916
11285
  const scriptName = workers?.scriptName ?? metadata?.service;
10917
- if (!scriptName) return null;
11286
+ if (typeof scriptName !== "string" || scriptName.length === 0) return null;
10918
11287
  const timestampMs = event.timestamp ?? metadata?.startTime;
10919
11288
  if (typeof timestampMs !== "number" || !Number.isFinite(timestampMs)) return null;
11289
+ const observedAt = new Date(timestampMs);
11290
+ if (Number.isNaN(observedAt.getTime())) return null;
10920
11291
  const statusCode = metadata?.statusCode;
10921
11292
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
10922
- const path53 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
11293
+ const path54 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
10923
11294
  return {
10924
11295
  targetKind: CLOUDFLARE_TARGET_KIND,
10925
11296
  targetName: scriptName,
10926
11297
  callCount: 1,
10927
11298
  errorCount: isError ? 1 : 0,
10928
- lastObservedIso: new Date(timestampMs).toISOString(),
11299
+ lastObservedIso: observedAt.toISOString(),
10929
11300
  method,
10930
- ...path53 ? { path: path53 } : {},
11301
+ ...path54 ? { path: path54 } : {},
10931
11302
  ...typeof statusCode === "number" ? { statusCode } : {},
10932
11303
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
10933
11304
  };
@@ -10967,19 +11338,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
10967
11338
  graph.forEachNode((id, attrs) => {
10968
11339
  if (found) return;
10969
11340
  const a = attrs;
10970
- if (a.type === import_types43.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
11341
+ if (a.type === import_types44.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
10971
11342
  found = id;
10972
11343
  }
10973
11344
  });
10974
11345
  return found;
10975
11346
  }
10976
- function findMatchingRouteNode(graph, serviceName, method, path53) {
10977
- const normalizedPath = normalizePathTemplate(path53);
11347
+ function findMatchingRouteNode(graph, serviceName, method, path54) {
11348
+ const normalizedPath = normalizePathTemplate(path54);
10978
11349
  let found = null;
10979
11350
  graph.forEachNode((id, attrs) => {
10980
11351
  if (found) return;
10981
11352
  const a = attrs;
10982
- if (a.type !== import_types43.NodeType.RouteNode || a.service !== serviceName) return;
11353
+ if (a.type !== import_types44.NodeType.RouteNode || a.service !== serviceName) return;
10983
11354
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
10984
11355
  const routeMethod = (a.method ?? "").toUpperCase();
10985
11356
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -10991,18 +11362,18 @@ function createCloudflareResolveTarget(config, graph) {
10991
11362
  return (signal) => {
10992
11363
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
10993
11364
  const scriptName = signal.targetName;
10994
- const { method, path: path53 } = signal;
11365
+ const { method, path: path54 } = signal;
10995
11366
  const resolveRouteGrain = (serviceName, wholeFileId) => {
10996
- if (!method || !path53) return wholeFileId;
10997
- return findMatchingRouteNode(graph, serviceName, method, path53) ?? wholeFileId;
11367
+ if (!method || !path54) return wholeFileId;
11368
+ return findMatchingRouteNode(graph, serviceName, method, path54) ?? wholeFileId;
10998
11369
  };
10999
11370
  const mapping = config.workers?.[scriptName];
11000
11371
  if (mapping) {
11001
- const wholeFileId = (0, import_types43.fileId)(mapping.service, mapping.entryFile);
11372
+ const wholeFileId = (0, import_types44.fileId)(mapping.service, mapping.entryFile);
11002
11373
  return {
11003
11374
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
11004
11375
  serviceName: mapping.service,
11005
- edgeType: import_types43.EdgeType.CALLS
11376
+ edgeType: import_types44.EdgeType.CALLS
11006
11377
  };
11007
11378
  }
11008
11379
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -11011,18 +11382,124 @@ function createCloudflareResolveTarget(config, graph) {
11011
11382
  return {
11012
11383
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
11013
11384
  serviceName: fileNode.service,
11014
- edgeType: import_types43.EdgeType.CALLS
11385
+ edgeType: import_types44.EdgeType.CALLS
11015
11386
  };
11016
11387
  }
11017
11388
  return {
11018
- targetNodeId: (0, import_types43.infraId)("cloudflare-worker", scriptName),
11389
+ targetNodeId: (0, import_types44.infraId)("cloudflare-worker", scriptName),
11019
11390
  serviceName: scriptName,
11020
- edgeType: import_types43.EdgeType.CALLS,
11391
+ edgeType: import_types44.EdgeType.CALLS,
11021
11392
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
11022
11393
  };
11023
11394
  };
11024
11395
  }
11025
11396
 
11397
+ // src/connectors/vercel/index.ts
11398
+ init_cjs_shims();
11399
+
11400
+ // src/connectors/vercel/client.ts
11401
+ init_cjs_shims();
11402
+ var DEFAULT_API_BASE_URL = "https://api.vercel.com";
11403
+ var DEFAULT_DRAIN_NAME = "neat-otlp";
11404
+ var TRACE_SCHEMAS = { trace: { version: "v1" } };
11405
+ function apiBase(config) {
11406
+ return config.apiBaseUrl ?? DEFAULT_API_BASE_URL;
11407
+ }
11408
+ function teamQuery(config) {
11409
+ return `?teamId=${encodeURIComponent(config.teamId)}`;
11410
+ }
11411
+ function drainDelivery(config, otelToken) {
11412
+ return {
11413
+ type: "http",
11414
+ endpoint: config.endpoint,
11415
+ encoding: "json",
11416
+ headers: bearerAuthHeader(otelToken),
11417
+ ...config.secret ? { secret: config.secret } : {}
11418
+ };
11419
+ }
11420
+ async function describeError(res) {
11421
+ try {
11422
+ const data = await res.json();
11423
+ const message = data?.error?.message;
11424
+ return typeof message === "string" && message.length > 0 ? ` \u2014 ${message}` : "";
11425
+ } catch {
11426
+ return "";
11427
+ }
11428
+ }
11429
+ async function createVercelDrain(config, credentials, fetchImpl = fetch) {
11430
+ const projectIds = config.projectIds ?? [];
11431
+ const body = {
11432
+ name: config.drainName ?? DEFAULT_DRAIN_NAME,
11433
+ projects: projectIds.length > 0 ? "some" : "all",
11434
+ ...projectIds.length > 0 ? { projectIds } : {},
11435
+ schemas: TRACE_SCHEMAS,
11436
+ delivery: drainDelivery(config, credentials.otelToken),
11437
+ source: { kind: "self-served" }
11438
+ };
11439
+ const res = await junctionFetch(
11440
+ `${apiBase(config)}/v1/drains${teamQuery(config)}`,
11441
+ {
11442
+ method: "POST",
11443
+ headers: { "Content-Type": "application/json", ...bearerAuthHeader(credentials.token) },
11444
+ body: JSON.stringify(body)
11445
+ },
11446
+ { provider: "vercel", accountKey: config.teamId, fetchImpl }
11447
+ );
11448
+ if (!res.ok) {
11449
+ throw new Error(
11450
+ `vercel connector: create drain failed (${res.status} ${res.statusText}${await describeError(res)})`
11451
+ );
11452
+ }
11453
+ const payload = await res.json().catch(() => null);
11454
+ if (!payload || typeof payload.id !== "string" || payload.id.length === 0) {
11455
+ throw new Error(
11456
+ "vercel connector: create drain returned no drain id \u2014 the Drains API response shape may have changed"
11457
+ );
11458
+ }
11459
+ return {
11460
+ id: payload.id,
11461
+ ...payload.status ? { status: payload.status } : {},
11462
+ ...payload.disabledReason ? { disabledReason: payload.disabledReason } : {}
11463
+ };
11464
+ }
11465
+ async function deleteVercelDrain(config, drainId, credentials, fetchImpl = fetch) {
11466
+ const res = await junctionFetch(
11467
+ `${apiBase(config)}/v1/drains/${encodeURIComponent(drainId)}${teamQuery(config)}`,
11468
+ { method: "DELETE", headers: { ...bearerAuthHeader(credentials.token) } },
11469
+ { provider: "vercel", accountKey: config.teamId, fetchImpl }
11470
+ );
11471
+ if (res.ok || res.status === 404) return;
11472
+ throw new Error(
11473
+ `vercel connector: delete drain failed (${res.status} ${res.statusText}${await describeError(res)})`
11474
+ );
11475
+ }
11476
+ async function testVercelDrainDelivery(config, credentials, fetchImpl = fetch) {
11477
+ const res = await junctionFetch(
11478
+ `${apiBase(config)}/v1/drains/test${teamQuery(config)}`,
11479
+ {
11480
+ method: "POST",
11481
+ headers: { "Content-Type": "application/json", ...bearerAuthHeader(credentials.token) },
11482
+ body: JSON.stringify({ schemas: TRACE_SCHEMAS, delivery: drainDelivery(config, credentials.otelToken) })
11483
+ },
11484
+ { provider: "vercel", accountKey: config.teamId, fetchImpl }
11485
+ );
11486
+ if (res.status === 401 || res.status === 403) {
11487
+ return { status: "failure", error: `vercel rejected the API token (HTTP ${res.status})` };
11488
+ }
11489
+ if (!res.ok) {
11490
+ return {
11491
+ status: "failure",
11492
+ error: `vercel drain validation failed (${res.status} ${res.statusText}${await describeError(res)})`
11493
+ };
11494
+ }
11495
+ const payload = await res.json().catch(() => null);
11496
+ return {
11497
+ ...payload?.status ? { status: payload.status } : {},
11498
+ ...payload?.error ? { error: payload.error } : {},
11499
+ ...payload?.endpoint ? { endpoint: payload.endpoint } : {}
11500
+ };
11501
+ }
11502
+
11026
11503
  // src/connectors/registry.ts
11027
11504
  var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
11028
11505
  async function authProbe(input) {
@@ -11174,6 +11651,91 @@ var PROVIDER_DISPATCH = {
11174
11651
  }
11175
11652
  }
11176
11653
  };
11654
+ function vercelCredsFrom(credentials) {
11655
+ return { token: String(credentials.token ?? ""), otelToken: String(credentials.otelToken ?? "") };
11656
+ }
11657
+ function vercelConfigFromOptions(options) {
11658
+ const raw = options.projectIds;
11659
+ let projectIds;
11660
+ if (Array.isArray(raw)) projectIds = raw.filter((p) => typeof p === "string");
11661
+ else if (typeof raw === "string" && raw.trim().length > 0) {
11662
+ projectIds = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
11663
+ }
11664
+ return {
11665
+ teamId: String(options.teamId ?? ""),
11666
+ endpoint: String(options.endpoint ?? ""),
11667
+ ...projectIds && projectIds.length > 0 ? { projectIds } : {},
11668
+ ...typeof options.drainId === "string" ? { drainId: options.drainId } : {},
11669
+ ...typeof options.drainName === "string" ? { drainName: options.drainName } : {},
11670
+ ...typeof options.apiBaseUrl === "string" ? { apiBaseUrl: options.apiBaseUrl } : {},
11671
+ ...typeof options.secret === "string" ? { secret: options.secret } : {}
11672
+ };
11673
+ }
11674
+ var PUSH_PROVIDER_DISPATCH = {
11675
+ vercel: {
11676
+ provider: "vercel",
11677
+ // The Vercel access token is the "primary" secret a single `--token`
11678
+ // populates; `otelToken` (the daemon's OTLP bearer) is the second field.
11679
+ primaryCredentialKey: "token",
11680
+ requiredCredentialFields: ["token", "otelToken"],
11681
+ // teamId scopes every Drains call; endpoint is where the drain delivers.
11682
+ // projectIds is optional (absent → the drain covers the whole team).
11683
+ requiredOptionFields: ["teamId", "endpoint"],
11684
+ // POST /v1/drains/test — authenticates the token and pings the endpoint
11685
+ // with a sample event, so `success` means the credential is live *and* the
11686
+ // daemon's OTLP endpoint is reachable and accepted the drain's bearer.
11687
+ async validate({ credentials, options, fetchImpl }) {
11688
+ const result = await testVercelDrainDelivery(
11689
+ vercelConfigFromOptions(options),
11690
+ vercelCredsFrom(credentials),
11691
+ fetchImpl
11692
+ );
11693
+ if (result.status === "success") return { ok: true };
11694
+ return {
11695
+ ok: false,
11696
+ reason: result.error ?? `vercel drain delivery test returned "${result.status ?? "no status"}"`
11697
+ };
11698
+ },
11699
+ // POST /v1/drains — creates the trace drain, returns its id to store in
11700
+ // `options.drainId`. A created-but-not-enabled drain is surfaced as a note,
11701
+ // not a failure (the entry still points at a real drain).
11702
+ async provision({ credentials, options, fetchImpl }) {
11703
+ try {
11704
+ const created = await createVercelDrain(
11705
+ vercelConfigFromOptions(options),
11706
+ vercelCredsFrom(credentials),
11707
+ fetchImpl
11708
+ );
11709
+ const note = created.status && created.status !== "enabled" ? `the drain was created but its status is "${created.status}"${created.disabledReason ? ` (${created.disabledReason})` : ""} \u2014 check the Vercel dashboard` : void 0;
11710
+ return { ok: true, options: { drainId: created.id }, ...note ? { note } : {} };
11711
+ } catch (err) {
11712
+ return { ok: false, reason: err.message };
11713
+ }
11714
+ },
11715
+ // DELETE /v1/drains/{id} — idempotent (deleteVercelDrain treats 404 as
11716
+ // success). No recorded drainId → nothing to delete, still a success.
11717
+ async deprovision({ credentials, options, fetchImpl }) {
11718
+ const drainId = typeof options.drainId === "string" ? options.drainId : "";
11719
+ if (!drainId) {
11720
+ return { ok: true, note: "no drain id was recorded \u2014 nothing to delete on the Vercel side" };
11721
+ }
11722
+ try {
11723
+ await deleteVercelDrain(
11724
+ vercelConfigFromOptions(options),
11725
+ drainId,
11726
+ vercelCredsFrom(credentials),
11727
+ fetchImpl
11728
+ );
11729
+ return { ok: true };
11730
+ } catch (err) {
11731
+ return { ok: false, reason: err.message };
11732
+ }
11733
+ }
11734
+ }
11735
+ };
11736
+ function isPushProvider(provider) {
11737
+ return provider in PUSH_PROVIDER_DISPATCH;
11738
+ }
11177
11739
  function resolveEntryCredentials(dispatch, entry2, env) {
11178
11740
  let credentials;
11179
11741
  try {
@@ -11196,6 +11758,13 @@ function resolveEntryCredentials(dispatch, entry2, env) {
11196
11758
  function buildRegistration(entry2, graph, env = process.env) {
11197
11759
  const dispatch = PROVIDER_DISPATCH[entry2.provider];
11198
11760
  if (!dispatch) {
11761
+ if (isPushProvider(entry2.provider)) {
11762
+ return {
11763
+ ok: false,
11764
+ push: true,
11765
+ reason: `push provider "${entry2.provider}" ingests via the OTLP receiver \u2014 nothing to poll`
11766
+ };
11767
+ }
11199
11768
  return { ok: false, reason: `unknown provider "${entry2.provider}"` };
11200
11769
  }
11201
11770
  const creds = resolveEntryCredentials(dispatch, entry2, env);
@@ -11246,7 +11815,7 @@ async function loadConnectorRegistrations(input) {
11246
11815
  if (!connectorMatchesProject(entry2, project)) continue;
11247
11816
  const result = buildRegistration(entry2, graph, env);
11248
11817
  if (result.ok) registrations.push(result.registration);
11249
- else onSkip?.(entry2, result.reason);
11818
+ else if (!result.push) onSkip?.(entry2, result.reason);
11250
11819
  }
11251
11820
  return registrations;
11252
11821
  }
@@ -11257,7 +11826,7 @@ init_auth();
11257
11826
  // src/unrouted.ts
11258
11827
  init_cjs_shims();
11259
11828
  var import_node_fs30 = require("fs");
11260
- var import_node_path49 = __toESM(require("path"), 1);
11829
+ var import_node_path50 = __toESM(require("path"), 1);
11261
11830
  function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
11262
11831
  return {
11263
11832
  timestamp: now.toISOString(),
@@ -11267,34 +11836,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
11267
11836
  };
11268
11837
  }
11269
11838
  async function appendUnroutedSpan(neatHome4, record) {
11270
- const target = import_node_path49.default.join(neatHome4, "errors.ndjson");
11839
+ const target = import_node_path50.default.join(neatHome4, "errors.ndjson");
11271
11840
  await import_node_fs30.promises.mkdir(neatHome4, { recursive: true });
11272
11841
  await import_node_fs30.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
11273
11842
  }
11274
11843
  function unroutedErrorsPath(neatHome4) {
11275
- return import_node_path49.default.join(neatHome4, "errors.ndjson");
11844
+ return import_node_path50.default.join(neatHome4, "errors.ndjson");
11276
11845
  }
11277
11846
 
11278
11847
  // src/daemon.ts
11279
- var import_types46 = require("@neat.is/types");
11848
+ var import_types47 = require("@neat.is/types");
11280
11849
  function daemonJsonPath(scanPath) {
11281
- return import_node_path50.default.join(scanPath, "neat-out", "daemon.json");
11850
+ return import_node_path51.default.join(scanPath, "neat-out", "daemon.json");
11282
11851
  }
11283
11852
  function daemonsDiscoveryDir(home) {
11284
11853
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
11285
- return import_node_path50.default.join(base, "daemons");
11854
+ return import_node_path51.default.join(base, "daemons");
11286
11855
  }
11287
11856
  function daemonDiscoveryPath(project, home) {
11288
- return import_node_path50.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
11857
+ return import_node_path51.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
11289
11858
  }
11290
11859
  function sanitizeDiscoveryName(project) {
11291
11860
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
11292
11861
  }
11293
11862
  function neatHomeFromEnv() {
11294
11863
  const env = process.env.NEAT_HOME;
11295
- if (env && env.length > 0) return import_node_path50.default.resolve(env);
11864
+ if (env && env.length > 0) return import_node_path51.default.resolve(env);
11296
11865
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
11297
- return import_node_path50.default.join(home, ".neat");
11866
+ return import_node_path51.default.join(home, ".neat");
11298
11867
  }
11299
11868
  function resolveNeatVersion() {
11300
11869
  if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
@@ -11363,11 +11932,11 @@ function teardownSlot(slot) {
11363
11932
  }
11364
11933
  }
11365
11934
  function neatHomeFor(opts) {
11366
- if (opts.neatHome && opts.neatHome.length > 0) return import_node_path50.default.resolve(opts.neatHome);
11935
+ if (opts.neatHome && opts.neatHome.length > 0) return import_node_path51.default.resolve(opts.neatHome);
11367
11936
  const env = process.env.NEAT_HOME;
11368
- if (env && env.length > 0) return import_node_path50.default.resolve(env);
11937
+ if (env && env.length > 0) return import_node_path51.default.resolve(env);
11369
11938
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
11370
- return import_node_path50.default.join(home, ".neat");
11939
+ return import_node_path51.default.join(home, ".neat");
11371
11940
  }
11372
11941
  function routeSpanToProject(serviceName, projects) {
11373
11942
  if (!serviceName) return DEFAULT_PROJECT;
@@ -11411,11 +11980,11 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
11411
11980
  if (!serviceName) return true;
11412
11981
  if (serviceNameMatchesProject(serviceName, project)) return true;
11413
11982
  return graph.someNode(
11414
- (_id, attrs) => attrs.type === import_types46.NodeType.ServiceNode && attrs.name === serviceName
11983
+ (_id, attrs) => attrs.type === import_types47.NodeType.ServiceNode && attrs.name === serviceName
11415
11984
  );
11416
11985
  }
11417
11986
  async function bootstrapProject(entry2, connectors = [], neatHome4) {
11418
- const paths = pathsForProject(entry2.name, import_node_path50.default.join(entry2.path, "neat-out"));
11987
+ const paths = pathsForProject(entry2.name, import_node_path51.default.join(entry2.path, "neat-out"));
11419
11988
  try {
11420
11989
  const stat = await import_node_fs31.promises.stat(entry2.path);
11421
11990
  if (!stat.isDirectory()) {
@@ -11546,7 +12115,7 @@ async function startDaemon(opts = {}) {
11546
12115
  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;
11547
12116
  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;
11548
12117
  const singleProject = projectArg;
11549
- const singleProjectPath = singleProject && projectPathArg ? import_node_path50.default.resolve(projectPathArg) : null;
12118
+ const singleProjectPath = singleProject && projectPathArg ? import_node_path51.default.resolve(projectPathArg) : null;
11550
12119
  if (singleProject && !singleProjectPath) {
11551
12120
  throw new Error(
11552
12121
  `neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
@@ -11561,7 +12130,7 @@ async function startDaemon(opts = {}) {
11561
12130
  );
11562
12131
  }
11563
12132
  }
11564
- const pidPath = import_node_path50.default.join(home, "neatd.pid");
12133
+ const pidPath = import_node_path51.default.join(home, "neatd.pid");
11565
12134
  await writeAtomically(pidPath, `${process.pid}
11566
12135
  `);
11567
12136
  const slots = /* @__PURE__ */ new Map();
@@ -11968,8 +12537,8 @@ async function startDaemon(opts = {}) {
11968
12537
  let registryWatcher = null;
11969
12538
  let reloadTimer = null;
11970
12539
  if (!singleProject) try {
11971
- const regDir = import_node_path50.default.dirname(regPath);
11972
- const regBase = import_node_path50.default.basename(regPath);
12540
+ const regDir = import_node_path51.default.dirname(regPath);
12541
+ const regBase = import_node_path51.default.basename(regPath);
11973
12542
  registryWatcher = (0, import_node_fs31.watch)(regDir, (_eventType, filename) => {
11974
12543
  if (filename !== null && filename !== regBase) return;
11975
12544
  if (reloadTimer) clearTimeout(reloadTimer);
@@ -12044,7 +12613,7 @@ init_cjs_shims();
12044
12613
  var import_node_child_process2 = require("child_process");
12045
12614
  var import_node_fs32 = require("fs");
12046
12615
  var import_node_net = __toESM(require("net"), 1);
12047
- var import_node_path51 = __toESM(require("path"), 1);
12616
+ var import_node_path52 = __toESM(require("path"), 1);
12048
12617
  var DEFAULT_WEB_PORT = 6328;
12049
12618
  var DEFAULT_REST_PORT = 8080;
12050
12619
  function asValidPort(value) {
@@ -12053,11 +12622,11 @@ function asValidPort(value) {
12053
12622
  }
12054
12623
  function projectRoot() {
12055
12624
  const fromEnv = process.env.NEAT_SCAN_PATH;
12056
- return import_node_path51.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
12625
+ return import_node_path52.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
12057
12626
  }
12058
12627
  async function readDaemonPorts(root) {
12059
12628
  try {
12060
- const raw = await import_node_fs32.promises.readFile(import_node_path51.default.join(root, "neat-out", "daemon.json"), "utf8");
12629
+ const raw = await import_node_fs32.promises.readFile(import_node_path52.default.join(root, "neat-out", "daemon.json"), "utf8");
12061
12630
  const parsed = JSON.parse(raw);
12062
12631
  const ports = parsed?.ports ?? {};
12063
12632
  return { web: asValidPort(ports.web), rest: asValidPort(ports.rest) };
@@ -12102,10 +12671,10 @@ function resolveWebPackageDir() {
12102
12671
  eval("require")
12103
12672
  );
12104
12673
  const pkgJsonPath = req.resolve("@neat.is/web/package.json");
12105
- return import_node_path51.default.dirname(pkgJsonPath);
12674
+ return import_node_path52.default.dirname(pkgJsonPath);
12106
12675
  }
12107
12676
  function resolveStandaloneServerEntry(webDir) {
12108
- return import_node_path51.default.join(webDir, ".next/standalone/packages/web/server.js");
12677
+ return import_node_path52.default.join(webDir, ".next/standalone/packages/web/server.js");
12109
12678
  }
12110
12679
  async function pickInternalPort() {
12111
12680
  return new Promise((resolve, reject) => {
@@ -12158,7 +12727,7 @@ async function spawnWebUI(restPort, opts = {}) {
12158
12727
  NEAT_API_URL: apiUrl
12159
12728
  };
12160
12729
  child = (0, import_node_child_process2.spawn)(process.execPath, [serverEntry], {
12161
- cwd: import_node_path51.default.dirname(serverEntry),
12730
+ cwd: import_node_path52.default.dirname(serverEntry),
12162
12731
  env,
12163
12732
  stdio: ["ignore", "inherit", "inherit"],
12164
12733
  detached: false
@@ -12334,14 +12903,14 @@ function localVersion() {
12334
12903
  }
12335
12904
  function neatHome3() {
12336
12905
  if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
12337
- return import_node_path52.default.resolve(process.env.NEAT_HOME);
12906
+ return import_node_path53.default.resolve(process.env.NEAT_HOME);
12338
12907
  }
12339
12908
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
12340
- return import_node_path52.default.join(home, ".neat");
12909
+ return import_node_path53.default.join(home, ".neat");
12341
12910
  }
12342
12911
  async function readPid() {
12343
12912
  try {
12344
- const raw = await import_node_fs33.promises.readFile(import_node_path52.default.join(neatHome3(), "neatd.pid"), "utf8");
12913
+ const raw = await import_node_fs33.promises.readFile(import_node_path53.default.join(neatHome3(), "neatd.pid"), "utf8");
12345
12914
  const n = Number.parseInt(raw.trim(), 10);
12346
12915
  return Number.isFinite(n) ? n : null;
12347
12916
  } catch {