@neat.is/core 0.5.3 → 0.6.0

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);
@@ -342,6 +342,11 @@ function pickEnv(spanAttrs, resourceAttrs) {
342
342
  }
343
343
  return ENV_FALLBACK;
344
344
  }
345
+ function normalizeDbSystem(attrs) {
346
+ const raw = attrs["db.system"];
347
+ if (typeof raw !== "string") return void 0;
348
+ return raw === "mongoose" ? "mongodb" : raw;
349
+ }
345
350
  function messagingDestinationOf(attrs) {
346
351
  for (const key of ["messaging.destination.name", "messaging.destination"]) {
347
352
  const v = attrs[key];
@@ -362,8 +367,8 @@ function websocketChannelPathOf(attrs) {
362
367
  const v = attrs[key];
363
368
  if (typeof v === "string" && v.length > 0) {
364
369
  const q = v.indexOf("?");
365
- const path53 = q === -1 ? v : v.slice(0, q);
366
- if (path53.length > 0) return path53;
370
+ const path54 = q === -1 ? v : v.slice(0, q);
371
+ if (path54.length > 0) return path54;
367
372
  }
368
373
  }
369
374
  return void 0;
@@ -396,8 +401,9 @@ function parseOtlpRequest(body) {
396
401
  durationNanos: durationNanos(span.startTimeUnixNano, span.endTimeUnixNano),
397
402
  env: pickEnv(attrs, resourceAttrs),
398
403
  attributes: attrs,
399
- dbSystem: typeof attrs["db.system"] === "string" ? attrs["db.system"] : void 0,
404
+ dbSystem: normalizeDbSystem(attrs),
400
405
  dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
406
+ dbCollection: typeof attrs["db.collection.name"] === "string" ? attrs["db.collection.name"] : typeof attrs["db.mongodb.collection"] === "string" ? attrs["db.mongodb.collection"] : void 0,
401
407
  messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
402
408
  messagingDestination: messagingDestinationOf(attrs),
403
409
  graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
@@ -417,10 +423,10 @@ function parseOtlpRequest(body) {
417
423
  return out;
418
424
  }
419
425
  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");
426
+ const here = import_node_path49.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
427
+ const protoRoot = import_node_path49.default.resolve(here, "..", "proto");
422
428
  const root = new import_protobufjs.default.Root();
423
- root.resolvePath = (_origin, target) => import_node_path48.default.resolve(protoRoot, target);
429
+ root.resolvePath = (_origin, target) => import_node_path49.default.resolve(protoRoot, target);
424
430
  root.loadSync(
425
431
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
426
432
  { keepCase: true }
@@ -648,12 +654,12 @@ async function listenSteppingOtlp(app, requestedPort, host) {
648
654
  }
649
655
  }
650
656
  }
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;
657
+ var import_node_path49, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
652
658
  var init_otel = __esm({
653
659
  "src/otel.ts"() {
654
660
  "use strict";
655
661
  init_cjs_shims();
656
- import_node_path48 = __toESM(require("path"), 1);
662
+ import_node_path49 = __toESM(require("path"), 1);
657
663
  import_node_url2 = require("url");
658
664
  import_fastify2 = __toESM(require("fastify"), 1);
659
665
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -677,13 +683,13 @@ __export(neatd_exports, {
677
683
  module.exports = __toCommonJS(neatd_exports);
678
684
  init_cjs_shims();
679
685
  var import_node_fs33 = require("fs");
680
- var import_node_path52 = __toESM(require("path"), 1);
686
+ var import_node_path53 = __toESM(require("path"), 1);
681
687
  var import_node_module2 = require("module");
682
688
 
683
689
  // src/daemon.ts
684
690
  init_cjs_shims();
685
691
  var import_node_fs31 = require("fs");
686
- var import_node_path50 = __toESM(require("path"), 1);
692
+ var import_node_path51 = __toESM(require("path"), 1);
687
693
  var import_node_module = require("module");
688
694
 
689
695
  // src/graph.ts
@@ -1213,19 +1219,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1213
1219
  function longestIncomingWalk(graph, start, maxDepth) {
1214
1220
  let best = { path: [start], edges: [] };
1215
1221
  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] };
1222
+ function step(node, path54, edges) {
1223
+ if (path54.length > best.path.length) {
1224
+ best = { path: [...path54], edges: [...edges] };
1219
1225
  }
1220
- if (path53.length - 1 >= maxDepth) return;
1226
+ if (path54.length - 1 >= maxDepth) return;
1221
1227
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1222
1228
  for (const [srcId, edge] of incoming) {
1223
1229
  if (visited.has(srcId)) continue;
1224
1230
  visited.add(srcId);
1225
- path53.push(srcId);
1231
+ path54.push(srcId);
1226
1232
  edges.push(edge);
1227
- step(srcId, path53, edges);
1228
- path53.pop();
1233
+ step(srcId, path54, edges);
1234
+ path54.pop();
1229
1235
  edges.pop();
1230
1236
  visited.delete(srcId);
1231
1237
  }
@@ -1419,26 +1425,26 @@ function dominantFailingCall(graph, serviceId6, visited) {
1419
1425
  return best;
1420
1426
  }
1421
1427
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1422
- const path53 = [originServiceId];
1428
+ const path54 = [originServiceId];
1423
1429
  const edges = [];
1424
1430
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1425
1431
  let current = originServiceId;
1426
1432
  for (let depth = 0; depth < maxDepth; depth++) {
1427
1433
  const hop = dominantFailingCall(graph, current, visited);
1428
1434
  if (!hop) break;
1429
- path53.push(hop.nextService);
1435
+ path54.push(hop.nextService);
1430
1436
  edges.push(hop.edge);
1431
1437
  visited.add(hop.nextService);
1432
1438
  current = hop.nextService;
1433
1439
  }
1434
1440
  if (edges.length === 0) return null;
1435
- return { path: path53, edges, culprit: current };
1441
+ return { path: path54, edges, culprit: current };
1436
1442
  }
1437
1443
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1438
1444
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1439
1445
  if (!chain) return null;
1440
1446
  const culprit = chain.culprit;
1441
- const path53 = [...chain.path];
1447
+ const path54 = [...chain.path];
1442
1448
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1443
1449
  const baseConfidence = confidenceFromMix(chain.edges);
1444
1450
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1446,14 +1452,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1446
1452
  if (loc) {
1447
1453
  let rootCauseNode = culprit;
1448
1454
  if (loc.fileNode) {
1449
- path53.push(loc.fileNode);
1455
+ path54.push(loc.fileNode);
1450
1456
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1451
1457
  rootCauseNode = loc.fileNode;
1452
1458
  }
1453
1459
  return import_types.RootCauseResultSchema.parse({
1454
1460
  rootCauseNode,
1455
1461
  rootCauseReason: loc.rootCauseReason,
1456
- traversalPath: path53,
1462
+ traversalPath: path54,
1457
1463
  edgeProvenances,
1458
1464
  confidence,
1459
1465
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1465,7 +1471,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1465
1471
  return import_types.RootCauseResultSchema.parse({
1466
1472
  rootCauseNode: culprit,
1467
1473
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1468
- traversalPath: path53,
1474
+ traversalPath: path54,
1469
1475
  edgeProvenances,
1470
1476
  confidence,
1471
1477
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2892,6 +2898,18 @@ async function handleSpan(ctx, span) {
2892
2898
  callSiteEvidence
2893
2899
  );
2894
2900
  if (result) affectedNode = targetId;
2901
+ if (span.dbSystem === "mongodb" && span.dbCollection) {
2902
+ const collectionId = ensureInfraNode(ctx.graph, "mongodb-collection", span.dbCollection, "self");
2903
+ upsertObservedEdge(
2904
+ ctx.graph,
2905
+ import_types3.EdgeType.CALLS,
2906
+ observedSource(),
2907
+ collectionId,
2908
+ ts,
2909
+ isError,
2910
+ callSiteEvidence
2911
+ );
2912
+ }
2895
2913
  }
2896
2914
  } else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
2897
2915
  const targetId = ensureMessagingDestinationNode(
@@ -5736,7 +5754,7 @@ async function addGrpcMethods(graph, services) {
5736
5754
 
5737
5755
  // src/extract/calls/index.ts
5738
5756
  init_cjs_shims();
5739
- var import_types20 = require("@neat.is/types");
5757
+ var import_types21 = require("@neat.is/types");
5740
5758
 
5741
5759
  // src/extract/calls/http.ts
5742
5760
  init_cjs_shims();
@@ -6449,15 +6467,311 @@ function supabaseEndpointsFromFile(file, serviceDir) {
6449
6467
  return out;
6450
6468
  }
6451
6469
 
6470
+ // src/extract/calls/mongoose.ts
6471
+ init_cjs_shims();
6472
+ var import_node_path31 = __toESM(require("path"), 1);
6473
+ var import_types20 = require("@neat.is/types");
6474
+ var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
6475
+ var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
6476
+ var PLURALIZE_DISABLED_RE = /\bpluralize\s*\(\s*(?:null|false)\s*\)/;
6477
+ var UNCOUNTABLES = /* @__PURE__ */ new Set([
6478
+ "advice",
6479
+ "energy",
6480
+ "excretion",
6481
+ "digestion",
6482
+ "cooperation",
6483
+ "health",
6484
+ "justice",
6485
+ "labour",
6486
+ "machinery",
6487
+ "equipment",
6488
+ "information",
6489
+ "pollution",
6490
+ "sewage",
6491
+ "paper",
6492
+ "money",
6493
+ "species",
6494
+ "series",
6495
+ "rain",
6496
+ "rice",
6497
+ "fish",
6498
+ "sheep",
6499
+ "moose",
6500
+ "deer",
6501
+ "news",
6502
+ "expertise",
6503
+ "status",
6504
+ "media"
6505
+ ]);
6506
+ var PLURALIZE_RULES = [
6507
+ [/human$/gi, "humans"],
6508
+ [/(m)an$/gi, "$1en"],
6509
+ [/(pe)rson$/gi, "$1ople"],
6510
+ [/(child)$/gi, "$1ren"],
6511
+ [/^(ox)$/gi, "$1en"],
6512
+ [/(ax|test)is$/gi, "$1es"],
6513
+ [/(octop|vir)us$/gi, "$1i"],
6514
+ [/(alias|status)$/gi, "$1es"],
6515
+ [/(bu)s$/gi, "$1ses"],
6516
+ [/(buffal|tomat|potat)o$/gi, "$1oes"],
6517
+ [/([ti])um$/gi, "$1a"],
6518
+ [/sis$/gi, "ses"],
6519
+ [/(?:([^f])fe|([lr])f)$/gi, "$1$2ves"],
6520
+ [/(hive)$/gi, "$1s"],
6521
+ [/([^aeiouy]|qu)y$/gi, "$1ies"],
6522
+ [/(x|ch|ss|sh)$/gi, "$1es"],
6523
+ [/(matr|vert|ind)ix|ex$/gi, "$1ices"],
6524
+ [/([m|l])ouse$/gi, "$1ice"],
6525
+ [/(kn|w|l)ife$/gi, "$1ives"],
6526
+ [/(quiz)$/gi, "$1zes"],
6527
+ [/s$/gi, "s"],
6528
+ [/([^a-z])$/, "$1"],
6529
+ [/$/gi, "s"]
6530
+ ];
6531
+ function pluralizeCollection(name) {
6532
+ const str = name.toLowerCase();
6533
+ if (UNCOUNTABLES.has(str)) return str;
6534
+ for (const [re, repl] of PLURALIZE_RULES) {
6535
+ if (str.match(re)) return str.replace(re, repl);
6536
+ }
6537
+ return str;
6538
+ }
6539
+ var MODEL_METHODS = [
6540
+ "find",
6541
+ "findOne",
6542
+ "findById",
6543
+ "findByIdAndUpdate",
6544
+ "findByIdAndDelete",
6545
+ "findOneAndUpdate",
6546
+ "findOneAndDelete",
6547
+ "findOneAndReplace",
6548
+ "countDocuments",
6549
+ "estimatedDocumentCount",
6550
+ "distinct",
6551
+ "aggregate",
6552
+ "exists",
6553
+ "where",
6554
+ "populate",
6555
+ "watch",
6556
+ "hydrate",
6557
+ "create",
6558
+ "insertMany",
6559
+ "insertOne",
6560
+ "updateOne",
6561
+ "updateMany",
6562
+ "replaceOne",
6563
+ "deleteOne",
6564
+ "deleteMany",
6565
+ "bulkWrite",
6566
+ "bulkSave",
6567
+ "save",
6568
+ // pre-v7, removed in current mongoose but present in older codebases:
6569
+ "count",
6570
+ "update",
6571
+ "remove",
6572
+ "findOneAndRemove",
6573
+ "findByIdAndRemove"
6574
+ ];
6575
+ var MODEL_METHODS_ALT = MODEL_METHODS.join("|");
6576
+ function schemaCollectionVars(content) {
6577
+ const out = /* @__PURE__ */ new Map();
6578
+ const re = /(?:const|let|var)\s+(\w+)\s*=\s*new\s+(?:mongoose\s*\.\s*)?Schema\s*\(/g;
6579
+ let m;
6580
+ while ((m = re.exec(content)) !== null) {
6581
+ const windowText = content.slice(m.index, m.index + 2e3);
6582
+ const opt = /\bcollection\s*:\s*['"`]([\w.-]+)['"`]/.exec(windowText);
6583
+ if (opt) out.set(m[1], opt[1]);
6584
+ }
6585
+ return out;
6586
+ }
6587
+ function resolveModel(modelName, rest, schemaColl, pluralizeOn) {
6588
+ const trimmed = rest.trim();
6589
+ const literalThird = /,\s*['"`]([\w.-]+)['"`]\s*$/.exec(trimmed);
6590
+ if (literalThird) return { name: literalThird[1], kind: "mongodb-collection" };
6591
+ const firstIdent = /^([A-Za-z_$][\w$]*)/.exec(trimmed)?.[1];
6592
+ if (firstIdent && schemaColl.has(firstIdent)) {
6593
+ return { name: schemaColl.get(firstIdent), kind: "mongodb-collection" };
6594
+ }
6595
+ if (/,\s*[A-Za-z_$][\w$]*\s*$/.test(trimmed)) {
6596
+ return { name: modelName, kind: "mongodb-model" };
6597
+ }
6598
+ return { name: pluralizeOn ? pluralizeCollection(modelName) : modelName, kind: "mongodb-collection" };
6599
+ }
6600
+ function collectModelDefs(content, pluralizeOn) {
6601
+ const schemaColl = schemaCollectionVars(content);
6602
+ const out = [];
6603
+ 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;
6604
+ let m;
6605
+ while ((m = re.exec(content)) !== null) {
6606
+ out.push({
6607
+ modelName: m[2],
6608
+ varName: m[1] ?? null,
6609
+ resolved: resolveModel(m[2], m[3] ?? "", schemaColl, pluralizeOn),
6610
+ matchText: m[0]
6611
+ });
6612
+ }
6613
+ return out;
6614
+ }
6615
+ function endpoint(r, file, serviceDir, matchText) {
6616
+ const line = lineOf(file.content, matchText);
6617
+ return {
6618
+ infraId: (0, import_types20.infraId)(r.kind, r.name),
6619
+ name: r.name,
6620
+ kind: r.kind,
6621
+ edgeType: "CALLS",
6622
+ confidenceKind: "verified-call-site",
6623
+ evidence: { file: import_node_path31.default.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
6624
+ };
6625
+ }
6626
+ function mongooseEndpointsFromFile(file, serviceDir) {
6627
+ const hasMongoose = MONGOOSE_IMPORT_RE.test(file.content);
6628
+ const hasMongodb = MONGODB_IMPORT_RE.test(file.content);
6629
+ if (!hasMongoose && !hasMongodb) return [];
6630
+ const content = file.content;
6631
+ const out = [];
6632
+ const seen = /* @__PURE__ */ new Set();
6633
+ const push = (r, matchText) => {
6634
+ const key = `${r.kind}/${r.name}`;
6635
+ if (seen.has(key)) return;
6636
+ seen.add(key);
6637
+ out.push(endpoint(r, file, serviceDir, matchText));
6638
+ };
6639
+ if (hasMongoose) {
6640
+ const pluralizeOn = !PLURALIZE_DISABLED_RE.test(content);
6641
+ for (const def of collectModelDefs(content, pluralizeOn)) push(def.resolved, def.matchText);
6642
+ }
6643
+ const collRe = /\.\s*collection\s*\(\s*['"`]([\w.$-]+)['"`]\s*\)/g;
6644
+ let c;
6645
+ while ((c = collRe.exec(content)) !== null) {
6646
+ push({ name: c[1], kind: "mongodb-collection" }, c[0]);
6647
+ }
6648
+ return out;
6649
+ }
6650
+ function parseDestructure(inner, specifier, out) {
6651
+ for (const raw of inner.split(",")) {
6652
+ const part = raw.trim();
6653
+ if (!part) continue;
6654
+ const asM = /^(\w+)\s+as\s+(\w+)$/.exec(part);
6655
+ const colonM = /^(\w+)\s*:\s*(\w+)$/.exec(part);
6656
+ if (asM) out.push({ local: asM[2], kind: "named", exportName: asM[1], specifier });
6657
+ else if (colonM) out.push({ local: colonM[2], kind: "named", exportName: colonM[1], specifier });
6658
+ else {
6659
+ const id = /^(\w+)$/.exec(part);
6660
+ if (id) out.push({ local: id[1], kind: "named", exportName: id[1], specifier });
6661
+ }
6662
+ }
6663
+ }
6664
+ function parseImportBindings(content) {
6665
+ const out = [];
6666
+ const esm = /import\s+([^;'"`\n]+?)\s+from\s*['"`]([^'"`]+)['"`]/g;
6667
+ let m;
6668
+ while ((m = esm.exec(content)) !== null) {
6669
+ const clause = m[1].trim();
6670
+ const spec = m[2];
6671
+ const ns = /^\*\s+as\s+(\w+)$/.exec(clause);
6672
+ if (ns) {
6673
+ out.push({ local: ns[1], kind: "namespace", specifier: spec });
6674
+ continue;
6675
+ }
6676
+ const named = /\{([^}]*)\}/.exec(clause);
6677
+ if (named) parseDestructure(named[1], spec, out);
6678
+ const def = /^(\w+)\s*(?:,|$)/.exec(clause);
6679
+ if (def && !clause.startsWith("{")) out.push({ local: def[1], kind: "default", specifier: spec });
6680
+ }
6681
+ const cjs = /(?:const|let|var)\s+(\{[^}]*\}|\w+)\s*=\s*require\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
6682
+ while ((m = cjs.exec(content)) !== null) {
6683
+ const lhs = m[1];
6684
+ const spec = m[2];
6685
+ if (lhs.startsWith("{")) parseDestructure(lhs.slice(1, -1), spec, out);
6686
+ else out.push({ local: lhs, kind: "whole", specifier: spec });
6687
+ }
6688
+ return out;
6689
+ }
6690
+ function fileExportsOf(content, pluralizeOn) {
6691
+ const defs = collectModelDefs(content, pluralizeOn).filter((d) => d.resolved.kind === "mongodb-collection");
6692
+ if (defs.length === 0) return null;
6693
+ const byVar = /* @__PURE__ */ new Map();
6694
+ for (const d of defs) if (d.varName) byVar.set(d.varName, d.resolved.name);
6695
+ const byName = new Map(byVar);
6696
+ let def;
6697
+ const named = /(?:module\.exports|export\s+default)\s*=\s*(\w+)\b/.exec(content);
6698
+ if (named && byVar.has(named[1])) def = byVar.get(named[1]);
6699
+ if (!def) {
6700
+ const inline = /(?:module\.exports|export\s+default)\s*=\s*(?:await\s+)?(?:\w+\s*\.\s*)?model\s*\(\s*['"`]([\w$]+)['"`]/.exec(content);
6701
+ if (inline) def = pluralizeOn ? pluralizeCollection(inline[1]) : inline[1];
6702
+ }
6703
+ if (!def && byVar.size === 1) def = [...byVar.values()][0];
6704
+ return { byName, ...def ? { default: def } : {} };
6705
+ }
6706
+ async function mongooseCrossFileEndpoints(files, serviceDir) {
6707
+ const mongooseFiles = files.filter((f) => MONGOOSE_IMPORT_RE.test(f.content));
6708
+ if (mongooseFiles.length === 0) return [];
6709
+ const pluralizeOn = !files.some((f) => PLURALIZE_DISABLED_RE.test(f.content));
6710
+ const registry = /* @__PURE__ */ new Map();
6711
+ for (const f of mongooseFiles) {
6712
+ const fx = fileExportsOf(f.content, pluralizeOn);
6713
+ if (fx) registry.set(toPosix2(import_node_path31.default.relative(serviceDir, f.path)), fx);
6714
+ }
6715
+ if (registry.size === 0) return [];
6716
+ const out = [];
6717
+ const seen = /* @__PURE__ */ new Set();
6718
+ for (const f of files) {
6719
+ const bindings = parseImportBindings(f.content);
6720
+ if (bindings.length === 0) continue;
6721
+ const directColl = /* @__PURE__ */ new Map();
6722
+ const nsExports = /* @__PURE__ */ new Map();
6723
+ for (const b of bindings) {
6724
+ const resolvedRel = await resolveJsImport(b.specifier, import_node_path31.default.dirname(f.path), serviceDir, null);
6725
+ if (!resolvedRel) continue;
6726
+ const fx = registry.get(resolvedRel);
6727
+ if (!fx) continue;
6728
+ if (b.kind === "named" && b.exportName) {
6729
+ const coll = fx.byName.get(b.exportName);
6730
+ if (coll) directColl.set(b.local, coll);
6731
+ } else if (b.kind === "default") {
6732
+ if (fx.default) directColl.set(b.local, fx.default);
6733
+ } else {
6734
+ if (fx.default) directColl.set(b.local, fx.default);
6735
+ nsExports.set(b.local, fx);
6736
+ }
6737
+ }
6738
+ if (directColl.size === 0 && nsExports.size === 0) continue;
6739
+ const localDefs = new Set(
6740
+ collectModelDefs(f.content, pluralizeOn).map((d) => d.varName).filter((v) => v !== null)
6741
+ );
6742
+ const emit = (collection, matchText) => {
6743
+ const key = `${f.path}::${collection}`;
6744
+ if (seen.has(key)) return;
6745
+ seen.add(key);
6746
+ out.push(endpoint({ name: collection, kind: "mongodb-collection" }, f, serviceDir, matchText));
6747
+ };
6748
+ for (const [local, collection] of directColl) {
6749
+ if (localDefs.has(local)) continue;
6750
+ const qre = new RegExp(`\\b${local}\\s*\\.\\s*(?:${MODEL_METHODS_ALT})\\s*\\(`, "g");
6751
+ let qm;
6752
+ while ((qm = qre.exec(f.content)) !== null) emit(collection, qm[0]);
6753
+ }
6754
+ for (const [local, fx] of nsExports) {
6755
+ const qre = new RegExp(`\\b${local}\\s*\\.\\s*(\\w+)\\s*\\.\\s*(?:${MODEL_METHODS_ALT})\\s*\\(`, "g");
6756
+ let qm;
6757
+ while ((qm = qre.exec(f.content)) !== null) {
6758
+ const coll = fx.byName.get(qm[1]);
6759
+ if (coll) emit(coll, qm[0]);
6760
+ }
6761
+ }
6762
+ }
6763
+ return out;
6764
+ }
6765
+
6452
6766
  // src/extract/calls/index.ts
6453
6767
  function edgeTypeFromEndpoint(ep) {
6454
6768
  switch (ep.edgeType) {
6455
6769
  case "PUBLISHES_TO":
6456
- return import_types20.EdgeType.PUBLISHES_TO;
6770
+ return import_types21.EdgeType.PUBLISHES_TO;
6457
6771
  case "CONSUMES_FROM":
6458
- return import_types20.EdgeType.CONSUMES_FROM;
6772
+ return import_types21.EdgeType.CONSUMES_FROM;
6459
6773
  default:
6460
- return import_types20.EdgeType.CALLS;
6774
+ return import_types21.EdgeType.CALLS;
6461
6775
  }
6462
6776
  }
6463
6777
  function isAwsKind(kind) {
@@ -6469,23 +6783,27 @@ async function addExternalEndpointEdges(graph, services) {
6469
6783
  for (const service of services) {
6470
6784
  const files = await loadSourceFiles(service.dir);
6471
6785
  const endpoints = [];
6786
+ const maskedFiles = [];
6472
6787
  for (const file of files) {
6473
6788
  if (isTestPath(file.path)) continue;
6474
6789
  const masked = maskCommentsInSource(file.content);
6475
6790
  const maskedFile = { path: file.path, content: masked };
6791
+ maskedFiles.push(maskedFile);
6476
6792
  endpoints.push(...kafkaEndpointsFromFile(maskedFile, service.dir));
6477
6793
  endpoints.push(...redisEndpointsFromFile(maskedFile, service.dir));
6478
6794
  endpoints.push(...awsEndpointsFromFile(maskedFile, service.dir));
6479
6795
  endpoints.push(...grpcEndpointsFromFile(maskedFile, service.dir));
6480
6796
  endpoints.push(...supabaseEndpointsFromFile(maskedFile, service.dir));
6797
+ endpoints.push(...mongooseEndpointsFromFile(maskedFile, service.dir));
6481
6798
  }
6799
+ endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
6482
6800
  if (endpoints.length === 0) continue;
6483
6801
  const seenEdges = /* @__PURE__ */ new Set();
6484
6802
  for (const ep of endpoints) {
6485
6803
  if (!graph.hasNode(ep.infraId)) {
6486
6804
  const node = {
6487
6805
  id: ep.infraId,
6488
- type: import_types20.NodeType.InfraNode,
6806
+ type: import_types21.NodeType.InfraNode,
6489
6807
  name: ep.name,
6490
6808
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
6491
6809
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -6497,7 +6815,7 @@ async function addExternalEndpointEdges(graph, services) {
6497
6815
  nodesAdded++;
6498
6816
  }
6499
6817
  const edgeType = edgeTypeFromEndpoint(ep);
6500
- const confidence = (0, import_types20.confidenceForExtracted)(ep.confidenceKind);
6818
+ const confidence = (0, import_types21.confidenceForExtracted)(ep.confidenceKind);
6501
6819
  const relFile = toPosix2(ep.evidence.file);
6502
6820
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
6503
6821
  graph,
@@ -6507,7 +6825,7 @@ async function addExternalEndpointEdges(graph, services) {
6507
6825
  );
6508
6826
  nodesAdded += n;
6509
6827
  edgesAdded += e;
6510
- if (!(0, import_types20.passesExtractedFloor)(confidence)) {
6828
+ if (!(0, import_types21.passesExtractedFloor)(confidence)) {
6511
6829
  noteExtractedDropped({
6512
6830
  source: fileNodeId,
6513
6831
  target: ep.infraId,
@@ -6527,7 +6845,7 @@ async function addExternalEndpointEdges(graph, services) {
6527
6845
  source: fileNodeId,
6528
6846
  target: ep.infraId,
6529
6847
  type: edgeType,
6530
- provenance: import_types20.Provenance.EXTRACTED,
6848
+ provenance: import_types21.Provenance.EXTRACTED,
6531
6849
  confidence,
6532
6850
  evidence: ep.evidence
6533
6851
  };
@@ -6553,16 +6871,16 @@ init_cjs_shims();
6553
6871
 
6554
6872
  // src/extract/infra/docker-compose.ts
6555
6873
  init_cjs_shims();
6556
- var import_node_path31 = __toESM(require("path"), 1);
6557
- var import_types22 = require("@neat.is/types");
6874
+ var import_node_path32 = __toESM(require("path"), 1);
6875
+ var import_types23 = require("@neat.is/types");
6558
6876
 
6559
6877
  // src/extract/infra/shared.ts
6560
6878
  init_cjs_shims();
6561
- var import_types21 = require("@neat.is/types");
6879
+ var import_types22 = require("@neat.is/types");
6562
6880
  function makeInfraNode(kind, name, provider = "self", extras) {
6563
6881
  return {
6564
- id: (0, import_types21.infraId)(kind, name),
6565
- type: import_types21.NodeType.InfraNode,
6882
+ id: (0, import_types22.infraId)(kind, name),
6883
+ type: import_types22.NodeType.InfraNode,
6566
6884
  name,
6567
6885
  provider,
6568
6886
  kind,
@@ -6606,8 +6924,8 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
6606
6924
  source: anchorId,
6607
6925
  target: node.id,
6608
6926
  type: edgeType,
6609
- provenance: import_types21.Provenance.EXTRACTED,
6610
- confidence: (0, import_types21.confidenceForExtracted)("structural"),
6927
+ provenance: import_types22.Provenance.EXTRACTED,
6928
+ confidence: (0, import_types22.confidenceForExtracted)("structural"),
6611
6929
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
6612
6930
  };
6613
6931
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6624,7 +6942,7 @@ function dependsOnList(value) {
6624
6942
  }
6625
6943
  function serviceNameToServiceNode(name, services) {
6626
6944
  for (const s of services) {
6627
- if (s.node.name === name || import_node_path31.default.basename(s.dir) === name) return s.node.id;
6945
+ if (s.node.name === name || import_node_path32.default.basename(s.dir) === name) return s.node.id;
6628
6946
  }
6629
6947
  return null;
6630
6948
  }
@@ -6633,7 +6951,7 @@ async function addComposeInfra(graph, scanPath, services) {
6633
6951
  let edgesAdded = 0;
6634
6952
  let composePath = null;
6635
6953
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
6636
- const abs = import_node_path31.default.join(scanPath, name);
6954
+ const abs = import_node_path32.default.join(scanPath, name);
6637
6955
  if (await exists(abs)) {
6638
6956
  composePath = abs;
6639
6957
  break;
@@ -6646,13 +6964,13 @@ async function addComposeInfra(graph, scanPath, services) {
6646
6964
  } catch (err) {
6647
6965
  recordExtractionError(
6648
6966
  "infra docker-compose",
6649
- import_node_path31.default.relative(scanPath, composePath),
6967
+ import_node_path32.default.relative(scanPath, composePath),
6650
6968
  err
6651
6969
  );
6652
6970
  return { nodesAdded, edgesAdded };
6653
6971
  }
6654
6972
  if (!compose?.services) return { nodesAdded, edgesAdded };
6655
- const evidenceFile = import_node_path31.default.relative(scanPath, composePath).split(import_node_path31.default.sep).join("/");
6973
+ const evidenceFile = import_node_path32.default.relative(scanPath, composePath).split(import_node_path32.default.sep).join("/");
6656
6974
  const composeNameToNodeId = /* @__PURE__ */ new Map();
6657
6975
  for (const [composeName, svc] of Object.entries(compose.services)) {
6658
6976
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -6674,15 +6992,15 @@ async function addComposeInfra(graph, scanPath, services) {
6674
6992
  for (const dep of dependsOnList(svc.depends_on)) {
6675
6993
  const targetId = composeNameToNodeId.get(dep);
6676
6994
  if (!targetId) continue;
6677
- const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types22.EdgeType.DEPENDS_ON);
6995
+ const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types23.EdgeType.DEPENDS_ON);
6678
6996
  if (graph.hasEdge(edgeId)) continue;
6679
6997
  const edge = {
6680
6998
  id: edgeId,
6681
6999
  source: sourceId,
6682
7000
  target: targetId,
6683
- type: import_types22.EdgeType.DEPENDS_ON,
6684
- provenance: import_types22.Provenance.EXTRACTED,
6685
- confidence: (0, import_types22.confidenceForExtracted)("structural"),
7001
+ type: import_types23.EdgeType.DEPENDS_ON,
7002
+ provenance: import_types23.Provenance.EXTRACTED,
7003
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
6686
7004
  evidence: { file: evidenceFile }
6687
7005
  };
6688
7006
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6694,9 +7012,9 @@ async function addComposeInfra(graph, scanPath, services) {
6694
7012
 
6695
7013
  // src/extract/infra/dockerfile.ts
6696
7014
  init_cjs_shims();
6697
- var import_node_path32 = __toESM(require("path"), 1);
7015
+ var import_node_path33 = __toESM(require("path"), 1);
6698
7016
  var import_node_fs16 = require("fs");
6699
- var import_types23 = require("@neat.is/types");
7017
+ var import_types24 = require("@neat.is/types");
6700
7018
  function readDockerfile(content) {
6701
7019
  let image = null;
6702
7020
  const ports = [];
@@ -6725,7 +7043,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6725
7043
  let nodesAdded = 0;
6726
7044
  let edgesAdded = 0;
6727
7045
  for (const service of services) {
6728
- const dockerfilePath = import_node_path32.default.join(service.dir, "Dockerfile");
7046
+ const dockerfilePath = import_node_path33.default.join(service.dir, "Dockerfile");
6729
7047
  if (!await exists(dockerfilePath)) continue;
6730
7048
  let content;
6731
7049
  try {
@@ -6733,7 +7051,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6733
7051
  } catch (err) {
6734
7052
  recordExtractionError(
6735
7053
  "infra dockerfile",
6736
- import_node_path32.default.relative(scanPath, dockerfilePath),
7054
+ import_node_path33.default.relative(scanPath, dockerfilePath),
6737
7055
  err
6738
7056
  );
6739
7057
  continue;
@@ -6745,8 +7063,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6745
7063
  graph.addNode(node.id, node);
6746
7064
  nodesAdded++;
6747
7065
  }
6748
- const relDockerfile = toPosix2(import_node_path32.default.relative(service.dir, dockerfilePath));
6749
- const evidenceFile = toPosix2(import_node_path32.default.relative(scanPath, dockerfilePath));
7066
+ const relDockerfile = toPosix2(import_node_path33.default.relative(service.dir, dockerfilePath));
7067
+ const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, dockerfilePath));
6750
7068
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
6751
7069
  graph,
6752
7070
  service.pkg.name,
@@ -6755,15 +7073,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6755
7073
  );
6756
7074
  nodesAdded += fn;
6757
7075
  edgesAdded += fe;
6758
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types23.EdgeType.RUNS_ON);
7076
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types24.EdgeType.RUNS_ON);
6759
7077
  if (!graph.hasEdge(edgeId)) {
6760
7078
  const edge = {
6761
7079
  id: edgeId,
6762
7080
  source: fileNodeId,
6763
7081
  target: node.id,
6764
- type: import_types23.EdgeType.RUNS_ON,
6765
- provenance: import_types23.Provenance.EXTRACTED,
6766
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
7082
+ type: import_types24.EdgeType.RUNS_ON,
7083
+ provenance: import_types24.Provenance.EXTRACTED,
7084
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
6767
7085
  evidence: {
6768
7086
  file: evidenceFile,
6769
7087
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -6778,15 +7096,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6778
7096
  graph.addNode(portNode.id, portNode);
6779
7097
  nodesAdded++;
6780
7098
  }
6781
- const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types23.EdgeType.CONNECTS_TO);
7099
+ const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types24.EdgeType.CONNECTS_TO);
6782
7100
  if (graph.hasEdge(portEdgeId)) continue;
6783
7101
  const portEdge = {
6784
7102
  id: portEdgeId,
6785
7103
  source: fileNodeId,
6786
7104
  target: portNode.id,
6787
- type: import_types23.EdgeType.CONNECTS_TO,
6788
- provenance: import_types23.Provenance.EXTRACTED,
6789
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
7105
+ type: import_types24.EdgeType.CONNECTS_TO,
7106
+ provenance: import_types24.Provenance.EXTRACTED,
7107
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
6790
7108
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
6791
7109
  };
6792
7110
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -6799,8 +7117,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6799
7117
  // src/extract/infra/terraform.ts
6800
7118
  init_cjs_shims();
6801
7119
  var import_node_fs17 = require("fs");
6802
- var import_node_path33 = __toESM(require("path"), 1);
6803
- var import_types24 = require("@neat.is/types");
7120
+ var import_node_path34 = __toESM(require("path"), 1);
7121
+ var import_types25 = require("@neat.is/types");
6804
7122
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
6805
7123
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
6806
7124
  async function walkTfFiles(start, depth = 0, max = 5) {
@@ -6810,11 +7128,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
6810
7128
  for (const entry2 of entries) {
6811
7129
  if (entry2.isDirectory()) {
6812
7130
  if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
6813
- const child = import_node_path33.default.join(start, entry2.name);
7131
+ const child = import_node_path34.default.join(start, entry2.name);
6814
7132
  if (await isPythonVenvDir(child)) continue;
6815
7133
  out.push(...await walkTfFiles(child, depth + 1, max));
6816
7134
  } else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
6817
- out.push(import_node_path33.default.join(start, entry2.name));
7135
+ out.push(import_node_path34.default.join(start, entry2.name));
6818
7136
  }
6819
7137
  }
6820
7138
  return out;
@@ -6846,7 +7164,7 @@ async function addTerraformResources(graph, scanPath) {
6846
7164
  const files = await walkTfFiles(scanPath);
6847
7165
  for (const file of files) {
6848
7166
  const content = await import_node_fs17.promises.readFile(file, "utf8");
6849
- const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, file));
7167
+ const evidenceFile = toPosix2(import_node_path34.default.relative(scanPath, file));
6850
7168
  const resources = [];
6851
7169
  const byKey = /* @__PURE__ */ new Map();
6852
7170
  RESOURCE_RE.lastIndex = 0;
@@ -6881,16 +7199,16 @@ async function addTerraformResources(graph, scanPath) {
6881
7199
  if (!target) continue;
6882
7200
  if (seen.has(target.nodeId)) continue;
6883
7201
  seen.add(target.nodeId);
6884
- const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types24.EdgeType.DEPENDS_ON);
7202
+ const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types25.EdgeType.DEPENDS_ON);
6885
7203
  if (graph.hasEdge(edgeId)) continue;
6886
7204
  const line = lineAt2(content, resource.bodyOffset + ref.index);
6887
7205
  const edge = {
6888
7206
  id: edgeId,
6889
7207
  source: resource.nodeId,
6890
7208
  target: target.nodeId,
6891
- type: import_types24.EdgeType.DEPENDS_ON,
6892
- provenance: import_types24.Provenance.EXTRACTED,
6893
- confidence: (0, import_types24.confidenceForExtracted)("structural"),
7209
+ type: import_types25.EdgeType.DEPENDS_ON,
7210
+ provenance: import_types25.Provenance.EXTRACTED,
7211
+ confidence: (0, import_types25.confidenceForExtracted)("structural"),
6894
7212
  evidence: { file: evidenceFile, line, snippet: key }
6895
7213
  };
6896
7214
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6904,7 +7222,7 @@ async function addTerraformResources(graph, scanPath) {
6904
7222
  // src/extract/infra/k8s.ts
6905
7223
  init_cjs_shims();
6906
7224
  var import_node_fs18 = require("fs");
6907
- var import_node_path34 = __toESM(require("path"), 1);
7225
+ var import_node_path35 = __toESM(require("path"), 1);
6908
7226
  var import_yaml3 = require("yaml");
6909
7227
  var K8S_KIND_TO_INFRA_KIND = {
6910
7228
  Service: "k8s-service",
@@ -6922,11 +7240,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
6922
7240
  for (const entry2 of entries) {
6923
7241
  if (entry2.isDirectory()) {
6924
7242
  if (IGNORED_DIRS.has(entry2.name)) continue;
6925
- const child = import_node_path34.default.join(start, entry2.name);
7243
+ const child = import_node_path35.default.join(start, entry2.name);
6926
7244
  if (await isPythonVenvDir(child)) continue;
6927
7245
  out.push(...await walkYamlFiles2(child, depth + 1, max));
6928
- } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path34.default.extname(entry2.name))) {
6929
- out.push(import_node_path34.default.join(start, entry2.name));
7246
+ } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path35.default.extname(entry2.name))) {
7247
+ out.push(import_node_path35.default.join(start, entry2.name));
6930
7248
  }
6931
7249
  }
6932
7250
  return out;
@@ -6960,13 +7278,13 @@ async function addK8sResources(graph, scanPath) {
6960
7278
  // src/extract/infra/cloudflare.ts
6961
7279
  init_cjs_shims();
6962
7280
  var import_node_fs19 = require("fs");
6963
- var import_node_path35 = __toESM(require("path"), 1);
7281
+ var import_node_path36 = __toESM(require("path"), 1);
6964
7282
  var import_smol_toml2 = require("smol-toml");
6965
- var import_types25 = require("@neat.is/types");
7283
+ var import_types26 = require("@neat.is/types");
6966
7284
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
6967
7285
  async function readWranglerConfig(dir) {
6968
7286
  for (const filename of WRANGLER_FILENAMES) {
6969
- const abs = import_node_path35.default.join(dir, filename);
7287
+ const abs = import_node_path36.default.join(dir, filename);
6970
7288
  if (!await exists(abs)) continue;
6971
7289
  const raw = await import_node_fs19.promises.readFile(abs, "utf8");
6972
7290
  const config = filename === "wrangler.toml" ? (0, import_smol_toml2.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -7010,8 +7328,8 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
7010
7328
  source: anchorId,
7011
7329
  target: node.id,
7012
7330
  type: edgeType,
7013
- provenance: import_types25.Provenance.EXTRACTED,
7014
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7331
+ provenance: import_types26.Provenance.EXTRACTED,
7332
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7015
7333
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
7016
7334
  };
7017
7335
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7029,11 +7347,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7029
7347
  try {
7030
7348
  read = await readWranglerConfig(service.dir);
7031
7349
  } catch (err) {
7032
- recordExtractionError("infra cloudflare", import_node_path35.default.relative(scanPath, service.dir), err);
7350
+ recordExtractionError("infra cloudflare", import_node_path36.default.relative(scanPath, service.dir), err);
7033
7351
  continue;
7034
7352
  }
7035
7353
  if (!read || !read.config.name) continue;
7036
- const evidenceFile = toPosix2(import_node_path35.default.relative(scanPath, import_node_path35.default.join(service.dir, read.relFile)));
7354
+ const evidenceFile = toPosix2(import_node_path36.default.relative(scanPath, import_node_path36.default.join(service.dir, read.relFile)));
7037
7355
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
7038
7356
  }
7039
7357
  for (const worker of discovered) {
@@ -7045,7 +7363,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7045
7363
  }
7046
7364
  let anchorId = service.node.id;
7047
7365
  if (config.main) {
7048
- const entryRelPath = toPosix2(import_node_path35.default.normalize(config.main));
7366
+ const entryRelPath = toPosix2(import_node_path36.default.normalize(config.main));
7049
7367
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
7050
7368
  graph,
7051
7369
  service.pkg.name,
@@ -7072,15 +7390,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7072
7390
  nodesAdded++;
7073
7391
  }
7074
7392
  if (runtimeNode.id !== anchorId) {
7075
- const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id, import_types25.EdgeType.RUNS_ON);
7393
+ const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id, import_types26.EdgeType.RUNS_ON);
7076
7394
  if (!graph.hasEdge(runsOnId)) {
7077
7395
  const edge = {
7078
7396
  id: runsOnId,
7079
7397
  source: anchorId,
7080
7398
  target: runtimeNode.id,
7081
- type: import_types25.EdgeType.RUNS_ON,
7082
- provenance: import_types25.Provenance.EXTRACTED,
7083
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7399
+ type: import_types26.EdgeType.RUNS_ON,
7400
+ provenance: import_types26.Provenance.EXTRACTED,
7401
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7084
7402
  evidence: {
7085
7403
  file: evidenceFile,
7086
7404
  ...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
@@ -7094,7 +7412,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7094
7412
  const result = addResourceEdge(
7095
7413
  graph,
7096
7414
  anchorId,
7097
- import_types25.EdgeType.CONNECTS_TO,
7415
+ import_types26.EdgeType.CONNECTS_TO,
7098
7416
  "cloudflare-route",
7099
7417
  route,
7100
7418
  evidenceFile,
@@ -7118,7 +7436,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7118
7436
  const result = addResourceEdge(
7119
7437
  graph,
7120
7438
  anchorId,
7121
- import_types25.EdgeType.DEPENDS_ON,
7439
+ import_types26.EdgeType.DEPENDS_ON,
7122
7440
  group.kind,
7123
7441
  name,
7124
7442
  evidenceFile,
@@ -7132,7 +7450,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7132
7450
  const result = addResourceEdge(
7133
7451
  graph,
7134
7452
  anchorId,
7135
- import_types25.EdgeType.DEPENDS_ON,
7453
+ import_types26.EdgeType.DEPENDS_ON,
7136
7454
  "cloudflare-cron",
7137
7455
  cron,
7138
7456
  evidenceFile,
@@ -7145,7 +7463,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7145
7463
  const result = addResourceEdge(
7146
7464
  graph,
7147
7465
  anchorId,
7148
- import_types25.EdgeType.DEPENDS_ON,
7466
+ import_types26.EdgeType.DEPENDS_ON,
7149
7467
  "cloudflare-env-var",
7150
7468
  varName,
7151
7469
  evidenceFile,
@@ -7158,15 +7476,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7158
7476
  if (!svc.service) continue;
7159
7477
  const target = workerIndex.get(svc.service);
7160
7478
  if (target && target.anchorId !== anchorId) {
7161
- const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId, import_types25.EdgeType.CALLS);
7479
+ const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId, import_types26.EdgeType.CALLS);
7162
7480
  if (!graph.hasEdge(edgeId)) {
7163
7481
  const edge = {
7164
7482
  id: edgeId,
7165
7483
  source: anchorId,
7166
7484
  target: target.anchorId,
7167
- type: import_types25.EdgeType.CALLS,
7168
- provenance: import_types25.Provenance.EXTRACTED,
7169
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7485
+ type: import_types26.EdgeType.CALLS,
7486
+ provenance: import_types26.Provenance.EXTRACTED,
7487
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7170
7488
  evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
7171
7489
  };
7172
7490
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7177,7 +7495,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7177
7495
  const result = addResourceEdge(
7178
7496
  graph,
7179
7497
  anchorId,
7180
- import_types25.EdgeType.DEPENDS_ON,
7498
+ import_types26.EdgeType.DEPENDS_ON,
7181
7499
  "cloudflare-service-binding",
7182
7500
  svc.service,
7183
7501
  evidenceFile,
@@ -7193,12 +7511,12 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7193
7511
  // src/extract/infra/vercel.ts
7194
7512
  init_cjs_shims();
7195
7513
  var import_node_fs20 = require("fs");
7196
- var import_node_path36 = __toESM(require("path"), 1);
7197
- var import_types26 = require("@neat.is/types");
7514
+ var import_node_path37 = __toESM(require("path"), 1);
7515
+ var import_types27 = require("@neat.is/types");
7198
7516
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
7199
7517
  async function readVercelConfig(dir) {
7200
7518
  for (const filename of VERCEL_CONFIG_FILENAMES) {
7201
- const abs = import_node_path36.default.join(dir, filename);
7519
+ const abs = import_node_path37.default.join(dir, filename);
7202
7520
  if (!await exists(abs)) continue;
7203
7521
  const raw = await import_node_fs20.promises.readFile(abs, "utf8");
7204
7522
  const config = JSON.parse(maskCommentsInSource(raw));
@@ -7207,7 +7525,7 @@ async function readVercelConfig(dir) {
7207
7525
  return null;
7208
7526
  }
7209
7527
  async function readLinkedProjectName(dir) {
7210
- const abs = import_node_path36.default.join(dir, ".vercel", "project.json");
7528
+ const abs = import_node_path37.default.join(dir, ".vercel", "project.json");
7211
7529
  if (!await exists(abs)) return void 0;
7212
7530
  const parsed = JSON.parse(await import_node_fs20.promises.readFile(abs, "utf8"));
7213
7531
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
@@ -7225,7 +7543,7 @@ async function addVercelServices(graph, services, scanPath) {
7225
7543
  read = await readVercelConfig(service.dir);
7226
7544
  projectName = await readLinkedProjectName(service.dir);
7227
7545
  } catch (err) {
7228
- recordExtractionError("infra vercel", import_node_path36.default.relative(scanPath, service.dir), err);
7546
+ recordExtractionError("infra vercel", import_node_path37.default.relative(scanPath, service.dir), err);
7229
7547
  continue;
7230
7548
  }
7231
7549
  if (!read && !projectName) continue;
@@ -7241,7 +7559,7 @@ async function addVercelServices(graph, services, scanPath) {
7241
7559
  const anchorId = service.node.id;
7242
7560
  if (!read) continue;
7243
7561
  const { config, relFile, raw } = read;
7244
- const evidenceFile = toPosix2(import_node_path36.default.relative(scanPath, import_node_path36.default.join(service.dir, relFile)));
7562
+ const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, relFile)));
7245
7563
  const add = (edgeType, kind, name) => {
7246
7564
  if (!name) return;
7247
7565
  const result = emitPlatformResourceEdge(
@@ -7257,12 +7575,12 @@ async function addVercelServices(graph, services, scanPath) {
7257
7575
  nodesAdded += result.nodesAdded;
7258
7576
  edgesAdded += result.edgesAdded;
7259
7577
  };
7260
- add(import_types26.EdgeType.RUNS_ON, "vercel", "vercel");
7261
- for (const cron of config.crons ?? []) add(import_types26.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
7262
- for (const varName of Object.keys(config.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7263
- for (const varName of Object.keys(config.build?.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7578
+ add(import_types27.EdgeType.RUNS_ON, "vercel", "vercel");
7579
+ for (const cron of config.crons ?? []) add(import_types27.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
7580
+ for (const varName of Object.keys(config.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7581
+ for (const varName of Object.keys(config.build?.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7264
7582
  for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
7265
- add(import_types26.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7583
+ add(import_types27.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7266
7584
  }
7267
7585
  }
7268
7586
  return { nodesAdded, edgesAdded };
@@ -7271,13 +7589,13 @@ async function addVercelServices(graph, services, scanPath) {
7271
7589
  // src/extract/infra/railway.ts
7272
7590
  init_cjs_shims();
7273
7591
  var import_node_fs21 = require("fs");
7274
- var import_node_path37 = __toESM(require("path"), 1);
7592
+ var import_node_path38 = __toESM(require("path"), 1);
7275
7593
  var import_smol_toml3 = require("smol-toml");
7276
- var import_types27 = require("@neat.is/types");
7594
+ var import_types28 = require("@neat.is/types");
7277
7595
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
7278
7596
  async function readRailwayConfig(dir) {
7279
7597
  for (const filename of RAILWAY_FILENAMES) {
7280
- const abs = import_node_path37.default.join(dir, filename);
7598
+ const abs = import_node_path38.default.join(dir, filename);
7281
7599
  if (!await exists(abs)) continue;
7282
7600
  const raw = await import_node_fs21.promises.readFile(abs, "utf8");
7283
7601
  const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -7293,7 +7611,7 @@ async function addRailwayServices(graph, services, scanPath) {
7293
7611
  try {
7294
7612
  read = await readRailwayConfig(service.dir);
7295
7613
  } catch (err) {
7296
- recordExtractionError("infra railway", import_node_path37.default.relative(scanPath, service.dir), err);
7614
+ recordExtractionError("infra railway", import_node_path38.default.relative(scanPath, service.dir), err);
7297
7615
  continue;
7298
7616
  }
7299
7617
  if (!read) continue;
@@ -7303,7 +7621,7 @@ async function addRailwayServices(graph, services, scanPath) {
7303
7621
  }
7304
7622
  const anchorId = service.node.id;
7305
7623
  const { config, relFile, raw } = read;
7306
- const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, relFile)));
7624
+ const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
7307
7625
  const add = (edgeType, kind, name) => {
7308
7626
  if (!name) return;
7309
7627
  const result = emitPlatformResourceEdge(
@@ -7319,9 +7637,9 @@ async function addRailwayServices(graph, services, scanPath) {
7319
7637
  nodesAdded += result.nodesAdded;
7320
7638
  edgesAdded += result.edgesAdded;
7321
7639
  };
7322
- add(import_types27.EdgeType.RUNS_ON, "railway", "railway");
7323
- add(import_types27.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
7324
- add(import_types27.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
7640
+ add(import_types28.EdgeType.RUNS_ON, "railway", "railway");
7641
+ add(import_types28.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
7642
+ add(import_types28.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
7325
7643
  }
7326
7644
  return { nodesAdded, edgesAdded };
7327
7645
  }
@@ -7329,12 +7647,12 @@ async function addRailwayServices(graph, services, scanPath) {
7329
7647
  // src/extract/infra/supabase.ts
7330
7648
  init_cjs_shims();
7331
7649
  var import_node_fs22 = require("fs");
7332
- var import_node_path38 = __toESM(require("path"), 1);
7650
+ var import_node_path39 = __toESM(require("path"), 1);
7333
7651
  var import_smol_toml4 = require("smol-toml");
7334
- var import_types28 = require("@neat.is/types");
7652
+ var import_types29 = require("@neat.is/types");
7335
7653
  async function readSupabaseConfig(dir) {
7336
- const relFile = import_node_path38.default.join("supabase", "config.toml");
7337
- const abs = import_node_path38.default.join(dir, relFile);
7654
+ const relFile = import_node_path39.default.join("supabase", "config.toml");
7655
+ const abs = import_node_path39.default.join(dir, relFile);
7338
7656
  if (!await exists(abs)) return null;
7339
7657
  const raw = await import_node_fs22.promises.readFile(abs, "utf8");
7340
7658
  const config = (0, import_smol_toml4.parse)(raw);
@@ -7348,7 +7666,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
7348
7666
  try {
7349
7667
  read = await readSupabaseConfig(service.dir);
7350
7668
  } catch (err) {
7351
- recordExtractionError("infra supabase", import_node_path38.default.relative(scanPath, service.dir), err);
7669
+ recordExtractionError("infra supabase", import_node_path39.default.relative(scanPath, service.dir), err);
7352
7670
  continue;
7353
7671
  }
7354
7672
  if (!read) continue;
@@ -7363,7 +7681,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
7363
7681
  });
7364
7682
  }
7365
7683
  const anchorId = service.node.id;
7366
- const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
7684
+ const evidenceFile = toPosix2(import_node_path39.default.relative(scanPath, import_node_path39.default.join(service.dir, relFile)));
7367
7685
  const add = (edgeType, kind, name) => {
7368
7686
  if (!name) return;
7369
7687
  const result = emitPlatformResourceEdge(
@@ -7379,10 +7697,10 @@ async function addSupabaseProjects(graph, services, scanPath) {
7379
7697
  nodesAdded += result.nodesAdded;
7380
7698
  edgesAdded += result.edgesAdded;
7381
7699
  };
7382
- add(import_types28.EdgeType.RUNS_ON, "supabase", "supabase");
7383
- for (const fn of Object.keys(config.functions ?? {})) add(import_types28.EdgeType.DEPENDS_ON, "supabase-function", fn);
7384
- if (config.storage) add(import_types28.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
7385
- if (config.auth) add(import_types28.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
7700
+ add(import_types29.EdgeType.RUNS_ON, "supabase", "supabase");
7701
+ for (const fn of Object.keys(config.functions ?? {})) add(import_types29.EdgeType.DEPENDS_ON, "supabase-function", fn);
7702
+ if (config.storage) add(import_types29.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
7703
+ if (config.auth) add(import_types29.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
7386
7704
  }
7387
7705
  return { nodesAdded, edgesAdded };
7388
7706
  }
@@ -7404,17 +7722,17 @@ async function addInfra(graph, scanPath, services) {
7404
7722
  }
7405
7723
 
7406
7724
  // src/extract/index.ts
7407
- var import_node_path40 = __toESM(require("path"), 1);
7725
+ var import_node_path41 = __toESM(require("path"), 1);
7408
7726
 
7409
7727
  // src/extract/retire.ts
7410
7728
  init_cjs_shims();
7411
7729
  var import_node_fs23 = require("fs");
7412
- var import_node_path39 = __toESM(require("path"), 1);
7413
- var import_types29 = require("@neat.is/types");
7730
+ var import_node_path40 = __toESM(require("path"), 1);
7731
+ var import_types30 = require("@neat.is/types");
7414
7732
  function dropOrphanedFileNodes(graph) {
7415
7733
  const orphans = [];
7416
7734
  graph.forEachNode((id, attrs) => {
7417
- if (attrs.type !== import_types29.NodeType.FileNode) return;
7735
+ if (attrs.type !== import_types30.NodeType.FileNode) return;
7418
7736
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
7419
7737
  orphans.push(id);
7420
7738
  }
@@ -7427,14 +7745,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
7427
7745
  const bases = [scanPath, ...serviceDirs];
7428
7746
  graph.forEachEdge((id, attrs) => {
7429
7747
  const edge = attrs;
7430
- if (edge.provenance !== import_types29.Provenance.EXTRACTED) return;
7748
+ if (edge.provenance !== import_types30.Provenance.EXTRACTED) return;
7431
7749
  const evidenceFile = edge.evidence?.file;
7432
7750
  if (!evidenceFile) return;
7433
- if (import_node_path39.default.isAbsolute(evidenceFile)) {
7751
+ if (import_node_path40.default.isAbsolute(evidenceFile)) {
7434
7752
  if (!(0, import_node_fs23.existsSync)(evidenceFile)) toDrop.push(id);
7435
7753
  return;
7436
7754
  }
7437
- const found = bases.some((base) => (0, import_node_fs23.existsSync)(import_node_path39.default.join(base, evidenceFile)));
7755
+ const found = bases.some((base) => (0, import_node_fs23.existsSync)(import_node_path40.default.join(base, evidenceFile)));
7438
7756
  if (!found) toDrop.push(id);
7439
7757
  });
7440
7758
  for (const id of toDrop) graph.dropEdge(id);
@@ -7476,7 +7794,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7476
7794
  }
7477
7795
  const droppedEntries = drainDroppedExtracted();
7478
7796
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
7479
- const rejectedPath = import_node_path40.default.join(import_node_path40.default.dirname(opts.errorsPath), "rejected.ndjson");
7797
+ const rejectedPath = import_node_path41.default.join(import_node_path41.default.dirname(opts.errorsPath), "rejected.ndjson");
7480
7798
  try {
7481
7799
  await writeRejectedExtracted(droppedEntries, rejectedPath);
7482
7800
  } catch (err) {
@@ -7511,8 +7829,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7511
7829
  // src/persist.ts
7512
7830
  init_cjs_shims();
7513
7831
  var import_node_fs24 = require("fs");
7514
- var import_node_path41 = __toESM(require("path"), 1);
7515
- var import_types30 = require("@neat.is/types");
7832
+ var import_node_path42 = __toESM(require("path"), 1);
7833
+ var import_types31 = require("@neat.is/types");
7516
7834
  var SCHEMA_VERSION = 4;
7517
7835
  function migrateV1ToV2(payload) {
7518
7836
  const nodes = payload.graph.nodes;
@@ -7534,12 +7852,12 @@ function migrateV2ToV3(payload) {
7534
7852
  for (const edge of edges) {
7535
7853
  const attrs = edge.attributes;
7536
7854
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
7537
- attrs.provenance = import_types30.Provenance.OBSERVED;
7855
+ attrs.provenance = import_types31.Provenance.OBSERVED;
7538
7856
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
7539
7857
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
7540
7858
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
7541
7859
  if (type && source && target) {
7542
- const newId = (0, import_types30.observedEdgeId)(source, target, type);
7860
+ const newId = (0, import_types31.observedEdgeId)(source, target, type);
7543
7861
  attrs.id = newId;
7544
7862
  if (edge.key) edge.key = newId;
7545
7863
  }
@@ -7548,7 +7866,7 @@ function migrateV2ToV3(payload) {
7548
7866
  return { ...payload, schemaVersion: 3 };
7549
7867
  }
7550
7868
  async function ensureDir(filePath) {
7551
- await import_node_fs24.promises.mkdir(import_node_path41.default.dirname(filePath), { recursive: true });
7869
+ await import_node_fs24.promises.mkdir(import_node_path42.default.dirname(filePath), { recursive: true });
7552
7870
  }
7553
7871
  async function saveGraphToDisk(graph, outPath) {
7554
7872
  await ensureDir(outPath);
@@ -7629,23 +7947,23 @@ function startPersistLoop(graph, outPath, opts = {}) {
7629
7947
 
7630
7948
  // src/projects.ts
7631
7949
  init_cjs_shims();
7632
- var import_node_path42 = __toESM(require("path"), 1);
7950
+ var import_node_path43 = __toESM(require("path"), 1);
7633
7951
  function pathsForProject(project, baseDir) {
7634
7952
  if (project === DEFAULT_PROJECT) {
7635
7953
  return {
7636
- snapshotPath: import_node_path42.default.join(baseDir, "graph.json"),
7637
- errorsPath: import_node_path42.default.join(baseDir, "errors.ndjson"),
7638
- staleEventsPath: import_node_path42.default.join(baseDir, "stale-events.ndjson"),
7639
- embeddingsCachePath: import_node_path42.default.join(baseDir, "embeddings.json"),
7640
- policyViolationsPath: import_node_path42.default.join(baseDir, "policy-violations.ndjson")
7954
+ snapshotPath: import_node_path43.default.join(baseDir, "graph.json"),
7955
+ errorsPath: import_node_path43.default.join(baseDir, "errors.ndjson"),
7956
+ staleEventsPath: import_node_path43.default.join(baseDir, "stale-events.ndjson"),
7957
+ embeddingsCachePath: import_node_path43.default.join(baseDir, "embeddings.json"),
7958
+ policyViolationsPath: import_node_path43.default.join(baseDir, "policy-violations.ndjson")
7641
7959
  };
7642
7960
  }
7643
7961
  return {
7644
- snapshotPath: import_node_path42.default.join(baseDir, `${project}.json`),
7645
- errorsPath: import_node_path42.default.join(baseDir, `errors.${project}.ndjson`),
7646
- staleEventsPath: import_node_path42.default.join(baseDir, `stale-events.${project}.ndjson`),
7647
- embeddingsCachePath: import_node_path42.default.join(baseDir, `embeddings.${project}.json`),
7648
- policyViolationsPath: import_node_path42.default.join(baseDir, `policy-violations.${project}.ndjson`)
7962
+ snapshotPath: import_node_path43.default.join(baseDir, `${project}.json`),
7963
+ errorsPath: import_node_path43.default.join(baseDir, `errors.${project}.ndjson`),
7964
+ staleEventsPath: import_node_path43.default.join(baseDir, `stale-events.${project}.ndjson`),
7965
+ embeddingsCachePath: import_node_path43.default.join(baseDir, `embeddings.${project}.json`),
7966
+ policyViolationsPath: import_node_path43.default.join(baseDir, `policy-violations.${project}.ndjson`)
7649
7967
  };
7650
7968
  }
7651
7969
  var Projects = class {
@@ -7683,19 +8001,19 @@ var Projects = class {
7683
8001
  init_cjs_shims();
7684
8002
  var import_fastify = __toESM(require("fastify"), 1);
7685
8003
  var import_cors = __toESM(require("@fastify/cors"), 1);
7686
- var import_types33 = require("@neat.is/types");
8004
+ var import_types34 = require("@neat.is/types");
7687
8005
 
7688
8006
  // src/extend/index.ts
7689
8007
  init_cjs_shims();
7690
8008
  var import_node_fs26 = require("fs");
7691
- var import_node_path44 = __toESM(require("path"), 1);
8009
+ var import_node_path45 = __toESM(require("path"), 1);
7692
8010
  var import_node_os2 = __toESM(require("os"), 1);
7693
8011
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
7694
8012
 
7695
8013
  // src/installers/package-manager.ts
7696
8014
  init_cjs_shims();
7697
8015
  var import_node_fs25 = require("fs");
7698
- var import_node_path43 = __toESM(require("path"), 1);
8016
+ var import_node_path44 = __toESM(require("path"), 1);
7699
8017
  var import_node_child_process = require("child_process");
7700
8018
  var LOCKFILE_PRIORITY = [
7701
8019
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -7717,22 +8035,22 @@ async function exists2(p) {
7717
8035
  }
7718
8036
  }
7719
8037
  async function detectPackageManager(serviceDir) {
7720
- let dir = import_node_path43.default.resolve(serviceDir);
8038
+ let dir = import_node_path44.default.resolve(serviceDir);
7721
8039
  const stops = /* @__PURE__ */ new Set();
7722
8040
  for (let i = 0; i < 64; i++) {
7723
8041
  if (stops.has(dir)) break;
7724
8042
  stops.add(dir);
7725
8043
  for (const candidate of LOCKFILE_PRIORITY) {
7726
- const lockPath = import_node_path43.default.join(dir, candidate.lockfile);
8044
+ const lockPath = import_node_path44.default.join(dir, candidate.lockfile);
7727
8045
  if (await exists2(lockPath)) {
7728
8046
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
7729
8047
  }
7730
8048
  }
7731
- const parent = import_node_path43.default.dirname(dir);
8049
+ const parent = import_node_path44.default.dirname(dir);
7732
8050
  if (parent === dir) break;
7733
8051
  dir = parent;
7734
8052
  }
7735
- return { pm: "npm", cwd: import_node_path43.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
8053
+ return { pm: "npm", cwd: import_node_path44.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
7736
8054
  }
7737
8055
  async function runPackageManagerInstall(cmd) {
7738
8056
  return new Promise((resolve) => {
@@ -7781,7 +8099,7 @@ async function fileExists2(p) {
7781
8099
  }
7782
8100
  }
7783
8101
  async function readPackageJson(scanPath) {
7784
- const pkgPath = import_node_path44.default.join(scanPath, "package.json");
8102
+ const pkgPath = import_node_path45.default.join(scanPath, "package.json");
7785
8103
  const raw = await import_node_fs26.promises.readFile(pkgPath, "utf8");
7786
8104
  return JSON.parse(raw);
7787
8105
  }
@@ -7800,11 +8118,11 @@ async function findHookFiles(scanPath) {
7800
8118
  for (const entry2 of entries) {
7801
8119
  if (entry2.isDirectory()) {
7802
8120
  if (entry2.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry2.name)) continue;
7803
- await walk3(import_node_path44.default.join(dir, entry2.name));
8121
+ await walk3(import_node_path45.default.join(dir, entry2.name));
7804
8122
  } else if (entry2.isFile()) {
7805
8123
  if ((entry2.name.startsWith("instrumentation") || entry2.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry2.name)) {
7806
- const rel = import_node_path44.default.relative(scanPath, import_node_path44.default.join(dir, entry2.name));
7807
- found.push(rel.split(import_node_path44.default.sep).join("/"));
8124
+ const rel = import_node_path45.default.relative(scanPath, import_node_path45.default.join(dir, entry2.name));
8125
+ found.push(rel.split(import_node_path45.default.sep).join("/"));
7808
8126
  }
7809
8127
  }
7810
8128
  }
@@ -7815,7 +8133,7 @@ async function findHookFiles(scanPath) {
7815
8133
  async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
7816
8134
  let fallback = null;
7817
8135
  for (const file of hookFiles) {
7818
- const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(scanPath, file), "utf8");
8136
+ const content = await import_node_fs26.promises.readFile(import_node_path45.default.join(scanPath, file), "utf8");
7819
8137
  const patched = splicedContent(content, snippet2);
7820
8138
  if (patched !== null) return { file, content, patched };
7821
8139
  if (fallback === null) fallback = { file, content };
@@ -7823,11 +8141,11 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
7823
8141
  return { file: fallback.file, content: fallback.content, patched: null };
7824
8142
  }
7825
8143
  function extendLogPath() {
7826
- return process.env.NEAT_EXTEND_LOG ?? import_node_path44.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
8144
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path45.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
7827
8145
  }
7828
8146
  async function appendExtendLog(entry2) {
7829
8147
  const logPath = extendLogPath();
7830
- await import_node_fs26.promises.mkdir(import_node_path44.default.dirname(logPath), { recursive: true });
8148
+ await import_node_fs26.promises.mkdir(import_node_path45.default.dirname(logPath), { recursive: true });
7831
8149
  await import_node_fs26.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
7832
8150
  }
7833
8151
  function splicedContent(fileContent, snippet2) {
@@ -7886,7 +8204,7 @@ function lookupInstrumentation(library, installedVersion) {
7886
8204
  }
7887
8205
  async function describeProjectInstrumentation(ctx) {
7888
8206
  const hookFiles = await findHookFiles(ctx.scanPath);
7889
- const envNeat = await fileExists2(import_node_path44.default.join(ctx.scanPath, ".env.neat"));
8207
+ const envNeat = await fileExists2(import_node_path45.default.join(ctx.scanPath, ".env.neat"));
7890
8208
  const registryInstrPackages = new Set(
7891
8209
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
7892
8210
  );
@@ -7908,7 +8226,7 @@ async function applyExtension(ctx, args, options) {
7908
8226
  );
7909
8227
  }
7910
8228
  for (const file of hookFiles) {
7911
- const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, file), "utf8");
8229
+ const content = await import_node_fs26.promises.readFile(import_node_path45.default.join(ctx.scanPath, file), "utf8");
7912
8230
  if (content.includes(args.registration_snippet)) {
7913
8231
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
7914
8232
  }
@@ -7920,10 +8238,10 @@ async function applyExtension(ctx, args, options) {
7920
8238
  );
7921
8239
  }
7922
8240
  const primaryFile = primary.file;
7923
- const primaryPath = import_node_path44.default.join(ctx.scanPath, primaryFile);
8241
+ const primaryPath = import_node_path45.default.join(ctx.scanPath, primaryFile);
7924
8242
  const filesTouched = [];
7925
8243
  const depsAdded = [];
7926
- const pkgPath = import_node_path44.default.join(ctx.scanPath, "package.json");
8244
+ const pkgPath = import_node_path45.default.join(ctx.scanPath, "package.json");
7927
8245
  const pkg = await readPackageJson(ctx.scanPath);
7928
8246
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
7929
8247
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
@@ -7962,7 +8280,7 @@ async function dryRunExtension(ctx, args) {
7962
8280
  };
7963
8281
  }
7964
8282
  for (const file of hookFiles) {
7965
- const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, file), "utf8");
8283
+ const content = await import_node_fs26.promises.readFile(import_node_path45.default.join(ctx.scanPath, file), "utf8");
7966
8284
  if (content.includes(args.registration_snippet)) {
7967
8285
  return {
7968
8286
  library: args.library,
@@ -8003,7 +8321,7 @@ async function rollbackExtension(ctx, args) {
8003
8321
  if (!match) {
8004
8322
  return { undone: false, message: "no apply found for library" };
8005
8323
  }
8006
- const pkgPath = import_node_path44.default.join(ctx.scanPath, "package.json");
8324
+ const pkgPath = import_node_path45.default.join(ctx.scanPath, "package.json");
8007
8325
  if (await fileExists2(pkgPath)) {
8008
8326
  const pkg = await readPackageJson(ctx.scanPath);
8009
8327
  if (pkg.dependencies?.[match.instrumentation_package]) {
@@ -8014,7 +8332,7 @@ async function rollbackExtension(ctx, args) {
8014
8332
  }
8015
8333
  const hookFiles = await findHookFiles(ctx.scanPath);
8016
8334
  for (const file of hookFiles) {
8017
- const filePath = import_node_path44.default.join(ctx.scanPath, file);
8335
+ const filePath = import_node_path45.default.join(ctx.scanPath, file);
8018
8336
  const content = await import_node_fs26.promises.readFile(filePath, "utf8");
8019
8337
  if (content.includes(match.registration_snippet)) {
8020
8338
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
@@ -8030,39 +8348,39 @@ async function rollbackExtension(ctx, args) {
8030
8348
 
8031
8349
  // src/divergences.ts
8032
8350
  init_cjs_shims();
8033
- var import_types31 = require("@neat.is/types");
8351
+ var import_types32 = require("@neat.is/types");
8034
8352
  function bucketKey(source, target, type) {
8035
8353
  return `${type}|${source}|${target}`;
8036
8354
  }
8037
8355
  function bucketSourceFor(graph, edge) {
8038
- if (edge.type !== import_types31.EdgeType.CONNECTS_TO) return edge.source;
8039
- const parsed = (0, import_types31.parseFileId)(edge.source);
8356
+ if (edge.type !== import_types32.EdgeType.CONNECTS_TO) return edge.source;
8357
+ const parsed = (0, import_types32.parseFileId)(edge.source);
8040
8358
  if (!parsed || !graph.hasNode(edge.target)) return edge.source;
8041
8359
  const target = graph.getNodeAttributes(edge.target);
8042
- if (target.type !== import_types31.NodeType.DatabaseNode) return edge.source;
8043
- return (0, import_types31.serviceId)(parsed.service);
8360
+ if (target.type !== import_types32.NodeType.DatabaseNode) return edge.source;
8361
+ return (0, import_types32.serviceId)(parsed.service);
8044
8362
  }
8045
8363
  function bucketEdges(graph) {
8046
8364
  const buckets2 = /* @__PURE__ */ new Map();
8047
8365
  graph.forEachEdge((id, attrs) => {
8048
8366
  const e = attrs;
8049
- const parsed = (0, import_types31.parseEdgeId)(id);
8367
+ const parsed = (0, import_types32.parseEdgeId)(id);
8050
8368
  const provenance = parsed?.provenance ?? e.provenance;
8051
8369
  const source = bucketSourceFor(graph, e);
8052
8370
  const key = bucketKey(source, e.target, e.type);
8053
8371
  const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
8054
8372
  switch (provenance) {
8055
- case import_types31.Provenance.EXTRACTED:
8373
+ case import_types32.Provenance.EXTRACTED:
8056
8374
  cur.extracted = e;
8057
8375
  break;
8058
- case import_types31.Provenance.OBSERVED:
8376
+ case import_types32.Provenance.OBSERVED:
8059
8377
  cur.observed = e;
8060
8378
  break;
8061
- case import_types31.Provenance.INFERRED:
8379
+ case import_types32.Provenance.INFERRED:
8062
8380
  cur.inferred = e;
8063
8381
  break;
8064
8382
  default:
8065
- if (e.provenance === import_types31.Provenance.STALE) cur.stale = e;
8383
+ if (e.provenance === import_types32.Provenance.STALE) cur.stale = e;
8066
8384
  }
8067
8385
  buckets2.set(key, cur);
8068
8386
  });
@@ -8071,12 +8389,12 @@ function bucketEdges(graph) {
8071
8389
  function nodeIsFrontier(graph, nodeId) {
8072
8390
  if (!graph.hasNode(nodeId)) return false;
8073
8391
  const attrs = graph.getNodeAttributes(nodeId);
8074
- return attrs.type === import_types31.NodeType.FrontierNode;
8392
+ return attrs.type === import_types32.NodeType.FrontierNode;
8075
8393
  }
8076
8394
  function nodeIsWebsocketChannel(graph, nodeId) {
8077
8395
  if (!graph.hasNode(nodeId)) return false;
8078
8396
  const attrs = graph.getNodeAttributes(nodeId);
8079
- return attrs.type === import_types31.NodeType.WebSocketChannelNode;
8397
+ return attrs.type === import_types32.NodeType.WebSocketChannelNode;
8080
8398
  }
8081
8399
  function clampConfidence(n) {
8082
8400
  if (!Number.isFinite(n)) return 0;
@@ -8096,14 +8414,14 @@ function gradedConfidence(edge) {
8096
8414
  return clampConfidence(confidenceForEdge(edge));
8097
8415
  }
8098
8416
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
8099
- import_types31.EdgeType.CALLS,
8100
- import_types31.EdgeType.CONNECTS_TO,
8101
- import_types31.EdgeType.PUBLISHES_TO,
8102
- import_types31.EdgeType.CONSUMES_FROM
8417
+ import_types32.EdgeType.CALLS,
8418
+ import_types32.EdgeType.CONNECTS_TO,
8419
+ import_types32.EdgeType.PUBLISHES_TO,
8420
+ import_types32.EdgeType.CONSUMES_FROM
8103
8421
  ]);
8104
8422
  function detectMissingDivergences(graph, bucket) {
8105
8423
  const out = [];
8106
- if (bucket.type === import_types31.EdgeType.CONTAINS) return out;
8424
+ if (bucket.type === import_types32.EdgeType.CONTAINS) return out;
8107
8425
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
8108
8426
  if (!nodeIsFrontier(graph, bucket.target)) {
8109
8427
  out.push({
@@ -8144,7 +8462,7 @@ function declaredHostFor(svc) {
8144
8462
  function hasExtractedConfiguredBy(graph, svcId) {
8145
8463
  for (const edgeId of graph.outboundEdges(svcId)) {
8146
8464
  const e = graph.getEdgeAttributes(edgeId);
8147
- if (e.type === import_types31.EdgeType.CONFIGURED_BY && e.provenance === import_types31.Provenance.EXTRACTED) {
8465
+ if (e.type === import_types32.EdgeType.CONFIGURED_BY && e.provenance === import_types32.Provenance.EXTRACTED) {
8148
8466
  return true;
8149
8467
  }
8150
8468
  }
@@ -8157,10 +8475,10 @@ function detectHostMismatch(graph, svcId, svc) {
8157
8475
  const out = [];
8158
8476
  for (const edgeId of graph.outboundEdges(svcId)) {
8159
8477
  const edge = graph.getEdgeAttributes(edgeId);
8160
- if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
8161
- if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
8478
+ if (edge.type !== import_types32.EdgeType.CONNECTS_TO) continue;
8479
+ if (edge.provenance !== import_types32.Provenance.OBSERVED) continue;
8162
8480
  const target = graph.getNodeAttributes(edge.target);
8163
- if (target.type !== import_types31.NodeType.DatabaseNode) continue;
8481
+ if (target.type !== import_types32.NodeType.DatabaseNode) continue;
8164
8482
  const observedHost = target.host?.trim();
8165
8483
  if (!observedHost) continue;
8166
8484
  if (observedHost === declaredHost) continue;
@@ -8182,10 +8500,10 @@ function detectCompatDivergences(graph, svcId, svc) {
8182
8500
  const deps = svc.dependencies ?? {};
8183
8501
  for (const edgeId of graph.outboundEdges(svcId)) {
8184
8502
  const edge = graph.getEdgeAttributes(edgeId);
8185
- if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
8186
- if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
8503
+ if (edge.type !== import_types32.EdgeType.CONNECTS_TO) continue;
8504
+ if (edge.provenance !== import_types32.Provenance.OBSERVED) continue;
8187
8505
  const target = graph.getNodeAttributes(edge.target);
8188
- if (target.type !== import_types31.NodeType.DatabaseNode) continue;
8506
+ if (target.type !== import_types32.NodeType.DatabaseNode) continue;
8189
8507
  for (const pair of compatPairs()) {
8190
8508
  if (pair.engine !== target.engine) continue;
8191
8509
  const declared = deps[pair.driver];
@@ -8244,7 +8562,7 @@ function suppressHostMismatchHalves(all) {
8244
8562
  for (const d of all) {
8245
8563
  if (d.type !== "host-mismatch") continue;
8246
8564
  observedHalf.add(`${d.source}->${d.target}`);
8247
- declaredHalf.add((0, import_types31.databaseId)(d.extractedHost));
8565
+ declaredHalf.add((0, import_types32.databaseId)(d.extractedHost));
8248
8566
  }
8249
8567
  if (observedHalf.size === 0) return all;
8250
8568
  return all.filter((d) => {
@@ -8263,7 +8581,7 @@ function computeDivergences(graph, opts = {}) {
8263
8581
  }
8264
8582
  graph.forEachNode((nodeId, attrs) => {
8265
8583
  const n = attrs;
8266
- if (n.type !== import_types31.NodeType.ServiceNode) return;
8584
+ if (n.type !== import_types32.NodeType.ServiceNode) return;
8267
8585
  const svc = n;
8268
8586
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
8269
8587
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -8297,7 +8615,7 @@ function computeDivergences(graph, opts = {}) {
8297
8615
  if (a.source !== b.source) return a.source.localeCompare(b.source);
8298
8616
  return a.target.localeCompare(b.target);
8299
8617
  });
8300
- return import_types31.DivergenceResultSchema.parse({
8618
+ return import_types32.DivergenceResultSchema.parse({
8301
8619
  divergences: filtered,
8302
8620
  totalAffected: filtered.length,
8303
8621
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -8430,23 +8748,23 @@ function canonicalJson(value) {
8430
8748
  init_cjs_shims();
8431
8749
  var import_node_fs28 = require("fs");
8432
8750
  var import_node_os3 = __toESM(require("os"), 1);
8433
- var import_node_path45 = __toESM(require("path"), 1);
8434
- var import_types32 = require("@neat.is/types");
8751
+ var import_node_path46 = __toESM(require("path"), 1);
8752
+ var import_types33 = require("@neat.is/types");
8435
8753
  var LOCK_TIMEOUT_MS = 5e3;
8436
8754
  var LOCK_RETRY_MS = 50;
8437
8755
  function neatHome() {
8438
8756
  const override = process.env.NEAT_HOME;
8439
- if (override && override.length > 0) return import_node_path45.default.resolve(override);
8440
- return import_node_path45.default.join(import_node_os3.default.homedir(), ".neat");
8757
+ if (override && override.length > 0) return import_node_path46.default.resolve(override);
8758
+ return import_node_path46.default.join(import_node_os3.default.homedir(), ".neat");
8441
8759
  }
8442
8760
  function registryPath() {
8443
- return import_node_path45.default.join(neatHome(), "projects.json");
8761
+ return import_node_path46.default.join(neatHome(), "projects.json");
8444
8762
  }
8445
8763
  function registryLockPath() {
8446
- return import_node_path45.default.join(neatHome(), "projects.json.lock");
8764
+ return import_node_path46.default.join(neatHome(), "projects.json.lock");
8447
8765
  }
8448
8766
  function daemonPidPath() {
8449
- return import_node_path45.default.join(neatHome(), "neatd.pid");
8767
+ return import_node_path46.default.join(neatHome(), "neatd.pid");
8450
8768
  }
8451
8769
  function isPidAliveDefault(pid) {
8452
8770
  try {
@@ -8506,7 +8824,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
8506
8824
  }
8507
8825
  }
8508
8826
  async function writeAtomically(target, contents) {
8509
- await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(target), { recursive: true });
8827
+ await import_node_fs28.promises.mkdir(import_node_path46.default.dirname(target), { recursive: true });
8510
8828
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
8511
8829
  const fd = await import_node_fs28.promises.open(tmp, "w");
8512
8830
  try {
@@ -8519,7 +8837,7 @@ async function writeAtomically(target, contents) {
8519
8837
  }
8520
8838
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
8521
8839
  const deadline = Date.now() + timeoutMs;
8522
- await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(lockPath), { recursive: true });
8840
+ await import_node_fs28.promises.mkdir(import_node_path46.default.dirname(lockPath), { recursive: true });
8523
8841
  let probedHolder = false;
8524
8842
  while (true) {
8525
8843
  try {
@@ -8572,10 +8890,10 @@ async function readRegistry() {
8572
8890
  throw err;
8573
8891
  }
8574
8892
  const parsed = JSON.parse(raw);
8575
- return import_types32.RegistryFileSchema.parse(parsed);
8893
+ return import_types33.RegistryFileSchema.parse(parsed);
8576
8894
  }
8577
8895
  async function writeRegistry(reg) {
8578
- const validated = import_types32.RegistryFileSchema.parse(reg);
8896
+ const validated = import_types33.RegistryFileSchema.parse(reg);
8579
8897
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
8580
8898
  }
8581
8899
  async function getProject(name) {
@@ -8720,7 +9038,7 @@ init_auth();
8720
9038
  // src/connectors-config.ts
8721
9039
  init_cjs_shims();
8722
9040
  var import_node_os4 = __toESM(require("os"), 1);
8723
- var import_node_path46 = __toESM(require("path"), 1);
9041
+ var import_node_path47 = __toESM(require("path"), 1);
8724
9042
  var import_node_fs29 = require("fs");
8725
9043
  var CONNECTORS_CONFIG_VERSION = 1;
8726
9044
  var EnvRefUnsetError = class extends Error {
@@ -8735,11 +9053,11 @@ var EnvRefUnsetError = class extends Error {
8735
9053
  };
8736
9054
  function neatHome2() {
8737
9055
  const override = process.env.NEAT_HOME;
8738
- if (override && override.length > 0) return import_node_path46.default.resolve(override);
8739
- return import_node_path46.default.join(import_node_os4.default.homedir(), ".neat");
9056
+ if (override && override.length > 0) return import_node_path47.default.resolve(override);
9057
+ return import_node_path47.default.join(import_node_os4.default.homedir(), ".neat");
8740
9058
  }
8741
9059
  function connectorsConfigPath(home = neatHome2()) {
8742
- return import_node_path46.default.join(home, "connectors.json");
9060
+ return import_node_path47.default.join(home, "connectors.json");
8743
9061
  }
8744
9062
  var MODE_MASK_LOOSER_THAN_0600 = 63;
8745
9063
  async function warnIfModeLooserThan0600(file) {
@@ -9075,11 +9393,11 @@ function registerRoutes(scope, ctx) {
9075
9393
  const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
9076
9394
  const parsed = [];
9077
9395
  for (const c of candidates) {
9078
- const r = import_types33.DivergenceTypeSchema.safeParse(c);
9396
+ const r = import_types34.DivergenceTypeSchema.safeParse(c);
9079
9397
  if (!r.success) {
9080
9398
  return reply.code(400).send({
9081
9399
  error: `unknown divergence type "${c}"`,
9082
- allowed: import_types33.DivergenceTypeSchema.options
9400
+ allowed: import_types34.DivergenceTypeSchema.options
9083
9401
  });
9084
9402
  }
9085
9403
  parsed.push(r.data);
@@ -9350,7 +9668,7 @@ function registerRoutes(scope, ctx) {
9350
9668
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
9351
9669
  let violations = await log.readAll();
9352
9670
  if (req2.query.severity) {
9353
- const sev = import_types33.PolicySeveritySchema.safeParse(req2.query.severity);
9671
+ const sev = import_types34.PolicySeveritySchema.safeParse(req2.query.severity);
9354
9672
  if (!sev.success) {
9355
9673
  return reply.code(400).send({
9356
9674
  error: "invalid severity",
@@ -9389,7 +9707,7 @@ function registerRoutes(scope, ctx) {
9389
9707
  scope.post("/policies/check", async (req2, reply) => {
9390
9708
  const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
9391
9709
  if (!proj) return;
9392
- const parsed = import_types33.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
9710
+ const parsed = import_types34.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
9393
9711
  if (!parsed.success) {
9394
9712
  return reply.code(400).send({
9395
9713
  error: "invalid /policies/check body",
@@ -9461,6 +9779,42 @@ function registerRoutes(scope, ctx) {
9461
9779
  return reply.code(500).send({ error: err.message });
9462
9780
  }
9463
9781
  });
9782
+ scope.get("/instrumentation", async (req2, reply) => {
9783
+ const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
9784
+ if (!proj) return;
9785
+ if (!proj.scanPath) {
9786
+ return { engaged: null };
9787
+ }
9788
+ try {
9789
+ const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath });
9790
+ const uninstrumented = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath });
9791
+ if (state.hookFiles.length === 0) {
9792
+ return {
9793
+ engaged: false,
9794
+ diagnosis: {
9795
+ reason: "No instrumented entry point found \u2014 NEAT hasn't written an OTel init hook for this project.",
9796
+ fixCommand: "neat init",
9797
+ detail: "Run `neat init` so NEAT writes the instrumentation hook next to your entry point, then run your app."
9798
+ }
9799
+ };
9800
+ }
9801
+ if (uninstrumented.length > 0) {
9802
+ const names = uninstrumented.map((u) => u.library);
9803
+ const more = names.length > 1 ? ` (and ${names.length - 1} more)` : "";
9804
+ return {
9805
+ engaged: false,
9806
+ diagnosis: {
9807
+ reason: `\`${names[0]}\`${more} isn't in the auto-instrumentation set, so spans from it won't reach the graph.`,
9808
+ fixCommand: "neat extend",
9809
+ detail: `Uninstrumented on your hot path: ${names.join(", ")}. Run \`neat extend\` to wire the missing instrumentation.`
9810
+ }
9811
+ };
9812
+ }
9813
+ return { engaged: true };
9814
+ } catch {
9815
+ return { engaged: null };
9816
+ }
9817
+ });
9464
9818
  scope.post("/extend/apply", async (req2, reply) => {
9465
9819
  const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
9466
9820
  if (!proj) return;
@@ -9653,15 +10007,15 @@ init_otel();
9653
10007
 
9654
10008
  // src/connectors/index.ts
9655
10009
  init_cjs_shims();
9656
- var import_types34 = require("@neat.is/types");
10010
+ var import_types35 = require("@neat.is/types");
9657
10011
  var NO_ENV = "unknown";
9658
10012
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
9659
10013
  if (!graph.hasNode(targetNodeId)) return void 0;
9660
10014
  const sites = [];
9661
10015
  for (const edgeId of graph.inboundEdges(targetNodeId)) {
9662
10016
  const edge = graph.getEdgeAttributes(edgeId);
9663
- if (edge.provenance !== import_types34.Provenance.EXTRACTED) continue;
9664
- const parsed = (0, import_types34.parseFileId)(edge.source);
10017
+ if (edge.provenance !== import_types35.Provenance.EXTRACTED) continue;
10018
+ const parsed = (0, import_types35.parseFileId)(edge.source);
9665
10019
  if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
9666
10020
  const site = { relPath: edge.evidence.file };
9667
10021
  if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
@@ -9672,7 +10026,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
9672
10026
  function routeCallSiteFor(graph, targetNodeId) {
9673
10027
  if (!graph.hasNode(targetNodeId)) return void 0;
9674
10028
  const attrs = graph.getNodeAttributes(targetNodeId);
9675
- if (attrs.type !== import_types34.NodeType.RouteNode || !attrs.path) return void 0;
10029
+ if (attrs.type !== import_types35.NodeType.RouteNode || !attrs.path) return void 0;
9676
10030
  const site = { relPath: attrs.path };
9677
10031
  if (attrs.line !== void 0) site.line = attrs.line;
9678
10032
  return site;
@@ -9846,7 +10200,12 @@ var JUNCTION_DEFAULT_RATE_LIMITS = {
9846
10200
  supabase: { capacity: 30, refillMs: 1e4 },
9847
10201
  // Not a documented API limit at all — a self-imposed ceiling on the raw
9848
10202
  // pg_stat_statements connection (see module header above).
9849
- "supabase-postgres": { capacity: 20, refillMs: 3e3 }
10203
+ "supabase-postgres": { capacity: 20, refillMs: 3e3 },
10204
+ // Push provider (ADR-146): the Drains REST API is touched only by `neat
10205
+ // connector add/remove/test` (provision/deprovision/validate), never a poll
10206
+ // loop, so this bucket is exercised a handful of times per command. Kept
10207
+ // conservative pending a documented Drains-API rate limit.
10208
+ vercel: { capacity: 20, refillMs: 5e3 }
9850
10209
  };
9851
10210
  var JUNCTION_GENERIC_RATE_LIMIT = { capacity: 20, refillMs: 5e3 };
9852
10211
  function defaultRateLimitFor(provider) {
@@ -10138,10 +10497,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
10138
10497
  // src/connectors/supabase/map.ts
10139
10498
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
10140
10499
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
10141
- function targetFromRestPath(path53) {
10142
- const rpcMatch = REST_RPC_PATH_RE.exec(path53);
10500
+ function targetFromRestPath(path54) {
10501
+ const rpcMatch = REST_RPC_PATH_RE.exec(path54);
10143
10502
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
10144
- const tableMatch = REST_TABLE_PATH_RE.exec(path53);
10503
+ const tableMatch = REST_TABLE_PATH_RE.exec(path54);
10145
10504
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
10146
10505
  return null;
10147
10506
  }
@@ -10250,23 +10609,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
10250
10609
 
10251
10610
  // src/connectors/supabase/resolve.ts
10252
10611
  init_cjs_shims();
10253
- var import_types36 = require("@neat.is/types");
10612
+ var import_types37 = require("@neat.is/types");
10254
10613
  function createSupabaseResolveTarget(graph, config) {
10255
10614
  return (signal, _ctx) => {
10256
10615
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
10257
10616
  return null;
10258
10617
  }
10259
- const subResourceId = (0, import_types36.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
10618
+ const subResourceId = (0, import_types37.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
10260
10619
  if (graph.hasNode(subResourceId)) {
10261
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
10620
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10262
10621
  }
10263
- const bareResourceId = (0, import_types36.infraId)(signal.targetKind, signal.targetName);
10622
+ const bareResourceId = (0, import_types37.infraId)(signal.targetKind, signal.targetName);
10264
10623
  if (graph.hasNode(bareResourceId)) {
10265
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
10624
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10266
10625
  }
10267
- const projectLevelId = (0, import_types36.infraId)("supabase", config.nodeRef);
10626
+ const projectLevelId = (0, import_types37.infraId)("supabase", config.nodeRef);
10268
10627
  if (graph.hasNode(projectLevelId)) {
10269
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
10628
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10270
10629
  }
10271
10630
  return null;
10272
10631
  };
@@ -10359,7 +10718,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
10359
10718
 
10360
10719
  // src/connectors/railway/index.ts
10361
10720
  init_cjs_shims();
10362
- var import_types40 = require("@neat.is/types");
10721
+ var import_types41 = require("@neat.is/types");
10363
10722
 
10364
10723
  // src/connectors/railway/client.ts
10365
10724
  init_cjs_shims();
@@ -10485,7 +10844,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
10485
10844
  const out = [];
10486
10845
  graph.forEachNode((_id, attrs) => {
10487
10846
  const node = attrs;
10488
- if (node.type !== import_types40.NodeType.RouteNode) return;
10847
+ if (node.type !== import_types41.NodeType.RouteNode) return;
10489
10848
  const route = attrs;
10490
10849
  if (route.service !== serviceName) return;
10491
10850
  out.push({
@@ -10589,12 +10948,12 @@ function createRailwayResolveTarget(config) {
10589
10948
  const serviceName = config.serviceNameById[config.serviceId];
10590
10949
  if (!serviceName) return null;
10591
10950
  if (signal.targetKind === ROUTE_TARGET_KIND) {
10592
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types40.EdgeType.CALLS };
10951
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types41.EdgeType.CALLS };
10593
10952
  }
10594
10953
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
10595
10954
  const peerName = config.serviceNameById[signal.targetName];
10596
10955
  if (!peerName) return null;
10597
- return { targetNodeId: (0, import_types40.serviceId)(peerName), serviceName, edgeType: import_types40.EdgeType.CONNECTS_TO };
10956
+ return { targetNodeId: (0, import_types41.serviceId)(peerName), serviceName, edgeType: import_types41.EdgeType.CONNECTS_TO };
10598
10957
  }
10599
10958
  return null;
10600
10959
  };
@@ -10718,9 +11077,9 @@ function parseFirebaseTargetName(targetName) {
10718
11077
  const secondSep = rest.indexOf(FIELD_SEP);
10719
11078
  if (secondSep === -1) return null;
10720
11079
  const method = rest.slice(0, secondSep);
10721
- const path53 = rest.slice(secondSep + 1);
10722
- if (!resourceName || !method || !path53) return null;
10723
- return { resourceName, method, path: path53 };
11080
+ const path54 = rest.slice(secondSep + 1);
11081
+ if (!resourceName || !method || !path54) return null;
11082
+ return { resourceName, method, path: path54 };
10724
11083
  }
10725
11084
  function resourceNameFor(type, labels) {
10726
11085
  if (!labels) return null;
@@ -10758,14 +11117,14 @@ function mapLogEntryToSignal(entry2) {
10758
11117
  if (!req2) return null;
10759
11118
  if (typeof req2.requestMethod !== "string" || req2.requestMethod.length === 0) return null;
10760
11119
  const method = req2.requestMethod.toUpperCase();
10761
- const path53 = pathFromRequestUrl(req2.requestUrl);
10762
- if (path53 === null) return null;
11120
+ const path54 = pathFromRequestUrl(req2.requestUrl);
11121
+ if (path54 === null) return null;
10763
11122
  const timestamp = entry2.timestamp;
10764
11123
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
10765
11124
  const isError = typeof req2.status === "number" && req2.status >= ERROR_STATUS_THRESHOLD2;
10766
11125
  return {
10767
11126
  targetKind: resourceType,
10768
- targetName: packFirebaseTargetName({ resourceName, method, path: path53 }),
11127
+ targetName: packFirebaseTargetName({ resourceName, method, path: path54 }),
10769
11128
  callCount: 1,
10770
11129
  errorCount: isError ? 1 : 0,
10771
11130
  lastObservedIso: timestamp
@@ -10782,7 +11141,7 @@ function mapLogEntriesToSignals(entries) {
10782
11141
 
10783
11142
  // src/connectors/firebase/resolve.ts
10784
11143
  init_cjs_shims();
10785
- var import_types41 = require("@neat.is/types");
11144
+ var import_types42 = require("@neat.is/types");
10786
11145
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
10787
11146
  switch (resourceType) {
10788
11147
  case "cloud_function":
@@ -10797,7 +11156,7 @@ function routeEntriesFor(graph, serviceName) {
10797
11156
  const entries = [];
10798
11157
  graph.forEachNode((_id, attrs) => {
10799
11158
  const node = attrs;
10800
- if (node.type !== import_types41.NodeType.RouteNode) return;
11159
+ if (node.type !== import_types42.NodeType.RouteNode) return;
10801
11160
  const route = attrs;
10802
11161
  if (route.service !== serviceName) return;
10803
11162
  entries.push({
@@ -10829,7 +11188,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
10829
11188
  return {
10830
11189
  targetNodeId: match.routeNodeId,
10831
11190
  serviceName,
10832
- edgeType: import_types41.EdgeType.CALLS
11191
+ edgeType: import_types42.EdgeType.CALLS
10833
11192
  };
10834
11193
  };
10835
11194
  }
@@ -10856,7 +11215,7 @@ init_cjs_shims();
10856
11215
 
10857
11216
  // src/connectors/cloudflare/connector.ts
10858
11217
  init_cjs_shims();
10859
- var import_types43 = require("@neat.is/types");
11218
+ var import_types44 = require("@neat.is/types");
10860
11219
 
10861
11220
  // src/connectors/cloudflare/client.ts
10862
11221
  init_cjs_shims();
@@ -10972,7 +11331,7 @@ function mapEventToSignal(event) {
10972
11331
  if (Number.isNaN(observedAt.getTime())) return null;
10973
11332
  const statusCode = metadata?.statusCode;
10974
11333
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
10975
- const path53 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
11334
+ const path54 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
10976
11335
  return {
10977
11336
  targetKind: CLOUDFLARE_TARGET_KIND,
10978
11337
  targetName: scriptName,
@@ -10980,7 +11339,7 @@ function mapEventToSignal(event) {
10980
11339
  errorCount: isError ? 1 : 0,
10981
11340
  lastObservedIso: observedAt.toISOString(),
10982
11341
  method,
10983
- ...path53 ? { path: path53 } : {},
11342
+ ...path54 ? { path: path54 } : {},
10984
11343
  ...typeof statusCode === "number" ? { statusCode } : {},
10985
11344
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
10986
11345
  };
@@ -11020,19 +11379,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
11020
11379
  graph.forEachNode((id, attrs) => {
11021
11380
  if (found) return;
11022
11381
  const a = attrs;
11023
- if (a.type === import_types43.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
11382
+ if (a.type === import_types44.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
11024
11383
  found = id;
11025
11384
  }
11026
11385
  });
11027
11386
  return found;
11028
11387
  }
11029
- function findMatchingRouteNode(graph, serviceName, method, path53) {
11030
- const normalizedPath = normalizePathTemplate(path53);
11388
+ function findMatchingRouteNode(graph, serviceName, method, path54) {
11389
+ const normalizedPath = normalizePathTemplate(path54);
11031
11390
  let found = null;
11032
11391
  graph.forEachNode((id, attrs) => {
11033
11392
  if (found) return;
11034
11393
  const a = attrs;
11035
- if (a.type !== import_types43.NodeType.RouteNode || a.service !== serviceName) return;
11394
+ if (a.type !== import_types44.NodeType.RouteNode || a.service !== serviceName) return;
11036
11395
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
11037
11396
  const routeMethod = (a.method ?? "").toUpperCase();
11038
11397
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -11044,18 +11403,18 @@ function createCloudflareResolveTarget(config, graph) {
11044
11403
  return (signal) => {
11045
11404
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
11046
11405
  const scriptName = signal.targetName;
11047
- const { method, path: path53 } = signal;
11406
+ const { method, path: path54 } = signal;
11048
11407
  const resolveRouteGrain = (serviceName, wholeFileId) => {
11049
- if (!method || !path53) return wholeFileId;
11050
- return findMatchingRouteNode(graph, serviceName, method, path53) ?? wholeFileId;
11408
+ if (!method || !path54) return wholeFileId;
11409
+ return findMatchingRouteNode(graph, serviceName, method, path54) ?? wholeFileId;
11051
11410
  };
11052
11411
  const mapping = config.workers?.[scriptName];
11053
11412
  if (mapping) {
11054
- const wholeFileId = (0, import_types43.fileId)(mapping.service, mapping.entryFile);
11413
+ const wholeFileId = (0, import_types44.fileId)(mapping.service, mapping.entryFile);
11055
11414
  return {
11056
11415
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
11057
11416
  serviceName: mapping.service,
11058
- edgeType: import_types43.EdgeType.CALLS
11417
+ edgeType: import_types44.EdgeType.CALLS
11059
11418
  };
11060
11419
  }
11061
11420
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -11064,18 +11423,124 @@ function createCloudflareResolveTarget(config, graph) {
11064
11423
  return {
11065
11424
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
11066
11425
  serviceName: fileNode.service,
11067
- edgeType: import_types43.EdgeType.CALLS
11426
+ edgeType: import_types44.EdgeType.CALLS
11068
11427
  };
11069
11428
  }
11070
11429
  return {
11071
- targetNodeId: (0, import_types43.infraId)("cloudflare-worker", scriptName),
11430
+ targetNodeId: (0, import_types44.infraId)("cloudflare-worker", scriptName),
11072
11431
  serviceName: scriptName,
11073
- edgeType: import_types43.EdgeType.CALLS,
11432
+ edgeType: import_types44.EdgeType.CALLS,
11074
11433
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
11075
11434
  };
11076
11435
  };
11077
11436
  }
11078
11437
 
11438
+ // src/connectors/vercel/index.ts
11439
+ init_cjs_shims();
11440
+
11441
+ // src/connectors/vercel/client.ts
11442
+ init_cjs_shims();
11443
+ var DEFAULT_API_BASE_URL = "https://api.vercel.com";
11444
+ var DEFAULT_DRAIN_NAME = "neat-otlp";
11445
+ var TRACE_SCHEMAS = { trace: { version: "v1" } };
11446
+ function apiBase(config) {
11447
+ return config.apiBaseUrl ?? DEFAULT_API_BASE_URL;
11448
+ }
11449
+ function teamQuery(config) {
11450
+ return `?teamId=${encodeURIComponent(config.teamId)}`;
11451
+ }
11452
+ function drainDelivery(config, otelToken) {
11453
+ return {
11454
+ type: "http",
11455
+ endpoint: config.endpoint,
11456
+ encoding: "json",
11457
+ headers: bearerAuthHeader(otelToken),
11458
+ ...config.secret ? { secret: config.secret } : {}
11459
+ };
11460
+ }
11461
+ async function describeError(res) {
11462
+ try {
11463
+ const data = await res.json();
11464
+ const message = data?.error?.message;
11465
+ return typeof message === "string" && message.length > 0 ? ` \u2014 ${message}` : "";
11466
+ } catch {
11467
+ return "";
11468
+ }
11469
+ }
11470
+ async function createVercelDrain(config, credentials, fetchImpl = fetch) {
11471
+ const projectIds = config.projectIds ?? [];
11472
+ const body = {
11473
+ name: config.drainName ?? DEFAULT_DRAIN_NAME,
11474
+ projects: projectIds.length > 0 ? "some" : "all",
11475
+ ...projectIds.length > 0 ? { projectIds } : {},
11476
+ schemas: TRACE_SCHEMAS,
11477
+ delivery: drainDelivery(config, credentials.otelToken),
11478
+ source: { kind: "self-served" }
11479
+ };
11480
+ const res = await junctionFetch(
11481
+ `${apiBase(config)}/v1/drains${teamQuery(config)}`,
11482
+ {
11483
+ method: "POST",
11484
+ headers: { "Content-Type": "application/json", ...bearerAuthHeader(credentials.token) },
11485
+ body: JSON.stringify(body)
11486
+ },
11487
+ { provider: "vercel", accountKey: config.teamId, fetchImpl }
11488
+ );
11489
+ if (!res.ok) {
11490
+ throw new Error(
11491
+ `vercel connector: create drain failed (${res.status} ${res.statusText}${await describeError(res)})`
11492
+ );
11493
+ }
11494
+ const payload = await res.json().catch(() => null);
11495
+ if (!payload || typeof payload.id !== "string" || payload.id.length === 0) {
11496
+ throw new Error(
11497
+ "vercel connector: create drain returned no drain id \u2014 the Drains API response shape may have changed"
11498
+ );
11499
+ }
11500
+ return {
11501
+ id: payload.id,
11502
+ ...payload.status ? { status: payload.status } : {},
11503
+ ...payload.disabledReason ? { disabledReason: payload.disabledReason } : {}
11504
+ };
11505
+ }
11506
+ async function deleteVercelDrain(config, drainId, credentials, fetchImpl = fetch) {
11507
+ const res = await junctionFetch(
11508
+ `${apiBase(config)}/v1/drains/${encodeURIComponent(drainId)}${teamQuery(config)}`,
11509
+ { method: "DELETE", headers: { ...bearerAuthHeader(credentials.token) } },
11510
+ { provider: "vercel", accountKey: config.teamId, fetchImpl }
11511
+ );
11512
+ if (res.ok || res.status === 404) return;
11513
+ throw new Error(
11514
+ `vercel connector: delete drain failed (${res.status} ${res.statusText}${await describeError(res)})`
11515
+ );
11516
+ }
11517
+ async function testVercelDrainDelivery(config, credentials, fetchImpl = fetch) {
11518
+ const res = await junctionFetch(
11519
+ `${apiBase(config)}/v1/drains/test${teamQuery(config)}`,
11520
+ {
11521
+ method: "POST",
11522
+ headers: { "Content-Type": "application/json", ...bearerAuthHeader(credentials.token) },
11523
+ body: JSON.stringify({ schemas: TRACE_SCHEMAS, delivery: drainDelivery(config, credentials.otelToken) })
11524
+ },
11525
+ { provider: "vercel", accountKey: config.teamId, fetchImpl }
11526
+ );
11527
+ if (res.status === 401 || res.status === 403) {
11528
+ return { status: "failure", error: `vercel rejected the API token (HTTP ${res.status})` };
11529
+ }
11530
+ if (!res.ok) {
11531
+ return {
11532
+ status: "failure",
11533
+ error: `vercel drain validation failed (${res.status} ${res.statusText}${await describeError(res)})`
11534
+ };
11535
+ }
11536
+ const payload = await res.json().catch(() => null);
11537
+ return {
11538
+ ...payload?.status ? { status: payload.status } : {},
11539
+ ...payload?.error ? { error: payload.error } : {},
11540
+ ...payload?.endpoint ? { endpoint: payload.endpoint } : {}
11541
+ };
11542
+ }
11543
+
11079
11544
  // src/connectors/registry.ts
11080
11545
  var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
11081
11546
  async function authProbe(input) {
@@ -11212,21 +11677,112 @@ var PROVIDER_DISPATCH = {
11212
11677
  resolveTarget: createCloudflareResolveTarget(config, graph)
11213
11678
  };
11214
11679
  },
11215
- // GET /user/tokens/verify — Cloudflare's own purpose-built "is this API
11216
- // token live" endpoint. 200 on a valid token, 401 on an invalid one.
11680
+ // GET /accounts/{accountId}/tokens/verify — the *account-scoped* token-verify
11681
+ // endpoint. A Workers connector token is scoped to the account, and the
11682
+ // user-level `GET /user/tokens/verify` returns 401 "Invalid API Token" for
11683
+ // such a token even though it authenticates fine against the account's own
11684
+ // resources (confirmed live). Probing the account-scoped verify endpoint —
11685
+ // `accountId` is already required for this provider — returns 200
11686
+ // `{status:"active"}` for a working token and 401 for a bad one, so a valid
11687
+ // Workers token is no longer falsely rejected at `neat connector add`.
11217
11688
  validate({ credentials, options, fetchImpl }) {
11218
11689
  const cfg = options;
11219
11690
  const baseUrl = cfg.baseUrl ?? CLOUDFLARE_API_BASE_URL;
11220
11691
  return authProbe({
11221
11692
  provider: "cloudflare",
11222
11693
  accountKey: cfg.accountId ?? "validate",
11223
- url: `${baseUrl}/user/tokens/verify`,
11694
+ url: `${baseUrl}/accounts/${cfg.accountId ?? ""}/tokens/verify`,
11224
11695
  token: String(credentials.apiToken ?? ""),
11225
11696
  ...fetchImpl ? { fetchImpl } : {}
11226
11697
  });
11227
11698
  }
11228
11699
  }
11229
11700
  };
11701
+ function vercelCredsFrom(credentials) {
11702
+ return { token: String(credentials.token ?? ""), otelToken: String(credentials.otelToken ?? "") };
11703
+ }
11704
+ function vercelConfigFromOptions(options) {
11705
+ const raw = options.projectIds;
11706
+ let projectIds;
11707
+ if (Array.isArray(raw)) projectIds = raw.filter((p) => typeof p === "string");
11708
+ else if (typeof raw === "string" && raw.trim().length > 0) {
11709
+ projectIds = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
11710
+ }
11711
+ return {
11712
+ teamId: String(options.teamId ?? ""),
11713
+ endpoint: String(options.endpoint ?? ""),
11714
+ ...projectIds && projectIds.length > 0 ? { projectIds } : {},
11715
+ ...typeof options.drainId === "string" ? { drainId: options.drainId } : {},
11716
+ ...typeof options.drainName === "string" ? { drainName: options.drainName } : {},
11717
+ ...typeof options.apiBaseUrl === "string" ? { apiBaseUrl: options.apiBaseUrl } : {},
11718
+ ...typeof options.secret === "string" ? { secret: options.secret } : {}
11719
+ };
11720
+ }
11721
+ var PUSH_PROVIDER_DISPATCH = {
11722
+ vercel: {
11723
+ provider: "vercel",
11724
+ // The Vercel access token is the "primary" secret a single `--token`
11725
+ // populates; `otelToken` (the daemon's OTLP bearer) is the second field.
11726
+ primaryCredentialKey: "token",
11727
+ requiredCredentialFields: ["token", "otelToken"],
11728
+ // teamId scopes every Drains call; endpoint is where the drain delivers.
11729
+ // projectIds is optional (absent → the drain covers the whole team).
11730
+ requiredOptionFields: ["teamId", "endpoint"],
11731
+ // POST /v1/drains/test — authenticates the token and pings the endpoint
11732
+ // with a sample event, so `success` means the credential is live *and* the
11733
+ // daemon's OTLP endpoint is reachable and accepted the drain's bearer.
11734
+ async validate({ credentials, options, fetchImpl }) {
11735
+ const result = await testVercelDrainDelivery(
11736
+ vercelConfigFromOptions(options),
11737
+ vercelCredsFrom(credentials),
11738
+ fetchImpl
11739
+ );
11740
+ if (result.status === "success") return { ok: true };
11741
+ return {
11742
+ ok: false,
11743
+ reason: result.error ?? `vercel drain delivery test returned "${result.status ?? "no status"}"`
11744
+ };
11745
+ },
11746
+ // POST /v1/drains — creates the trace drain, returns its id to store in
11747
+ // `options.drainId`. A created-but-not-enabled drain is surfaced as a note,
11748
+ // not a failure (the entry still points at a real drain).
11749
+ async provision({ credentials, options, fetchImpl }) {
11750
+ try {
11751
+ const created = await createVercelDrain(
11752
+ vercelConfigFromOptions(options),
11753
+ vercelCredsFrom(credentials),
11754
+ fetchImpl
11755
+ );
11756
+ 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;
11757
+ return { ok: true, options: { drainId: created.id }, ...note ? { note } : {} };
11758
+ } catch (err) {
11759
+ return { ok: false, reason: err.message };
11760
+ }
11761
+ },
11762
+ // DELETE /v1/drains/{id} — idempotent (deleteVercelDrain treats 404 as
11763
+ // success). No recorded drainId → nothing to delete, still a success.
11764
+ async deprovision({ credentials, options, fetchImpl }) {
11765
+ const drainId = typeof options.drainId === "string" ? options.drainId : "";
11766
+ if (!drainId) {
11767
+ return { ok: true, note: "no drain id was recorded \u2014 nothing to delete on the Vercel side" };
11768
+ }
11769
+ try {
11770
+ await deleteVercelDrain(
11771
+ vercelConfigFromOptions(options),
11772
+ drainId,
11773
+ vercelCredsFrom(credentials),
11774
+ fetchImpl
11775
+ );
11776
+ return { ok: true };
11777
+ } catch (err) {
11778
+ return { ok: false, reason: err.message };
11779
+ }
11780
+ }
11781
+ }
11782
+ };
11783
+ function isPushProvider(provider) {
11784
+ return provider in PUSH_PROVIDER_DISPATCH;
11785
+ }
11230
11786
  function resolveEntryCredentials(dispatch, entry2, env) {
11231
11787
  let credentials;
11232
11788
  try {
@@ -11249,6 +11805,13 @@ function resolveEntryCredentials(dispatch, entry2, env) {
11249
11805
  function buildRegistration(entry2, graph, env = process.env) {
11250
11806
  const dispatch = PROVIDER_DISPATCH[entry2.provider];
11251
11807
  if (!dispatch) {
11808
+ if (isPushProvider(entry2.provider)) {
11809
+ return {
11810
+ ok: false,
11811
+ push: true,
11812
+ reason: `push provider "${entry2.provider}" ingests via the OTLP receiver \u2014 nothing to poll`
11813
+ };
11814
+ }
11252
11815
  return { ok: false, reason: `unknown provider "${entry2.provider}"` };
11253
11816
  }
11254
11817
  const creds = resolveEntryCredentials(dispatch, entry2, env);
@@ -11299,7 +11862,7 @@ async function loadConnectorRegistrations(input) {
11299
11862
  if (!connectorMatchesProject(entry2, project)) continue;
11300
11863
  const result = buildRegistration(entry2, graph, env);
11301
11864
  if (result.ok) registrations.push(result.registration);
11302
- else onSkip?.(entry2, result.reason);
11865
+ else if (!result.push) onSkip?.(entry2, result.reason);
11303
11866
  }
11304
11867
  return registrations;
11305
11868
  }
@@ -11310,7 +11873,7 @@ init_auth();
11310
11873
  // src/unrouted.ts
11311
11874
  init_cjs_shims();
11312
11875
  var import_node_fs30 = require("fs");
11313
- var import_node_path49 = __toESM(require("path"), 1);
11876
+ var import_node_path50 = __toESM(require("path"), 1);
11314
11877
  function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
11315
11878
  return {
11316
11879
  timestamp: now.toISOString(),
@@ -11320,34 +11883,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
11320
11883
  };
11321
11884
  }
11322
11885
  async function appendUnroutedSpan(neatHome4, record) {
11323
- const target = import_node_path49.default.join(neatHome4, "errors.ndjson");
11886
+ const target = import_node_path50.default.join(neatHome4, "errors.ndjson");
11324
11887
  await import_node_fs30.promises.mkdir(neatHome4, { recursive: true });
11325
11888
  await import_node_fs30.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
11326
11889
  }
11327
11890
  function unroutedErrorsPath(neatHome4) {
11328
- return import_node_path49.default.join(neatHome4, "errors.ndjson");
11891
+ return import_node_path50.default.join(neatHome4, "errors.ndjson");
11329
11892
  }
11330
11893
 
11331
11894
  // src/daemon.ts
11332
- var import_types46 = require("@neat.is/types");
11895
+ var import_types47 = require("@neat.is/types");
11333
11896
  function daemonJsonPath(scanPath) {
11334
- return import_node_path50.default.join(scanPath, "neat-out", "daemon.json");
11897
+ return import_node_path51.default.join(scanPath, "neat-out", "daemon.json");
11335
11898
  }
11336
11899
  function daemonsDiscoveryDir(home) {
11337
11900
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
11338
- return import_node_path50.default.join(base, "daemons");
11901
+ return import_node_path51.default.join(base, "daemons");
11339
11902
  }
11340
11903
  function daemonDiscoveryPath(project, home) {
11341
- return import_node_path50.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
11904
+ return import_node_path51.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
11342
11905
  }
11343
11906
  function sanitizeDiscoveryName(project) {
11344
11907
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
11345
11908
  }
11346
11909
  function neatHomeFromEnv() {
11347
11910
  const env = process.env.NEAT_HOME;
11348
- if (env && env.length > 0) return import_node_path50.default.resolve(env);
11911
+ if (env && env.length > 0) return import_node_path51.default.resolve(env);
11349
11912
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
11350
- return import_node_path50.default.join(home, ".neat");
11913
+ return import_node_path51.default.join(home, ".neat");
11351
11914
  }
11352
11915
  function resolveNeatVersion() {
11353
11916
  if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
@@ -11416,11 +11979,11 @@ function teardownSlot(slot) {
11416
11979
  }
11417
11980
  }
11418
11981
  function neatHomeFor(opts) {
11419
- if (opts.neatHome && opts.neatHome.length > 0) return import_node_path50.default.resolve(opts.neatHome);
11982
+ if (opts.neatHome && opts.neatHome.length > 0) return import_node_path51.default.resolve(opts.neatHome);
11420
11983
  const env = process.env.NEAT_HOME;
11421
- if (env && env.length > 0) return import_node_path50.default.resolve(env);
11984
+ if (env && env.length > 0) return import_node_path51.default.resolve(env);
11422
11985
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
11423
- return import_node_path50.default.join(home, ".neat");
11986
+ return import_node_path51.default.join(home, ".neat");
11424
11987
  }
11425
11988
  function routeSpanToProject(serviceName, projects) {
11426
11989
  if (!serviceName) return DEFAULT_PROJECT;
@@ -11464,11 +12027,11 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
11464
12027
  if (!serviceName) return true;
11465
12028
  if (serviceNameMatchesProject(serviceName, project)) return true;
11466
12029
  return graph.someNode(
11467
- (_id, attrs) => attrs.type === import_types46.NodeType.ServiceNode && attrs.name === serviceName
12030
+ (_id, attrs) => attrs.type === import_types47.NodeType.ServiceNode && attrs.name === serviceName
11468
12031
  );
11469
12032
  }
11470
12033
  async function bootstrapProject(entry2, connectors = [], neatHome4) {
11471
- const paths = pathsForProject(entry2.name, import_node_path50.default.join(entry2.path, "neat-out"));
12034
+ const paths = pathsForProject(entry2.name, import_node_path51.default.join(entry2.path, "neat-out"));
11472
12035
  try {
11473
12036
  const stat = await import_node_fs31.promises.stat(entry2.path);
11474
12037
  if (!stat.isDirectory()) {
@@ -11599,7 +12162,7 @@ async function startDaemon(opts = {}) {
11599
12162
  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;
11600
12163
  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;
11601
12164
  const singleProject = projectArg;
11602
- const singleProjectPath = singleProject && projectPathArg ? import_node_path50.default.resolve(projectPathArg) : null;
12165
+ const singleProjectPath = singleProject && projectPathArg ? import_node_path51.default.resolve(projectPathArg) : null;
11603
12166
  if (singleProject && !singleProjectPath) {
11604
12167
  throw new Error(
11605
12168
  `neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
@@ -11614,7 +12177,7 @@ async function startDaemon(opts = {}) {
11614
12177
  );
11615
12178
  }
11616
12179
  }
11617
- const pidPath = import_node_path50.default.join(home, "neatd.pid");
12180
+ const pidPath = import_node_path51.default.join(home, "neatd.pid");
11618
12181
  await writeAtomically(pidPath, `${process.pid}
11619
12182
  `);
11620
12183
  const slots = /* @__PURE__ */ new Map();
@@ -12021,8 +12584,8 @@ async function startDaemon(opts = {}) {
12021
12584
  let registryWatcher = null;
12022
12585
  let reloadTimer = null;
12023
12586
  if (!singleProject) try {
12024
- const regDir = import_node_path50.default.dirname(regPath);
12025
- const regBase = import_node_path50.default.basename(regPath);
12587
+ const regDir = import_node_path51.default.dirname(regPath);
12588
+ const regBase = import_node_path51.default.basename(regPath);
12026
12589
  registryWatcher = (0, import_node_fs31.watch)(regDir, (_eventType, filename) => {
12027
12590
  if (filename !== null && filename !== regBase) return;
12028
12591
  if (reloadTimer) clearTimeout(reloadTimer);
@@ -12097,7 +12660,7 @@ init_cjs_shims();
12097
12660
  var import_node_child_process2 = require("child_process");
12098
12661
  var import_node_fs32 = require("fs");
12099
12662
  var import_node_net = __toESM(require("net"), 1);
12100
- var import_node_path51 = __toESM(require("path"), 1);
12663
+ var import_node_path52 = __toESM(require("path"), 1);
12101
12664
  var DEFAULT_WEB_PORT = 6328;
12102
12665
  var DEFAULT_REST_PORT = 8080;
12103
12666
  function asValidPort(value) {
@@ -12106,11 +12669,11 @@ function asValidPort(value) {
12106
12669
  }
12107
12670
  function projectRoot() {
12108
12671
  const fromEnv = process.env.NEAT_SCAN_PATH;
12109
- return import_node_path51.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
12672
+ return import_node_path52.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
12110
12673
  }
12111
12674
  async function readDaemonPorts(root) {
12112
12675
  try {
12113
- const raw = await import_node_fs32.promises.readFile(import_node_path51.default.join(root, "neat-out", "daemon.json"), "utf8");
12676
+ const raw = await import_node_fs32.promises.readFile(import_node_path52.default.join(root, "neat-out", "daemon.json"), "utf8");
12114
12677
  const parsed = JSON.parse(raw);
12115
12678
  const ports = parsed?.ports ?? {};
12116
12679
  return { web: asValidPort(ports.web), rest: asValidPort(ports.rest) };
@@ -12155,10 +12718,10 @@ function resolveWebPackageDir() {
12155
12718
  eval("require")
12156
12719
  );
12157
12720
  const pkgJsonPath = req.resolve("@neat.is/web/package.json");
12158
- return import_node_path51.default.dirname(pkgJsonPath);
12721
+ return import_node_path52.default.dirname(pkgJsonPath);
12159
12722
  }
12160
12723
  function resolveStandaloneServerEntry(webDir) {
12161
- return import_node_path51.default.join(webDir, ".next/standalone/packages/web/server.js");
12724
+ return import_node_path52.default.join(webDir, ".next/standalone/packages/web/server.js");
12162
12725
  }
12163
12726
  async function pickInternalPort() {
12164
12727
  return new Promise((resolve, reject) => {
@@ -12211,7 +12774,7 @@ async function spawnWebUI(restPort, opts = {}) {
12211
12774
  NEAT_API_URL: apiUrl
12212
12775
  };
12213
12776
  child = (0, import_node_child_process2.spawn)(process.execPath, [serverEntry], {
12214
- cwd: import_node_path51.default.dirname(serverEntry),
12777
+ cwd: import_node_path52.default.dirname(serverEntry),
12215
12778
  env,
12216
12779
  stdio: ["ignore", "inherit", "inherit"],
12217
12780
  detached: false
@@ -12387,14 +12950,14 @@ function localVersion() {
12387
12950
  }
12388
12951
  function neatHome3() {
12389
12952
  if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
12390
- return import_node_path52.default.resolve(process.env.NEAT_HOME);
12953
+ return import_node_path53.default.resolve(process.env.NEAT_HOME);
12391
12954
  }
12392
12955
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
12393
- return import_node_path52.default.join(home, ".neat");
12956
+ return import_node_path53.default.join(home, ".neat");
12394
12957
  }
12395
12958
  async function readPid() {
12396
12959
  try {
12397
- const raw = await import_node_fs33.promises.readFile(import_node_path52.default.join(neatHome3(), "neatd.pid"), "utf8");
12960
+ const raw = await import_node_fs33.promises.readFile(import_node_path53.default.join(neatHome3(), "neatd.pid"), "utf8");
12398
12961
  const n = Number.parseInt(raw.trim(), 10);
12399
12962
  return Number.isFinite(n) ? n : null;
12400
12963
  } catch {