@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/cli.cjs CHANGED
@@ -61,8 +61,8 @@ function mountBearerAuth(app, opts) {
61
61
  ]);
62
62
  const publicRead = opts.publicRead === true;
63
63
  app.addHook("preHandler", (req, reply, done) => {
64
- const path62 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
- if (exactUnauthPaths.has(path62) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path62)) {
64
+ const path63 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
+ if (exactUnauthPaths.has(path63) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path63)) {
66
66
  done();
67
67
  return;
68
68
  }
@@ -194,8 +194,8 @@ function reshapeGrpcRequest(req) {
194
194
  };
195
195
  }
196
196
  function resolveProtoRoot() {
197
- const here = import_node_path49.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
198
- return import_node_path49.default.resolve(here, "..", "proto");
197
+ const here = import_node_path50.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
198
+ return import_node_path50.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_url2, import_node_path49, import_node_crypto2, grpc, protoLoader;
266
+ var import_node_url2, import_node_path50, 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_url2 = require("url");
272
- import_node_path49 = __toESM(require("path"), 1);
272
+ import_node_path50 = __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 path62 = q === -1 ? v : v.slice(0, q);
366
- if (path62.length > 0) return path62;
370
+ const path63 = q === -1 ? v : v.slice(0, q);
371
+ if (path63.length > 0) return path63;
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_path50.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
421
- const protoRoot = import_node_path50.default.resolve(here, "..", "proto");
426
+ const here = import_node_path51.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
427
+ const protoRoot = import_node_path51.default.resolve(here, "..", "proto");
422
428
  const root = new import_protobufjs.default.Root();
423
- root.resolvePath = (_origin, target) => import_node_path50.default.resolve(protoRoot, target);
429
+ root.resolvePath = (_origin, target) => import_node_path51.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_path50, import_node_url3, 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_path51, import_node_url3, 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_path50 = __toESM(require("path"), 1);
662
+ import_node_path51 = __toESM(require("path"), 1);
657
663
  import_node_url3 = require("url");
658
664
  import_fastify2 = __toESM(require("fastify"), 1);
659
665
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -690,7 +696,7 @@ __export(cli_exports, {
690
696
  });
691
697
  module.exports = __toCommonJS(cli_exports);
692
698
  init_cjs_shims();
693
- var import_node_path61 = __toESM(require("path"), 1);
699
+ var import_node_path62 = __toESM(require("path"), 1);
694
700
  var import_node_fs41 = require("fs");
695
701
 
696
702
  // src/banner.ts
@@ -1256,19 +1262,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1256
1262
  function longestIncomingWalk(graph, start, maxDepth) {
1257
1263
  let best = { path: [start], edges: [] };
1258
1264
  const visited = /* @__PURE__ */ new Set([start]);
1259
- function step(node, path62, edges) {
1260
- if (path62.length > best.path.length) {
1261
- best = { path: [...path62], edges: [...edges] };
1265
+ function step(node, path63, edges) {
1266
+ if (path63.length > best.path.length) {
1267
+ best = { path: [...path63], edges: [...edges] };
1262
1268
  }
1263
- if (path62.length - 1 >= maxDepth) return;
1269
+ if (path63.length - 1 >= maxDepth) return;
1264
1270
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1265
1271
  for (const [srcId, edge] of incoming) {
1266
1272
  if (visited.has(srcId)) continue;
1267
1273
  visited.add(srcId);
1268
- path62.push(srcId);
1274
+ path63.push(srcId);
1269
1275
  edges.push(edge);
1270
- step(srcId, path62, edges);
1271
- path62.pop();
1276
+ step(srcId, path63, edges);
1277
+ path63.pop();
1272
1278
  edges.pop();
1273
1279
  visited.delete(srcId);
1274
1280
  }
@@ -1462,26 +1468,26 @@ function dominantFailingCall(graph, serviceId6, visited) {
1462
1468
  return best;
1463
1469
  }
1464
1470
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1465
- const path62 = [originServiceId];
1471
+ const path63 = [originServiceId];
1466
1472
  const edges = [];
1467
1473
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1468
1474
  let current = originServiceId;
1469
1475
  for (let depth = 0; depth < maxDepth; depth++) {
1470
1476
  const hop = dominantFailingCall(graph, current, visited);
1471
1477
  if (!hop) break;
1472
- path62.push(hop.nextService);
1478
+ path63.push(hop.nextService);
1473
1479
  edges.push(hop.edge);
1474
1480
  visited.add(hop.nextService);
1475
1481
  current = hop.nextService;
1476
1482
  }
1477
1483
  if (edges.length === 0) return null;
1478
- return { path: path62, edges, culprit: current };
1484
+ return { path: path63, edges, culprit: current };
1479
1485
  }
1480
1486
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1481
1487
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1482
1488
  if (!chain) return null;
1483
1489
  const culprit = chain.culprit;
1484
- const path62 = [...chain.path];
1490
+ const path63 = [...chain.path];
1485
1491
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1486
1492
  const baseConfidence = confidenceFromMix(chain.edges);
1487
1493
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1489,14 +1495,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1489
1495
  if (loc) {
1490
1496
  let rootCauseNode = culprit;
1491
1497
  if (loc.fileNode) {
1492
- path62.push(loc.fileNode);
1498
+ path63.push(loc.fileNode);
1493
1499
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1494
1500
  rootCauseNode = loc.fileNode;
1495
1501
  }
1496
1502
  return import_types.RootCauseResultSchema.parse({
1497
1503
  rootCauseNode,
1498
1504
  rootCauseReason: loc.rootCauseReason,
1499
- traversalPath: path62,
1505
+ traversalPath: path63,
1500
1506
  edgeProvenances,
1501
1507
  confidence,
1502
1508
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1508,7 +1514,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1508
1514
  return import_types.RootCauseResultSchema.parse({
1509
1515
  rootCauseNode: culprit,
1510
1516
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1511
- traversalPath: path62,
1517
+ traversalPath: path63,
1512
1518
  edgeProvenances,
1513
1519
  confidence,
1514
1520
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2585,6 +2591,19 @@ function ensureServiceNode(graph, serviceName, env) {
2585
2591
  graph.addNode(id, node);
2586
2592
  return id;
2587
2593
  }
2594
+ function ensureInfraNode(graph, kind, name, provider) {
2595
+ const id = (0, import_types3.infraId)(kind, name);
2596
+ if (graph.hasNode(id)) return id;
2597
+ const node = {
2598
+ id,
2599
+ type: import_types3.NodeType.InfraNode,
2600
+ name,
2601
+ provider,
2602
+ kind
2603
+ };
2604
+ graph.addNode(id, node);
2605
+ return id;
2606
+ }
2588
2607
  function ensureDatabaseNode(graph, host, engine) {
2589
2608
  const id = (0, import_types3.databaseId)(host);
2590
2609
  if (graph.hasNode(id)) return id;
@@ -2922,6 +2941,18 @@ async function handleSpan(ctx, span) {
2922
2941
  callSiteEvidence
2923
2942
  );
2924
2943
  if (result) affectedNode = targetId;
2944
+ if (span.dbSystem === "mongodb" && span.dbCollection) {
2945
+ const collectionId = ensureInfraNode(ctx.graph, "mongodb-collection", span.dbCollection, "self");
2946
+ upsertObservedEdge(
2947
+ ctx.graph,
2948
+ import_types3.EdgeType.CALLS,
2949
+ observedSource(),
2950
+ collectionId,
2951
+ ts,
2952
+ isError,
2953
+ callSiteEvidence
2954
+ );
2955
+ }
2925
2956
  }
2926
2957
  } else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
2927
2958
  const targetId = ensureMessagingDestinationNode(
@@ -5781,7 +5812,7 @@ async function addGrpcMethods(graph, services) {
5781
5812
 
5782
5813
  // src/extract/calls/index.ts
5783
5814
  init_cjs_shims();
5784
- var import_types20 = require("@neat.is/types");
5815
+ var import_types21 = require("@neat.is/types");
5785
5816
 
5786
5817
  // src/extract/calls/http.ts
5787
5818
  init_cjs_shims();
@@ -6494,15 +6525,311 @@ function supabaseEndpointsFromFile(file, serviceDir) {
6494
6525
  return out;
6495
6526
  }
6496
6527
 
6528
+ // src/extract/calls/mongoose.ts
6529
+ init_cjs_shims();
6530
+ var import_node_path32 = __toESM(require("path"), 1);
6531
+ var import_types20 = require("@neat.is/types");
6532
+ var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
6533
+ var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
6534
+ var PLURALIZE_DISABLED_RE = /\bpluralize\s*\(\s*(?:null|false)\s*\)/;
6535
+ var UNCOUNTABLES = /* @__PURE__ */ new Set([
6536
+ "advice",
6537
+ "energy",
6538
+ "excretion",
6539
+ "digestion",
6540
+ "cooperation",
6541
+ "health",
6542
+ "justice",
6543
+ "labour",
6544
+ "machinery",
6545
+ "equipment",
6546
+ "information",
6547
+ "pollution",
6548
+ "sewage",
6549
+ "paper",
6550
+ "money",
6551
+ "species",
6552
+ "series",
6553
+ "rain",
6554
+ "rice",
6555
+ "fish",
6556
+ "sheep",
6557
+ "moose",
6558
+ "deer",
6559
+ "news",
6560
+ "expertise",
6561
+ "status",
6562
+ "media"
6563
+ ]);
6564
+ var PLURALIZE_RULES = [
6565
+ [/human$/gi, "humans"],
6566
+ [/(m)an$/gi, "$1en"],
6567
+ [/(pe)rson$/gi, "$1ople"],
6568
+ [/(child)$/gi, "$1ren"],
6569
+ [/^(ox)$/gi, "$1en"],
6570
+ [/(ax|test)is$/gi, "$1es"],
6571
+ [/(octop|vir)us$/gi, "$1i"],
6572
+ [/(alias|status)$/gi, "$1es"],
6573
+ [/(bu)s$/gi, "$1ses"],
6574
+ [/(buffal|tomat|potat)o$/gi, "$1oes"],
6575
+ [/([ti])um$/gi, "$1a"],
6576
+ [/sis$/gi, "ses"],
6577
+ [/(?:([^f])fe|([lr])f)$/gi, "$1$2ves"],
6578
+ [/(hive)$/gi, "$1s"],
6579
+ [/([^aeiouy]|qu)y$/gi, "$1ies"],
6580
+ [/(x|ch|ss|sh)$/gi, "$1es"],
6581
+ [/(matr|vert|ind)ix|ex$/gi, "$1ices"],
6582
+ [/([m|l])ouse$/gi, "$1ice"],
6583
+ [/(kn|w|l)ife$/gi, "$1ives"],
6584
+ [/(quiz)$/gi, "$1zes"],
6585
+ [/s$/gi, "s"],
6586
+ [/([^a-z])$/, "$1"],
6587
+ [/$/gi, "s"]
6588
+ ];
6589
+ function pluralizeCollection(name) {
6590
+ const str = name.toLowerCase();
6591
+ if (UNCOUNTABLES.has(str)) return str;
6592
+ for (const [re, repl] of PLURALIZE_RULES) {
6593
+ if (str.match(re)) return str.replace(re, repl);
6594
+ }
6595
+ return str;
6596
+ }
6597
+ var MODEL_METHODS = [
6598
+ "find",
6599
+ "findOne",
6600
+ "findById",
6601
+ "findByIdAndUpdate",
6602
+ "findByIdAndDelete",
6603
+ "findOneAndUpdate",
6604
+ "findOneAndDelete",
6605
+ "findOneAndReplace",
6606
+ "countDocuments",
6607
+ "estimatedDocumentCount",
6608
+ "distinct",
6609
+ "aggregate",
6610
+ "exists",
6611
+ "where",
6612
+ "populate",
6613
+ "watch",
6614
+ "hydrate",
6615
+ "create",
6616
+ "insertMany",
6617
+ "insertOne",
6618
+ "updateOne",
6619
+ "updateMany",
6620
+ "replaceOne",
6621
+ "deleteOne",
6622
+ "deleteMany",
6623
+ "bulkWrite",
6624
+ "bulkSave",
6625
+ "save",
6626
+ // pre-v7, removed in current mongoose but present in older codebases:
6627
+ "count",
6628
+ "update",
6629
+ "remove",
6630
+ "findOneAndRemove",
6631
+ "findByIdAndRemove"
6632
+ ];
6633
+ var MODEL_METHODS_ALT = MODEL_METHODS.join("|");
6634
+ function schemaCollectionVars(content) {
6635
+ const out = /* @__PURE__ */ new Map();
6636
+ const re = /(?:const|let|var)\s+(\w+)\s*=\s*new\s+(?:mongoose\s*\.\s*)?Schema\s*\(/g;
6637
+ let m;
6638
+ while ((m = re.exec(content)) !== null) {
6639
+ const windowText = content.slice(m.index, m.index + 2e3);
6640
+ const opt = /\bcollection\s*:\s*['"`]([\w.-]+)['"`]/.exec(windowText);
6641
+ if (opt) out.set(m[1], opt[1]);
6642
+ }
6643
+ return out;
6644
+ }
6645
+ function resolveModel(modelName, rest, schemaColl, pluralizeOn) {
6646
+ const trimmed = rest.trim();
6647
+ const literalThird = /,\s*['"`]([\w.-]+)['"`]\s*$/.exec(trimmed);
6648
+ if (literalThird) return { name: literalThird[1], kind: "mongodb-collection" };
6649
+ const firstIdent = /^([A-Za-z_$][\w$]*)/.exec(trimmed)?.[1];
6650
+ if (firstIdent && schemaColl.has(firstIdent)) {
6651
+ return { name: schemaColl.get(firstIdent), kind: "mongodb-collection" };
6652
+ }
6653
+ if (/,\s*[A-Za-z_$][\w$]*\s*$/.test(trimmed)) {
6654
+ return { name: modelName, kind: "mongodb-model" };
6655
+ }
6656
+ return { name: pluralizeOn ? pluralizeCollection(modelName) : modelName, kind: "mongodb-collection" };
6657
+ }
6658
+ function collectModelDefs(content, pluralizeOn) {
6659
+ const schemaColl = schemaCollectionVars(content);
6660
+ const out = [];
6661
+ 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;
6662
+ let m;
6663
+ while ((m = re.exec(content)) !== null) {
6664
+ out.push({
6665
+ modelName: m[2],
6666
+ varName: m[1] ?? null,
6667
+ resolved: resolveModel(m[2], m[3] ?? "", schemaColl, pluralizeOn),
6668
+ matchText: m[0]
6669
+ });
6670
+ }
6671
+ return out;
6672
+ }
6673
+ function endpoint(r, file, serviceDir, matchText) {
6674
+ const line = lineOf(file.content, matchText);
6675
+ return {
6676
+ infraId: (0, import_types20.infraId)(r.kind, r.name),
6677
+ name: r.name,
6678
+ kind: r.kind,
6679
+ edgeType: "CALLS",
6680
+ confidenceKind: "verified-call-site",
6681
+ evidence: { file: import_node_path32.default.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
6682
+ };
6683
+ }
6684
+ function mongooseEndpointsFromFile(file, serviceDir) {
6685
+ const hasMongoose = MONGOOSE_IMPORT_RE.test(file.content);
6686
+ const hasMongodb = MONGODB_IMPORT_RE.test(file.content);
6687
+ if (!hasMongoose && !hasMongodb) return [];
6688
+ const content = file.content;
6689
+ const out = [];
6690
+ const seen = /* @__PURE__ */ new Set();
6691
+ const push = (r, matchText) => {
6692
+ const key = `${r.kind}/${r.name}`;
6693
+ if (seen.has(key)) return;
6694
+ seen.add(key);
6695
+ out.push(endpoint(r, file, serviceDir, matchText));
6696
+ };
6697
+ if (hasMongoose) {
6698
+ const pluralizeOn = !PLURALIZE_DISABLED_RE.test(content);
6699
+ for (const def of collectModelDefs(content, pluralizeOn)) push(def.resolved, def.matchText);
6700
+ }
6701
+ const collRe = /\.\s*collection\s*\(\s*['"`]([\w.$-]+)['"`]\s*\)/g;
6702
+ let c;
6703
+ while ((c = collRe.exec(content)) !== null) {
6704
+ push({ name: c[1], kind: "mongodb-collection" }, c[0]);
6705
+ }
6706
+ return out;
6707
+ }
6708
+ function parseDestructure(inner, specifier, out) {
6709
+ for (const raw of inner.split(",")) {
6710
+ const part = raw.trim();
6711
+ if (!part) continue;
6712
+ const asM = /^(\w+)\s+as\s+(\w+)$/.exec(part);
6713
+ const colonM = /^(\w+)\s*:\s*(\w+)$/.exec(part);
6714
+ if (asM) out.push({ local: asM[2], kind: "named", exportName: asM[1], specifier });
6715
+ else if (colonM) out.push({ local: colonM[2], kind: "named", exportName: colonM[1], specifier });
6716
+ else {
6717
+ const id = /^(\w+)$/.exec(part);
6718
+ if (id) out.push({ local: id[1], kind: "named", exportName: id[1], specifier });
6719
+ }
6720
+ }
6721
+ }
6722
+ function parseImportBindings(content) {
6723
+ const out = [];
6724
+ const esm = /import\s+([^;'"`\n]+?)\s+from\s*['"`]([^'"`]+)['"`]/g;
6725
+ let m;
6726
+ while ((m = esm.exec(content)) !== null) {
6727
+ const clause = m[1].trim();
6728
+ const spec = m[2];
6729
+ const ns = /^\*\s+as\s+(\w+)$/.exec(clause);
6730
+ if (ns) {
6731
+ out.push({ local: ns[1], kind: "namespace", specifier: spec });
6732
+ continue;
6733
+ }
6734
+ const named = /\{([^}]*)\}/.exec(clause);
6735
+ if (named) parseDestructure(named[1], spec, out);
6736
+ const def = /^(\w+)\s*(?:,|$)/.exec(clause);
6737
+ if (def && !clause.startsWith("{")) out.push({ local: def[1], kind: "default", specifier: spec });
6738
+ }
6739
+ const cjs = /(?:const|let|var)\s+(\{[^}]*\}|\w+)\s*=\s*require\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
6740
+ while ((m = cjs.exec(content)) !== null) {
6741
+ const lhs = m[1];
6742
+ const spec = m[2];
6743
+ if (lhs.startsWith("{")) parseDestructure(lhs.slice(1, -1), spec, out);
6744
+ else out.push({ local: lhs, kind: "whole", specifier: spec });
6745
+ }
6746
+ return out;
6747
+ }
6748
+ function fileExportsOf(content, pluralizeOn) {
6749
+ const defs = collectModelDefs(content, pluralizeOn).filter((d) => d.resolved.kind === "mongodb-collection");
6750
+ if (defs.length === 0) return null;
6751
+ const byVar = /* @__PURE__ */ new Map();
6752
+ for (const d of defs) if (d.varName) byVar.set(d.varName, d.resolved.name);
6753
+ const byName = new Map(byVar);
6754
+ let def;
6755
+ const named = /(?:module\.exports|export\s+default)\s*=\s*(\w+)\b/.exec(content);
6756
+ if (named && byVar.has(named[1])) def = byVar.get(named[1]);
6757
+ if (!def) {
6758
+ const inline = /(?:module\.exports|export\s+default)\s*=\s*(?:await\s+)?(?:\w+\s*\.\s*)?model\s*\(\s*['"`]([\w$]+)['"`]/.exec(content);
6759
+ if (inline) def = pluralizeOn ? pluralizeCollection(inline[1]) : inline[1];
6760
+ }
6761
+ if (!def && byVar.size === 1) def = [...byVar.values()][0];
6762
+ return { byName, ...def ? { default: def } : {} };
6763
+ }
6764
+ async function mongooseCrossFileEndpoints(files, serviceDir) {
6765
+ const mongooseFiles = files.filter((f) => MONGOOSE_IMPORT_RE.test(f.content));
6766
+ if (mongooseFiles.length === 0) return [];
6767
+ const pluralizeOn = !files.some((f) => PLURALIZE_DISABLED_RE.test(f.content));
6768
+ const registry = /* @__PURE__ */ new Map();
6769
+ for (const f of mongooseFiles) {
6770
+ const fx = fileExportsOf(f.content, pluralizeOn);
6771
+ if (fx) registry.set(toPosix2(import_node_path32.default.relative(serviceDir, f.path)), fx);
6772
+ }
6773
+ if (registry.size === 0) return [];
6774
+ const out = [];
6775
+ const seen = /* @__PURE__ */ new Set();
6776
+ for (const f of files) {
6777
+ const bindings = parseImportBindings(f.content);
6778
+ if (bindings.length === 0) continue;
6779
+ const directColl = /* @__PURE__ */ new Map();
6780
+ const nsExports = /* @__PURE__ */ new Map();
6781
+ for (const b of bindings) {
6782
+ const resolvedRel = await resolveJsImport(b.specifier, import_node_path32.default.dirname(f.path), serviceDir, null);
6783
+ if (!resolvedRel) continue;
6784
+ const fx = registry.get(resolvedRel);
6785
+ if (!fx) continue;
6786
+ if (b.kind === "named" && b.exportName) {
6787
+ const coll = fx.byName.get(b.exportName);
6788
+ if (coll) directColl.set(b.local, coll);
6789
+ } else if (b.kind === "default") {
6790
+ if (fx.default) directColl.set(b.local, fx.default);
6791
+ } else {
6792
+ if (fx.default) directColl.set(b.local, fx.default);
6793
+ nsExports.set(b.local, fx);
6794
+ }
6795
+ }
6796
+ if (directColl.size === 0 && nsExports.size === 0) continue;
6797
+ const localDefs = new Set(
6798
+ collectModelDefs(f.content, pluralizeOn).map((d) => d.varName).filter((v) => v !== null)
6799
+ );
6800
+ const emit = (collection, matchText) => {
6801
+ const key = `${f.path}::${collection}`;
6802
+ if (seen.has(key)) return;
6803
+ seen.add(key);
6804
+ out.push(endpoint({ name: collection, kind: "mongodb-collection" }, f, serviceDir, matchText));
6805
+ };
6806
+ for (const [local, collection] of directColl) {
6807
+ if (localDefs.has(local)) continue;
6808
+ const qre = new RegExp(`\\b${local}\\s*\\.\\s*(?:${MODEL_METHODS_ALT})\\s*\\(`, "g");
6809
+ let qm;
6810
+ while ((qm = qre.exec(f.content)) !== null) emit(collection, qm[0]);
6811
+ }
6812
+ for (const [local, fx] of nsExports) {
6813
+ const qre = new RegExp(`\\b${local}\\s*\\.\\s*(\\w+)\\s*\\.\\s*(?:${MODEL_METHODS_ALT})\\s*\\(`, "g");
6814
+ let qm;
6815
+ while ((qm = qre.exec(f.content)) !== null) {
6816
+ const coll = fx.byName.get(qm[1]);
6817
+ if (coll) emit(coll, qm[0]);
6818
+ }
6819
+ }
6820
+ }
6821
+ return out;
6822
+ }
6823
+
6497
6824
  // src/extract/calls/index.ts
6498
6825
  function edgeTypeFromEndpoint(ep) {
6499
6826
  switch (ep.edgeType) {
6500
6827
  case "PUBLISHES_TO":
6501
- return import_types20.EdgeType.PUBLISHES_TO;
6828
+ return import_types21.EdgeType.PUBLISHES_TO;
6502
6829
  case "CONSUMES_FROM":
6503
- return import_types20.EdgeType.CONSUMES_FROM;
6830
+ return import_types21.EdgeType.CONSUMES_FROM;
6504
6831
  default:
6505
- return import_types20.EdgeType.CALLS;
6832
+ return import_types21.EdgeType.CALLS;
6506
6833
  }
6507
6834
  }
6508
6835
  function isAwsKind(kind) {
@@ -6514,23 +6841,27 @@ async function addExternalEndpointEdges(graph, services) {
6514
6841
  for (const service of services) {
6515
6842
  const files = await loadSourceFiles(service.dir);
6516
6843
  const endpoints = [];
6844
+ const maskedFiles = [];
6517
6845
  for (const file of files) {
6518
6846
  if (isTestPath(file.path)) continue;
6519
6847
  const masked = maskCommentsInSource(file.content);
6520
6848
  const maskedFile = { path: file.path, content: masked };
6849
+ maskedFiles.push(maskedFile);
6521
6850
  endpoints.push(...kafkaEndpointsFromFile(maskedFile, service.dir));
6522
6851
  endpoints.push(...redisEndpointsFromFile(maskedFile, service.dir));
6523
6852
  endpoints.push(...awsEndpointsFromFile(maskedFile, service.dir));
6524
6853
  endpoints.push(...grpcEndpointsFromFile(maskedFile, service.dir));
6525
6854
  endpoints.push(...supabaseEndpointsFromFile(maskedFile, service.dir));
6855
+ endpoints.push(...mongooseEndpointsFromFile(maskedFile, service.dir));
6526
6856
  }
6857
+ endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
6527
6858
  if (endpoints.length === 0) continue;
6528
6859
  const seenEdges = /* @__PURE__ */ new Set();
6529
6860
  for (const ep of endpoints) {
6530
6861
  if (!graph.hasNode(ep.infraId)) {
6531
6862
  const node = {
6532
6863
  id: ep.infraId,
6533
- type: import_types20.NodeType.InfraNode,
6864
+ type: import_types21.NodeType.InfraNode,
6534
6865
  name: ep.name,
6535
6866
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
6536
6867
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -6542,7 +6873,7 @@ async function addExternalEndpointEdges(graph, services) {
6542
6873
  nodesAdded++;
6543
6874
  }
6544
6875
  const edgeType = edgeTypeFromEndpoint(ep);
6545
- const confidence = (0, import_types20.confidenceForExtracted)(ep.confidenceKind);
6876
+ const confidence = (0, import_types21.confidenceForExtracted)(ep.confidenceKind);
6546
6877
  const relFile = toPosix2(ep.evidence.file);
6547
6878
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
6548
6879
  graph,
@@ -6552,7 +6883,7 @@ async function addExternalEndpointEdges(graph, services) {
6552
6883
  );
6553
6884
  nodesAdded += n;
6554
6885
  edgesAdded += e;
6555
- if (!(0, import_types20.passesExtractedFloor)(confidence)) {
6886
+ if (!(0, import_types21.passesExtractedFloor)(confidence)) {
6556
6887
  noteExtractedDropped({
6557
6888
  source: fileNodeId,
6558
6889
  target: ep.infraId,
@@ -6572,7 +6903,7 @@ async function addExternalEndpointEdges(graph, services) {
6572
6903
  source: fileNodeId,
6573
6904
  target: ep.infraId,
6574
6905
  type: edgeType,
6575
- provenance: import_types20.Provenance.EXTRACTED,
6906
+ provenance: import_types21.Provenance.EXTRACTED,
6576
6907
  confidence,
6577
6908
  evidence: ep.evidence
6578
6909
  };
@@ -6598,16 +6929,16 @@ init_cjs_shims();
6598
6929
 
6599
6930
  // src/extract/infra/docker-compose.ts
6600
6931
  init_cjs_shims();
6601
- var import_node_path32 = __toESM(require("path"), 1);
6602
- var import_types22 = require("@neat.is/types");
6932
+ var import_node_path33 = __toESM(require("path"), 1);
6933
+ var import_types23 = require("@neat.is/types");
6603
6934
 
6604
6935
  // src/extract/infra/shared.ts
6605
6936
  init_cjs_shims();
6606
- var import_types21 = require("@neat.is/types");
6937
+ var import_types22 = require("@neat.is/types");
6607
6938
  function makeInfraNode(kind, name, provider = "self", extras) {
6608
6939
  return {
6609
- id: (0, import_types21.infraId)(kind, name),
6610
- type: import_types21.NodeType.InfraNode,
6940
+ id: (0, import_types22.infraId)(kind, name),
6941
+ type: import_types22.NodeType.InfraNode,
6611
6942
  name,
6612
6943
  provider,
6613
6944
  kind,
@@ -6651,8 +6982,8 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
6651
6982
  source: anchorId,
6652
6983
  target: node.id,
6653
6984
  type: edgeType,
6654
- provenance: import_types21.Provenance.EXTRACTED,
6655
- confidence: (0, import_types21.confidenceForExtracted)("structural"),
6985
+ provenance: import_types22.Provenance.EXTRACTED,
6986
+ confidence: (0, import_types22.confidenceForExtracted)("structural"),
6656
6987
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
6657
6988
  };
6658
6989
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6669,7 +7000,7 @@ function dependsOnList(value) {
6669
7000
  }
6670
7001
  function serviceNameToServiceNode(name, services) {
6671
7002
  for (const s of services) {
6672
- if (s.node.name === name || import_node_path32.default.basename(s.dir) === name) return s.node.id;
7003
+ if (s.node.name === name || import_node_path33.default.basename(s.dir) === name) return s.node.id;
6673
7004
  }
6674
7005
  return null;
6675
7006
  }
@@ -6678,7 +7009,7 @@ async function addComposeInfra(graph, scanPath, services) {
6678
7009
  let edgesAdded = 0;
6679
7010
  let composePath = null;
6680
7011
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
6681
- const abs = import_node_path32.default.join(scanPath, name);
7012
+ const abs = import_node_path33.default.join(scanPath, name);
6682
7013
  if (await exists(abs)) {
6683
7014
  composePath = abs;
6684
7015
  break;
@@ -6691,13 +7022,13 @@ async function addComposeInfra(graph, scanPath, services) {
6691
7022
  } catch (err) {
6692
7023
  recordExtractionError(
6693
7024
  "infra docker-compose",
6694
- import_node_path32.default.relative(scanPath, composePath),
7025
+ import_node_path33.default.relative(scanPath, composePath),
6695
7026
  err
6696
7027
  );
6697
7028
  return { nodesAdded, edgesAdded };
6698
7029
  }
6699
7030
  if (!compose?.services) return { nodesAdded, edgesAdded };
6700
- const evidenceFile = import_node_path32.default.relative(scanPath, composePath).split(import_node_path32.default.sep).join("/");
7031
+ const evidenceFile = import_node_path33.default.relative(scanPath, composePath).split(import_node_path33.default.sep).join("/");
6701
7032
  const composeNameToNodeId = /* @__PURE__ */ new Map();
6702
7033
  for (const [composeName, svc] of Object.entries(compose.services)) {
6703
7034
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -6719,15 +7050,15 @@ async function addComposeInfra(graph, scanPath, services) {
6719
7050
  for (const dep of dependsOnList(svc.depends_on)) {
6720
7051
  const targetId = composeNameToNodeId.get(dep);
6721
7052
  if (!targetId) continue;
6722
- const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types22.EdgeType.DEPENDS_ON);
7053
+ const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types23.EdgeType.DEPENDS_ON);
6723
7054
  if (graph.hasEdge(edgeId)) continue;
6724
7055
  const edge = {
6725
7056
  id: edgeId,
6726
7057
  source: sourceId,
6727
7058
  target: targetId,
6728
- type: import_types22.EdgeType.DEPENDS_ON,
6729
- provenance: import_types22.Provenance.EXTRACTED,
6730
- confidence: (0, import_types22.confidenceForExtracted)("structural"),
7059
+ type: import_types23.EdgeType.DEPENDS_ON,
7060
+ provenance: import_types23.Provenance.EXTRACTED,
7061
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
6731
7062
  evidence: { file: evidenceFile }
6732
7063
  };
6733
7064
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6739,9 +7070,9 @@ async function addComposeInfra(graph, scanPath, services) {
6739
7070
 
6740
7071
  // src/extract/infra/dockerfile.ts
6741
7072
  init_cjs_shims();
6742
- var import_node_path33 = __toESM(require("path"), 1);
7073
+ var import_node_path34 = __toESM(require("path"), 1);
6743
7074
  var import_node_fs17 = require("fs");
6744
- var import_types23 = require("@neat.is/types");
7075
+ var import_types24 = require("@neat.is/types");
6745
7076
  function readDockerfile(content) {
6746
7077
  let image = null;
6747
7078
  const ports = [];
@@ -6770,7 +7101,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6770
7101
  let nodesAdded = 0;
6771
7102
  let edgesAdded = 0;
6772
7103
  for (const service of services) {
6773
- const dockerfilePath = import_node_path33.default.join(service.dir, "Dockerfile");
7104
+ const dockerfilePath = import_node_path34.default.join(service.dir, "Dockerfile");
6774
7105
  if (!await exists(dockerfilePath)) continue;
6775
7106
  let content;
6776
7107
  try {
@@ -6778,7 +7109,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6778
7109
  } catch (err) {
6779
7110
  recordExtractionError(
6780
7111
  "infra dockerfile",
6781
- import_node_path33.default.relative(scanPath, dockerfilePath),
7112
+ import_node_path34.default.relative(scanPath, dockerfilePath),
6782
7113
  err
6783
7114
  );
6784
7115
  continue;
@@ -6790,8 +7121,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6790
7121
  graph.addNode(node.id, node);
6791
7122
  nodesAdded++;
6792
7123
  }
6793
- const relDockerfile = toPosix2(import_node_path33.default.relative(service.dir, dockerfilePath));
6794
- const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, dockerfilePath));
7124
+ const relDockerfile = toPosix2(import_node_path34.default.relative(service.dir, dockerfilePath));
7125
+ const evidenceFile = toPosix2(import_node_path34.default.relative(scanPath, dockerfilePath));
6795
7126
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
6796
7127
  graph,
6797
7128
  service.pkg.name,
@@ -6800,15 +7131,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6800
7131
  );
6801
7132
  nodesAdded += fn;
6802
7133
  edgesAdded += fe;
6803
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types23.EdgeType.RUNS_ON);
7134
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types24.EdgeType.RUNS_ON);
6804
7135
  if (!graph.hasEdge(edgeId)) {
6805
7136
  const edge = {
6806
7137
  id: edgeId,
6807
7138
  source: fileNodeId,
6808
7139
  target: node.id,
6809
- type: import_types23.EdgeType.RUNS_ON,
6810
- provenance: import_types23.Provenance.EXTRACTED,
6811
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
7140
+ type: import_types24.EdgeType.RUNS_ON,
7141
+ provenance: import_types24.Provenance.EXTRACTED,
7142
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
6812
7143
  evidence: {
6813
7144
  file: evidenceFile,
6814
7145
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -6823,15 +7154,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6823
7154
  graph.addNode(portNode.id, portNode);
6824
7155
  nodesAdded++;
6825
7156
  }
6826
- const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types23.EdgeType.CONNECTS_TO);
7157
+ const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types24.EdgeType.CONNECTS_TO);
6827
7158
  if (graph.hasEdge(portEdgeId)) continue;
6828
7159
  const portEdge = {
6829
7160
  id: portEdgeId,
6830
7161
  source: fileNodeId,
6831
7162
  target: portNode.id,
6832
- type: import_types23.EdgeType.CONNECTS_TO,
6833
- provenance: import_types23.Provenance.EXTRACTED,
6834
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
7163
+ type: import_types24.EdgeType.CONNECTS_TO,
7164
+ provenance: import_types24.Provenance.EXTRACTED,
7165
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
6835
7166
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
6836
7167
  };
6837
7168
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -6844,8 +7175,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6844
7175
  // src/extract/infra/terraform.ts
6845
7176
  init_cjs_shims();
6846
7177
  var import_node_fs18 = require("fs");
6847
- var import_node_path34 = __toESM(require("path"), 1);
6848
- var import_types24 = require("@neat.is/types");
7178
+ var import_node_path35 = __toESM(require("path"), 1);
7179
+ var import_types25 = require("@neat.is/types");
6849
7180
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
6850
7181
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
6851
7182
  async function walkTfFiles(start, depth = 0, max = 5) {
@@ -6855,11 +7186,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
6855
7186
  for (const entry2 of entries) {
6856
7187
  if (entry2.isDirectory()) {
6857
7188
  if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
6858
- const child = import_node_path34.default.join(start, entry2.name);
7189
+ const child = import_node_path35.default.join(start, entry2.name);
6859
7190
  if (await isPythonVenvDir(child)) continue;
6860
7191
  out.push(...await walkTfFiles(child, depth + 1, max));
6861
7192
  } else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
6862
- out.push(import_node_path34.default.join(start, entry2.name));
7193
+ out.push(import_node_path35.default.join(start, entry2.name));
6863
7194
  }
6864
7195
  }
6865
7196
  return out;
@@ -6891,7 +7222,7 @@ async function addTerraformResources(graph, scanPath) {
6891
7222
  const files = await walkTfFiles(scanPath);
6892
7223
  for (const file of files) {
6893
7224
  const content = await import_node_fs18.promises.readFile(file, "utf8");
6894
- const evidenceFile = toPosix2(import_node_path34.default.relative(scanPath, file));
7225
+ const evidenceFile = toPosix2(import_node_path35.default.relative(scanPath, file));
6895
7226
  const resources = [];
6896
7227
  const byKey = /* @__PURE__ */ new Map();
6897
7228
  RESOURCE_RE.lastIndex = 0;
@@ -6926,16 +7257,16 @@ async function addTerraformResources(graph, scanPath) {
6926
7257
  if (!target) continue;
6927
7258
  if (seen.has(target.nodeId)) continue;
6928
7259
  seen.add(target.nodeId);
6929
- const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types24.EdgeType.DEPENDS_ON);
7260
+ const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types25.EdgeType.DEPENDS_ON);
6930
7261
  if (graph.hasEdge(edgeId)) continue;
6931
7262
  const line = lineAt2(content, resource.bodyOffset + ref.index);
6932
7263
  const edge = {
6933
7264
  id: edgeId,
6934
7265
  source: resource.nodeId,
6935
7266
  target: target.nodeId,
6936
- type: import_types24.EdgeType.DEPENDS_ON,
6937
- provenance: import_types24.Provenance.EXTRACTED,
6938
- confidence: (0, import_types24.confidenceForExtracted)("structural"),
7267
+ type: import_types25.EdgeType.DEPENDS_ON,
7268
+ provenance: import_types25.Provenance.EXTRACTED,
7269
+ confidence: (0, import_types25.confidenceForExtracted)("structural"),
6939
7270
  evidence: { file: evidenceFile, line, snippet: key }
6940
7271
  };
6941
7272
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6949,7 +7280,7 @@ async function addTerraformResources(graph, scanPath) {
6949
7280
  // src/extract/infra/k8s.ts
6950
7281
  init_cjs_shims();
6951
7282
  var import_node_fs19 = require("fs");
6952
- var import_node_path35 = __toESM(require("path"), 1);
7283
+ var import_node_path36 = __toESM(require("path"), 1);
6953
7284
  var import_yaml3 = require("yaml");
6954
7285
  var K8S_KIND_TO_INFRA_KIND = {
6955
7286
  Service: "k8s-service",
@@ -6967,11 +7298,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
6967
7298
  for (const entry2 of entries) {
6968
7299
  if (entry2.isDirectory()) {
6969
7300
  if (IGNORED_DIRS.has(entry2.name)) continue;
6970
- const child = import_node_path35.default.join(start, entry2.name);
7301
+ const child = import_node_path36.default.join(start, entry2.name);
6971
7302
  if (await isPythonVenvDir(child)) continue;
6972
7303
  out.push(...await walkYamlFiles2(child, depth + 1, max));
6973
- } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path35.default.extname(entry2.name))) {
6974
- out.push(import_node_path35.default.join(start, entry2.name));
7304
+ } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path36.default.extname(entry2.name))) {
7305
+ out.push(import_node_path36.default.join(start, entry2.name));
6975
7306
  }
6976
7307
  }
6977
7308
  return out;
@@ -7005,13 +7336,13 @@ async function addK8sResources(graph, scanPath) {
7005
7336
  // src/extract/infra/cloudflare.ts
7006
7337
  init_cjs_shims();
7007
7338
  var import_node_fs20 = require("fs");
7008
- var import_node_path36 = __toESM(require("path"), 1);
7339
+ var import_node_path37 = __toESM(require("path"), 1);
7009
7340
  var import_smol_toml2 = require("smol-toml");
7010
- var import_types25 = require("@neat.is/types");
7341
+ var import_types26 = require("@neat.is/types");
7011
7342
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
7012
7343
  async function readWranglerConfig(dir) {
7013
7344
  for (const filename of WRANGLER_FILENAMES) {
7014
- const abs = import_node_path36.default.join(dir, filename);
7345
+ const abs = import_node_path37.default.join(dir, filename);
7015
7346
  if (!await exists(abs)) continue;
7016
7347
  const raw = await import_node_fs20.promises.readFile(abs, "utf8");
7017
7348
  const config = filename === "wrangler.toml" ? (0, import_smol_toml2.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -7055,8 +7386,8 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
7055
7386
  source: anchorId,
7056
7387
  target: node.id,
7057
7388
  type: edgeType,
7058
- provenance: import_types25.Provenance.EXTRACTED,
7059
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7389
+ provenance: import_types26.Provenance.EXTRACTED,
7390
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7060
7391
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
7061
7392
  };
7062
7393
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7074,11 +7405,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7074
7405
  try {
7075
7406
  read = await readWranglerConfig(service.dir);
7076
7407
  } catch (err) {
7077
- recordExtractionError("infra cloudflare", import_node_path36.default.relative(scanPath, service.dir), err);
7408
+ recordExtractionError("infra cloudflare", import_node_path37.default.relative(scanPath, service.dir), err);
7078
7409
  continue;
7079
7410
  }
7080
7411
  if (!read || !read.config.name) continue;
7081
- const evidenceFile = toPosix2(import_node_path36.default.relative(scanPath, import_node_path36.default.join(service.dir, read.relFile)));
7412
+ const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, read.relFile)));
7082
7413
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
7083
7414
  }
7084
7415
  for (const worker of discovered) {
@@ -7090,7 +7421,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7090
7421
  }
7091
7422
  let anchorId = service.node.id;
7092
7423
  if (config.main) {
7093
- const entryRelPath = toPosix2(import_node_path36.default.normalize(config.main));
7424
+ const entryRelPath = toPosix2(import_node_path37.default.normalize(config.main));
7094
7425
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
7095
7426
  graph,
7096
7427
  service.pkg.name,
@@ -7117,15 +7448,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7117
7448
  nodesAdded++;
7118
7449
  }
7119
7450
  if (runtimeNode.id !== anchorId) {
7120
- const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id, import_types25.EdgeType.RUNS_ON);
7451
+ const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id, import_types26.EdgeType.RUNS_ON);
7121
7452
  if (!graph.hasEdge(runsOnId)) {
7122
7453
  const edge = {
7123
7454
  id: runsOnId,
7124
7455
  source: anchorId,
7125
7456
  target: runtimeNode.id,
7126
- type: import_types25.EdgeType.RUNS_ON,
7127
- provenance: import_types25.Provenance.EXTRACTED,
7128
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7457
+ type: import_types26.EdgeType.RUNS_ON,
7458
+ provenance: import_types26.Provenance.EXTRACTED,
7459
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7129
7460
  evidence: {
7130
7461
  file: evidenceFile,
7131
7462
  ...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
@@ -7139,7 +7470,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7139
7470
  const result = addResourceEdge(
7140
7471
  graph,
7141
7472
  anchorId,
7142
- import_types25.EdgeType.CONNECTS_TO,
7473
+ import_types26.EdgeType.CONNECTS_TO,
7143
7474
  "cloudflare-route",
7144
7475
  route,
7145
7476
  evidenceFile,
@@ -7163,7 +7494,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7163
7494
  const result = addResourceEdge(
7164
7495
  graph,
7165
7496
  anchorId,
7166
- import_types25.EdgeType.DEPENDS_ON,
7497
+ import_types26.EdgeType.DEPENDS_ON,
7167
7498
  group.kind,
7168
7499
  name,
7169
7500
  evidenceFile,
@@ -7177,7 +7508,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7177
7508
  const result = addResourceEdge(
7178
7509
  graph,
7179
7510
  anchorId,
7180
- import_types25.EdgeType.DEPENDS_ON,
7511
+ import_types26.EdgeType.DEPENDS_ON,
7181
7512
  "cloudflare-cron",
7182
7513
  cron,
7183
7514
  evidenceFile,
@@ -7190,7 +7521,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7190
7521
  const result = addResourceEdge(
7191
7522
  graph,
7192
7523
  anchorId,
7193
- import_types25.EdgeType.DEPENDS_ON,
7524
+ import_types26.EdgeType.DEPENDS_ON,
7194
7525
  "cloudflare-env-var",
7195
7526
  varName,
7196
7527
  evidenceFile,
@@ -7203,15 +7534,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7203
7534
  if (!svc.service) continue;
7204
7535
  const target = workerIndex.get(svc.service);
7205
7536
  if (target && target.anchorId !== anchorId) {
7206
- const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId, import_types25.EdgeType.CALLS);
7537
+ const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId, import_types26.EdgeType.CALLS);
7207
7538
  if (!graph.hasEdge(edgeId)) {
7208
7539
  const edge = {
7209
7540
  id: edgeId,
7210
7541
  source: anchorId,
7211
7542
  target: target.anchorId,
7212
- type: import_types25.EdgeType.CALLS,
7213
- provenance: import_types25.Provenance.EXTRACTED,
7214
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7543
+ type: import_types26.EdgeType.CALLS,
7544
+ provenance: import_types26.Provenance.EXTRACTED,
7545
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7215
7546
  evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
7216
7547
  };
7217
7548
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7222,7 +7553,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7222
7553
  const result = addResourceEdge(
7223
7554
  graph,
7224
7555
  anchorId,
7225
- import_types25.EdgeType.DEPENDS_ON,
7556
+ import_types26.EdgeType.DEPENDS_ON,
7226
7557
  "cloudflare-service-binding",
7227
7558
  svc.service,
7228
7559
  evidenceFile,
@@ -7238,12 +7569,12 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7238
7569
  // src/extract/infra/vercel.ts
7239
7570
  init_cjs_shims();
7240
7571
  var import_node_fs21 = require("fs");
7241
- var import_node_path37 = __toESM(require("path"), 1);
7242
- var import_types26 = require("@neat.is/types");
7572
+ var import_node_path38 = __toESM(require("path"), 1);
7573
+ var import_types27 = require("@neat.is/types");
7243
7574
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
7244
7575
  async function readVercelConfig(dir) {
7245
7576
  for (const filename of VERCEL_CONFIG_FILENAMES) {
7246
- const abs = import_node_path37.default.join(dir, filename);
7577
+ const abs = import_node_path38.default.join(dir, filename);
7247
7578
  if (!await exists(abs)) continue;
7248
7579
  const raw = await import_node_fs21.promises.readFile(abs, "utf8");
7249
7580
  const config = JSON.parse(maskCommentsInSource(raw));
@@ -7252,7 +7583,7 @@ async function readVercelConfig(dir) {
7252
7583
  return null;
7253
7584
  }
7254
7585
  async function readLinkedProjectName(dir) {
7255
- const abs = import_node_path37.default.join(dir, ".vercel", "project.json");
7586
+ const abs = import_node_path38.default.join(dir, ".vercel", "project.json");
7256
7587
  if (!await exists(abs)) return void 0;
7257
7588
  const parsed = JSON.parse(await import_node_fs21.promises.readFile(abs, "utf8"));
7258
7589
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
@@ -7270,7 +7601,7 @@ async function addVercelServices(graph, services, scanPath) {
7270
7601
  read = await readVercelConfig(service.dir);
7271
7602
  projectName = await readLinkedProjectName(service.dir);
7272
7603
  } catch (err) {
7273
- recordExtractionError("infra vercel", import_node_path37.default.relative(scanPath, service.dir), err);
7604
+ recordExtractionError("infra vercel", import_node_path38.default.relative(scanPath, service.dir), err);
7274
7605
  continue;
7275
7606
  }
7276
7607
  if (!read && !projectName) continue;
@@ -7286,7 +7617,7 @@ async function addVercelServices(graph, services, scanPath) {
7286
7617
  const anchorId = service.node.id;
7287
7618
  if (!read) continue;
7288
7619
  const { config, relFile, raw } = read;
7289
- const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, relFile)));
7620
+ const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
7290
7621
  const add = (edgeType, kind, name) => {
7291
7622
  if (!name) return;
7292
7623
  const result = emitPlatformResourceEdge(
@@ -7302,12 +7633,12 @@ async function addVercelServices(graph, services, scanPath) {
7302
7633
  nodesAdded += result.nodesAdded;
7303
7634
  edgesAdded += result.edgesAdded;
7304
7635
  };
7305
- add(import_types26.EdgeType.RUNS_ON, "vercel", "vercel");
7306
- for (const cron of config.crons ?? []) add(import_types26.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
7307
- for (const varName of Object.keys(config.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7308
- for (const varName of Object.keys(config.build?.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7636
+ add(import_types27.EdgeType.RUNS_ON, "vercel", "vercel");
7637
+ for (const cron of config.crons ?? []) add(import_types27.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
7638
+ for (const varName of Object.keys(config.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7639
+ for (const varName of Object.keys(config.build?.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7309
7640
  for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
7310
- add(import_types26.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7641
+ add(import_types27.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7311
7642
  }
7312
7643
  }
7313
7644
  return { nodesAdded, edgesAdded };
@@ -7316,13 +7647,13 @@ async function addVercelServices(graph, services, scanPath) {
7316
7647
  // src/extract/infra/railway.ts
7317
7648
  init_cjs_shims();
7318
7649
  var import_node_fs22 = require("fs");
7319
- var import_node_path38 = __toESM(require("path"), 1);
7650
+ var import_node_path39 = __toESM(require("path"), 1);
7320
7651
  var import_smol_toml3 = require("smol-toml");
7321
- var import_types27 = require("@neat.is/types");
7652
+ var import_types28 = require("@neat.is/types");
7322
7653
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
7323
7654
  async function readRailwayConfig(dir) {
7324
7655
  for (const filename of RAILWAY_FILENAMES) {
7325
- const abs = import_node_path38.default.join(dir, filename);
7656
+ const abs = import_node_path39.default.join(dir, filename);
7326
7657
  if (!await exists(abs)) continue;
7327
7658
  const raw = await import_node_fs22.promises.readFile(abs, "utf8");
7328
7659
  const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -7338,7 +7669,7 @@ async function addRailwayServices(graph, services, scanPath) {
7338
7669
  try {
7339
7670
  read = await readRailwayConfig(service.dir);
7340
7671
  } catch (err) {
7341
- recordExtractionError("infra railway", import_node_path38.default.relative(scanPath, service.dir), err);
7672
+ recordExtractionError("infra railway", import_node_path39.default.relative(scanPath, service.dir), err);
7342
7673
  continue;
7343
7674
  }
7344
7675
  if (!read) continue;
@@ -7348,7 +7679,7 @@ async function addRailwayServices(graph, services, scanPath) {
7348
7679
  }
7349
7680
  const anchorId = service.node.id;
7350
7681
  const { config, relFile, raw } = read;
7351
- const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
7682
+ const evidenceFile = toPosix2(import_node_path39.default.relative(scanPath, import_node_path39.default.join(service.dir, relFile)));
7352
7683
  const add = (edgeType, kind, name) => {
7353
7684
  if (!name) return;
7354
7685
  const result = emitPlatformResourceEdge(
@@ -7364,9 +7695,9 @@ async function addRailwayServices(graph, services, scanPath) {
7364
7695
  nodesAdded += result.nodesAdded;
7365
7696
  edgesAdded += result.edgesAdded;
7366
7697
  };
7367
- add(import_types27.EdgeType.RUNS_ON, "railway", "railway");
7368
- add(import_types27.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
7369
- add(import_types27.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
7698
+ add(import_types28.EdgeType.RUNS_ON, "railway", "railway");
7699
+ add(import_types28.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
7700
+ add(import_types28.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
7370
7701
  }
7371
7702
  return { nodesAdded, edgesAdded };
7372
7703
  }
@@ -7374,12 +7705,12 @@ async function addRailwayServices(graph, services, scanPath) {
7374
7705
  // src/extract/infra/supabase.ts
7375
7706
  init_cjs_shims();
7376
7707
  var import_node_fs23 = require("fs");
7377
- var import_node_path39 = __toESM(require("path"), 1);
7708
+ var import_node_path40 = __toESM(require("path"), 1);
7378
7709
  var import_smol_toml4 = require("smol-toml");
7379
- var import_types28 = require("@neat.is/types");
7710
+ var import_types29 = require("@neat.is/types");
7380
7711
  async function readSupabaseConfig(dir) {
7381
- const relFile = import_node_path39.default.join("supabase", "config.toml");
7382
- const abs = import_node_path39.default.join(dir, relFile);
7712
+ const relFile = import_node_path40.default.join("supabase", "config.toml");
7713
+ const abs = import_node_path40.default.join(dir, relFile);
7383
7714
  if (!await exists(abs)) return null;
7384
7715
  const raw = await import_node_fs23.promises.readFile(abs, "utf8");
7385
7716
  const config = (0, import_smol_toml4.parse)(raw);
@@ -7393,7 +7724,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
7393
7724
  try {
7394
7725
  read = await readSupabaseConfig(service.dir);
7395
7726
  } catch (err) {
7396
- recordExtractionError("infra supabase", import_node_path39.default.relative(scanPath, service.dir), err);
7727
+ recordExtractionError("infra supabase", import_node_path40.default.relative(scanPath, service.dir), err);
7397
7728
  continue;
7398
7729
  }
7399
7730
  if (!read) continue;
@@ -7408,7 +7739,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
7408
7739
  });
7409
7740
  }
7410
7741
  const anchorId = service.node.id;
7411
- const evidenceFile = toPosix2(import_node_path39.default.relative(scanPath, import_node_path39.default.join(service.dir, relFile)));
7742
+ const evidenceFile = toPosix2(import_node_path40.default.relative(scanPath, import_node_path40.default.join(service.dir, relFile)));
7412
7743
  const add = (edgeType, kind, name) => {
7413
7744
  if (!name) return;
7414
7745
  const result = emitPlatformResourceEdge(
@@ -7424,10 +7755,10 @@ async function addSupabaseProjects(graph, services, scanPath) {
7424
7755
  nodesAdded += result.nodesAdded;
7425
7756
  edgesAdded += result.edgesAdded;
7426
7757
  };
7427
- add(import_types28.EdgeType.RUNS_ON, "supabase", "supabase");
7428
- for (const fn of Object.keys(config.functions ?? {})) add(import_types28.EdgeType.DEPENDS_ON, "supabase-function", fn);
7429
- if (config.storage) add(import_types28.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
7430
- if (config.auth) add(import_types28.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
7758
+ add(import_types29.EdgeType.RUNS_ON, "supabase", "supabase");
7759
+ for (const fn of Object.keys(config.functions ?? {})) add(import_types29.EdgeType.DEPENDS_ON, "supabase-function", fn);
7760
+ if (config.storage) add(import_types29.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
7761
+ if (config.auth) add(import_types29.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
7431
7762
  }
7432
7763
  return { nodesAdded, edgesAdded };
7433
7764
  }
@@ -7449,17 +7780,17 @@ async function addInfra(graph, scanPath, services) {
7449
7780
  }
7450
7781
 
7451
7782
  // src/extract/index.ts
7452
- var import_node_path41 = __toESM(require("path"), 1);
7783
+ var import_node_path42 = __toESM(require("path"), 1);
7453
7784
 
7454
7785
  // src/extract/retire.ts
7455
7786
  init_cjs_shims();
7456
7787
  var import_node_fs24 = require("fs");
7457
- var import_node_path40 = __toESM(require("path"), 1);
7458
- var import_types29 = require("@neat.is/types");
7788
+ var import_node_path41 = __toESM(require("path"), 1);
7789
+ var import_types30 = require("@neat.is/types");
7459
7790
  function dropOrphanedFileNodes(graph) {
7460
7791
  const orphans = [];
7461
7792
  graph.forEachNode((id, attrs) => {
7462
- if (attrs.type !== import_types29.NodeType.FileNode) return;
7793
+ if (attrs.type !== import_types30.NodeType.FileNode) return;
7463
7794
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
7464
7795
  orphans.push(id);
7465
7796
  }
@@ -7472,7 +7803,7 @@ function retireEdgesByFile(graph, file) {
7472
7803
  const toDrop = [];
7473
7804
  graph.forEachEdge((id, attrs) => {
7474
7805
  const edge = attrs;
7475
- if (edge.provenance !== import_types29.Provenance.EXTRACTED) return;
7806
+ if (edge.provenance !== import_types30.Provenance.EXTRACTED) return;
7476
7807
  if (!edge.evidence?.file) return;
7477
7808
  if (edge.evidence.file === normalized) toDrop.push(id);
7478
7809
  });
@@ -7485,14 +7816,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
7485
7816
  const bases = [scanPath, ...serviceDirs];
7486
7817
  graph.forEachEdge((id, attrs) => {
7487
7818
  const edge = attrs;
7488
- if (edge.provenance !== import_types29.Provenance.EXTRACTED) return;
7819
+ if (edge.provenance !== import_types30.Provenance.EXTRACTED) return;
7489
7820
  const evidenceFile = edge.evidence?.file;
7490
7821
  if (!evidenceFile) return;
7491
- if (import_node_path40.default.isAbsolute(evidenceFile)) {
7822
+ if (import_node_path41.default.isAbsolute(evidenceFile)) {
7492
7823
  if (!(0, import_node_fs24.existsSync)(evidenceFile)) toDrop.push(id);
7493
7824
  return;
7494
7825
  }
7495
- const found = bases.some((base) => (0, import_node_fs24.existsSync)(import_node_path40.default.join(base, evidenceFile)));
7826
+ const found = bases.some((base) => (0, import_node_fs24.existsSync)(import_node_path41.default.join(base, evidenceFile)));
7496
7827
  if (!found) toDrop.push(id);
7497
7828
  });
7498
7829
  for (const id of toDrop) graph.dropEdge(id);
@@ -7534,7 +7865,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7534
7865
  }
7535
7866
  const droppedEntries = drainDroppedExtracted();
7536
7867
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
7537
- const rejectedPath = import_node_path41.default.join(import_node_path41.default.dirname(opts.errorsPath), "rejected.ndjson");
7868
+ const rejectedPath = import_node_path42.default.join(import_node_path42.default.dirname(opts.errorsPath), "rejected.ndjson");
7538
7869
  try {
7539
7870
  await writeRejectedExtracted(droppedEntries, rejectedPath);
7540
7871
  } catch (err) {
@@ -7568,39 +7899,39 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7568
7899
 
7569
7900
  // src/divergences.ts
7570
7901
  init_cjs_shims();
7571
- var import_types30 = require("@neat.is/types");
7902
+ var import_types31 = require("@neat.is/types");
7572
7903
  function bucketKey(source, target, type) {
7573
7904
  return `${type}|${source}|${target}`;
7574
7905
  }
7575
7906
  function bucketSourceFor(graph, edge) {
7576
- if (edge.type !== import_types30.EdgeType.CONNECTS_TO) return edge.source;
7577
- const parsed = (0, import_types30.parseFileId)(edge.source);
7907
+ if (edge.type !== import_types31.EdgeType.CONNECTS_TO) return edge.source;
7908
+ const parsed = (0, import_types31.parseFileId)(edge.source);
7578
7909
  if (!parsed || !graph.hasNode(edge.target)) return edge.source;
7579
7910
  const target = graph.getNodeAttributes(edge.target);
7580
- if (target.type !== import_types30.NodeType.DatabaseNode) return edge.source;
7581
- return (0, import_types30.serviceId)(parsed.service);
7911
+ if (target.type !== import_types31.NodeType.DatabaseNode) return edge.source;
7912
+ return (0, import_types31.serviceId)(parsed.service);
7582
7913
  }
7583
7914
  function bucketEdges(graph) {
7584
7915
  const buckets2 = /* @__PURE__ */ new Map();
7585
7916
  graph.forEachEdge((id, attrs) => {
7586
7917
  const e = attrs;
7587
- const parsed = (0, import_types30.parseEdgeId)(id);
7918
+ const parsed = (0, import_types31.parseEdgeId)(id);
7588
7919
  const provenance = parsed?.provenance ?? e.provenance;
7589
7920
  const source = bucketSourceFor(graph, e);
7590
7921
  const key = bucketKey(source, e.target, e.type);
7591
7922
  const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
7592
7923
  switch (provenance) {
7593
- case import_types30.Provenance.EXTRACTED:
7924
+ case import_types31.Provenance.EXTRACTED:
7594
7925
  cur.extracted = e;
7595
7926
  break;
7596
- case import_types30.Provenance.OBSERVED:
7927
+ case import_types31.Provenance.OBSERVED:
7597
7928
  cur.observed = e;
7598
7929
  break;
7599
- case import_types30.Provenance.INFERRED:
7930
+ case import_types31.Provenance.INFERRED:
7600
7931
  cur.inferred = e;
7601
7932
  break;
7602
7933
  default:
7603
- if (e.provenance === import_types30.Provenance.STALE) cur.stale = e;
7934
+ if (e.provenance === import_types31.Provenance.STALE) cur.stale = e;
7604
7935
  }
7605
7936
  buckets2.set(key, cur);
7606
7937
  });
@@ -7609,12 +7940,12 @@ function bucketEdges(graph) {
7609
7940
  function nodeIsFrontier(graph, nodeId) {
7610
7941
  if (!graph.hasNode(nodeId)) return false;
7611
7942
  const attrs = graph.getNodeAttributes(nodeId);
7612
- return attrs.type === import_types30.NodeType.FrontierNode;
7943
+ return attrs.type === import_types31.NodeType.FrontierNode;
7613
7944
  }
7614
7945
  function nodeIsWebsocketChannel(graph, nodeId) {
7615
7946
  if (!graph.hasNode(nodeId)) return false;
7616
7947
  const attrs = graph.getNodeAttributes(nodeId);
7617
- return attrs.type === import_types30.NodeType.WebSocketChannelNode;
7948
+ return attrs.type === import_types31.NodeType.WebSocketChannelNode;
7618
7949
  }
7619
7950
  function clampConfidence(n) {
7620
7951
  if (!Number.isFinite(n)) return 0;
@@ -7634,14 +7965,14 @@ function gradedConfidence(edge) {
7634
7965
  return clampConfidence(confidenceForEdge(edge));
7635
7966
  }
7636
7967
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
7637
- import_types30.EdgeType.CALLS,
7638
- import_types30.EdgeType.CONNECTS_TO,
7639
- import_types30.EdgeType.PUBLISHES_TO,
7640
- import_types30.EdgeType.CONSUMES_FROM
7968
+ import_types31.EdgeType.CALLS,
7969
+ import_types31.EdgeType.CONNECTS_TO,
7970
+ import_types31.EdgeType.PUBLISHES_TO,
7971
+ import_types31.EdgeType.CONSUMES_FROM
7641
7972
  ]);
7642
7973
  function detectMissingDivergences(graph, bucket) {
7643
7974
  const out = [];
7644
- if (bucket.type === import_types30.EdgeType.CONTAINS) return out;
7975
+ if (bucket.type === import_types31.EdgeType.CONTAINS) return out;
7645
7976
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
7646
7977
  if (!nodeIsFrontier(graph, bucket.target)) {
7647
7978
  out.push({
@@ -7682,7 +8013,7 @@ function declaredHostFor(svc) {
7682
8013
  function hasExtractedConfiguredBy(graph, svcId) {
7683
8014
  for (const edgeId of graph.outboundEdges(svcId)) {
7684
8015
  const e = graph.getEdgeAttributes(edgeId);
7685
- if (e.type === import_types30.EdgeType.CONFIGURED_BY && e.provenance === import_types30.Provenance.EXTRACTED) {
8016
+ if (e.type === import_types31.EdgeType.CONFIGURED_BY && e.provenance === import_types31.Provenance.EXTRACTED) {
7686
8017
  return true;
7687
8018
  }
7688
8019
  }
@@ -7695,10 +8026,10 @@ function detectHostMismatch(graph, svcId, svc) {
7695
8026
  const out = [];
7696
8027
  for (const edgeId of graph.outboundEdges(svcId)) {
7697
8028
  const edge = graph.getEdgeAttributes(edgeId);
7698
- if (edge.type !== import_types30.EdgeType.CONNECTS_TO) continue;
7699
- if (edge.provenance !== import_types30.Provenance.OBSERVED) continue;
8029
+ if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
8030
+ if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
7700
8031
  const target = graph.getNodeAttributes(edge.target);
7701
- if (target.type !== import_types30.NodeType.DatabaseNode) continue;
8032
+ if (target.type !== import_types31.NodeType.DatabaseNode) continue;
7702
8033
  const observedHost = target.host?.trim();
7703
8034
  if (!observedHost) continue;
7704
8035
  if (observedHost === declaredHost) continue;
@@ -7720,10 +8051,10 @@ function detectCompatDivergences(graph, svcId, svc) {
7720
8051
  const deps = svc.dependencies ?? {};
7721
8052
  for (const edgeId of graph.outboundEdges(svcId)) {
7722
8053
  const edge = graph.getEdgeAttributes(edgeId);
7723
- if (edge.type !== import_types30.EdgeType.CONNECTS_TO) continue;
7724
- if (edge.provenance !== import_types30.Provenance.OBSERVED) continue;
8054
+ if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
8055
+ if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
7725
8056
  const target = graph.getNodeAttributes(edge.target);
7726
- if (target.type !== import_types30.NodeType.DatabaseNode) continue;
8057
+ if (target.type !== import_types31.NodeType.DatabaseNode) continue;
7727
8058
  for (const pair of compatPairs()) {
7728
8059
  if (pair.engine !== target.engine) continue;
7729
8060
  const declared = deps[pair.driver];
@@ -7782,7 +8113,7 @@ function suppressHostMismatchHalves(all) {
7782
8113
  for (const d of all) {
7783
8114
  if (d.type !== "host-mismatch") continue;
7784
8115
  observedHalf.add(`${d.source}->${d.target}`);
7785
- declaredHalf.add((0, import_types30.databaseId)(d.extractedHost));
8116
+ declaredHalf.add((0, import_types31.databaseId)(d.extractedHost));
7786
8117
  }
7787
8118
  if (observedHalf.size === 0) return all;
7788
8119
  return all.filter((d) => {
@@ -7801,7 +8132,7 @@ function computeDivergences(graph, opts = {}) {
7801
8132
  }
7802
8133
  graph.forEachNode((nodeId, attrs) => {
7803
8134
  const n = attrs;
7804
- if (n.type !== import_types30.NodeType.ServiceNode) return;
8135
+ if (n.type !== import_types31.NodeType.ServiceNode) return;
7805
8136
  const svc = n;
7806
8137
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
7807
8138
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -7835,7 +8166,7 @@ function computeDivergences(graph, opts = {}) {
7835
8166
  if (a.source !== b.source) return a.source.localeCompare(b.source);
7836
8167
  return a.target.localeCompare(b.target);
7837
8168
  });
7838
- return import_types30.DivergenceResultSchema.parse({
8169
+ return import_types31.DivergenceResultSchema.parse({
7839
8170
  divergences: filtered,
7840
8171
  totalAffected: filtered.length,
7841
8172
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -7845,8 +8176,8 @@ function computeDivergences(graph, opts = {}) {
7845
8176
  // src/persist.ts
7846
8177
  init_cjs_shims();
7847
8178
  var import_node_fs25 = require("fs");
7848
- var import_node_path42 = __toESM(require("path"), 1);
7849
- var import_types31 = require("@neat.is/types");
8179
+ var import_node_path43 = __toESM(require("path"), 1);
8180
+ var import_types32 = require("@neat.is/types");
7850
8181
  var SCHEMA_VERSION = 4;
7851
8182
  function migrateV1ToV2(payload) {
7852
8183
  const nodes = payload.graph.nodes;
@@ -7868,12 +8199,12 @@ function migrateV2ToV3(payload) {
7868
8199
  for (const edge of edges) {
7869
8200
  const attrs = edge.attributes;
7870
8201
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
7871
- attrs.provenance = import_types31.Provenance.OBSERVED;
8202
+ attrs.provenance = import_types32.Provenance.OBSERVED;
7872
8203
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
7873
8204
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
7874
8205
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
7875
8206
  if (type && source && target) {
7876
- const newId = (0, import_types31.observedEdgeId)(source, target, type);
8207
+ const newId = (0, import_types32.observedEdgeId)(source, target, type);
7877
8208
  attrs.id = newId;
7878
8209
  if (edge.key) edge.key = newId;
7879
8210
  }
@@ -7882,7 +8213,7 @@ function migrateV2ToV3(payload) {
7882
8213
  return { ...payload, schemaVersion: 3 };
7883
8214
  }
7884
8215
  async function ensureDir(filePath) {
7885
- await import_node_fs25.promises.mkdir(import_node_path42.default.dirname(filePath), { recursive: true });
8216
+ await import_node_fs25.promises.mkdir(import_node_path43.default.dirname(filePath), { recursive: true });
7886
8217
  }
7887
8218
  async function saveGraphToDisk(graph, outPath) {
7888
8219
  await ensureDir(outPath);
@@ -7964,7 +8295,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
7964
8295
  // src/gitignore.ts
7965
8296
  init_cjs_shims();
7966
8297
  var import_node_fs26 = require("fs");
7967
- var import_node_path43 = __toESM(require("path"), 1);
8298
+ var import_node_path44 = __toESM(require("path"), 1);
7968
8299
  var NEAT_OUT_LINE = "neat-out/";
7969
8300
  var NEAT_HEADER = "# NEAT \u2014 machine-local snapshots and events";
7970
8301
  function isNeatOutLine(line) {
@@ -7972,7 +8303,7 @@ function isNeatOutLine(line) {
7972
8303
  return trimmed === "neat-out/" || trimmed === "neat-out";
7973
8304
  }
7974
8305
  async function ensureNeatOutIgnored(projectDir) {
7975
- const file = import_node_path43.default.join(projectDir, ".gitignore");
8306
+ const file = import_node_path44.default.join(projectDir, ".gitignore");
7976
8307
  let existing = null;
7977
8308
  try {
7978
8309
  existing = await import_node_fs26.promises.readFile(file, "utf8");
@@ -7999,7 +8330,7 @@ ${NEAT_OUT_LINE}
7999
8330
 
8000
8331
  // src/summary.ts
8001
8332
  init_cjs_shims();
8002
- var import_types32 = require("@neat.is/types");
8333
+ var import_types33 = require("@neat.is/types");
8003
8334
  function renderOtelEnvBlock() {
8004
8335
  return [
8005
8336
  "for prod OTel routing, set these in your deploy platform's env:",
@@ -8009,19 +8340,19 @@ function renderOtelEnvBlock() {
8009
8340
  }
8010
8341
  function findIncompatServices(nodes) {
8011
8342
  return nodes.filter(
8012
- (n) => n.type === import_types32.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
8343
+ (n) => n.type === import_types33.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
8013
8344
  );
8014
8345
  }
8015
8346
  function servicesWithoutObserved(nodes, edges) {
8016
8347
  const seen = /* @__PURE__ */ new Set();
8017
8348
  for (const e of edges) {
8018
- if (e.provenance === import_types32.Provenance.OBSERVED) {
8349
+ if (e.provenance === import_types33.Provenance.OBSERVED) {
8019
8350
  seen.add(e.source);
8020
8351
  seen.add(e.target);
8021
8352
  }
8022
8353
  }
8023
8354
  return nodes.filter(
8024
- (n) => n.type === import_types32.NodeType.ServiceNode && !seen.has(n.id)
8355
+ (n) => n.type === import_types33.NodeType.ServiceNode && !seen.has(n.id)
8025
8356
  );
8026
8357
  }
8027
8358
  function formatDivergence(d) {
@@ -8096,26 +8427,26 @@ function formatIncompat(inc) {
8096
8427
  // src/watch.ts
8097
8428
  init_cjs_shims();
8098
8429
  var import_node_fs35 = __toESM(require("fs"), 1);
8099
- var import_node_path54 = __toESM(require("path"), 1);
8430
+ var import_node_path55 = __toESM(require("path"), 1);
8100
8431
  var import_chokidar = __toESM(require("chokidar"), 1);
8101
8432
 
8102
8433
  // src/api.ts
8103
8434
  init_cjs_shims();
8104
8435
  var import_fastify = __toESM(require("fastify"), 1);
8105
8436
  var import_cors = __toESM(require("@fastify/cors"), 1);
8106
- var import_types34 = require("@neat.is/types");
8437
+ var import_types35 = require("@neat.is/types");
8107
8438
 
8108
8439
  // src/extend/index.ts
8109
8440
  init_cjs_shims();
8110
8441
  var import_node_fs28 = require("fs");
8111
- var import_node_path45 = __toESM(require("path"), 1);
8442
+ var import_node_path46 = __toESM(require("path"), 1);
8112
8443
  var import_node_os2 = __toESM(require("os"), 1);
8113
8444
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
8114
8445
 
8115
8446
  // src/installers/package-manager.ts
8116
8447
  init_cjs_shims();
8117
8448
  var import_node_fs27 = require("fs");
8118
- var import_node_path44 = __toESM(require("path"), 1);
8449
+ var import_node_path45 = __toESM(require("path"), 1);
8119
8450
  var import_node_child_process = require("child_process");
8120
8451
  var LOCKFILE_PRIORITY = [
8121
8452
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -8137,22 +8468,22 @@ async function exists2(p) {
8137
8468
  }
8138
8469
  }
8139
8470
  async function detectPackageManager(serviceDir) {
8140
- let dir = import_node_path44.default.resolve(serviceDir);
8471
+ let dir = import_node_path45.default.resolve(serviceDir);
8141
8472
  const stops = /* @__PURE__ */ new Set();
8142
8473
  for (let i = 0; i < 64; i++) {
8143
8474
  if (stops.has(dir)) break;
8144
8475
  stops.add(dir);
8145
8476
  for (const candidate of LOCKFILE_PRIORITY) {
8146
- const lockPath = import_node_path44.default.join(dir, candidate.lockfile);
8477
+ const lockPath = import_node_path45.default.join(dir, candidate.lockfile);
8147
8478
  if (await exists2(lockPath)) {
8148
8479
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
8149
8480
  }
8150
8481
  }
8151
- const parent = import_node_path44.default.dirname(dir);
8482
+ const parent = import_node_path45.default.dirname(dir);
8152
8483
  if (parent === dir) break;
8153
8484
  dir = parent;
8154
8485
  }
8155
- return { pm: "npm", cwd: import_node_path44.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
8486
+ return { pm: "npm", cwd: import_node_path45.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
8156
8487
  }
8157
8488
  async function runPackageManagerInstall(cmd) {
8158
8489
  return new Promise((resolve) => {
@@ -8201,7 +8532,7 @@ async function fileExists2(p) {
8201
8532
  }
8202
8533
  }
8203
8534
  async function readPackageJson(scanPath) {
8204
- const pkgPath = import_node_path45.default.join(scanPath, "package.json");
8535
+ const pkgPath = import_node_path46.default.join(scanPath, "package.json");
8205
8536
  const raw = await import_node_fs28.promises.readFile(pkgPath, "utf8");
8206
8537
  return JSON.parse(raw);
8207
8538
  }
@@ -8220,11 +8551,11 @@ async function findHookFiles(scanPath) {
8220
8551
  for (const entry2 of entries) {
8221
8552
  if (entry2.isDirectory()) {
8222
8553
  if (entry2.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry2.name)) continue;
8223
- await walk3(import_node_path45.default.join(dir, entry2.name));
8554
+ await walk3(import_node_path46.default.join(dir, entry2.name));
8224
8555
  } else if (entry2.isFile()) {
8225
8556
  if ((entry2.name.startsWith("instrumentation") || entry2.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry2.name)) {
8226
- const rel = import_node_path45.default.relative(scanPath, import_node_path45.default.join(dir, entry2.name));
8227
- found.push(rel.split(import_node_path45.default.sep).join("/"));
8557
+ const rel = import_node_path46.default.relative(scanPath, import_node_path46.default.join(dir, entry2.name));
8558
+ found.push(rel.split(import_node_path46.default.sep).join("/"));
8228
8559
  }
8229
8560
  }
8230
8561
  }
@@ -8235,7 +8566,7 @@ async function findHookFiles(scanPath) {
8235
8566
  async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
8236
8567
  let fallback = null;
8237
8568
  for (const file of hookFiles) {
8238
- const content = await import_node_fs28.promises.readFile(import_node_path45.default.join(scanPath, file), "utf8");
8569
+ const content = await import_node_fs28.promises.readFile(import_node_path46.default.join(scanPath, file), "utf8");
8239
8570
  const patched = splicedContent(content, snippet2);
8240
8571
  if (patched !== null) return { file, content, patched };
8241
8572
  if (fallback === null) fallback = { file, content };
@@ -8243,11 +8574,11 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
8243
8574
  return { file: fallback.file, content: fallback.content, patched: null };
8244
8575
  }
8245
8576
  function extendLogPath() {
8246
- return process.env.NEAT_EXTEND_LOG ?? import_node_path45.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
8577
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path46.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
8247
8578
  }
8248
8579
  async function appendExtendLog(entry2) {
8249
8580
  const logPath = extendLogPath();
8250
- await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(logPath), { recursive: true });
8581
+ await import_node_fs28.promises.mkdir(import_node_path46.default.dirname(logPath), { recursive: true });
8251
8582
  await import_node_fs28.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
8252
8583
  }
8253
8584
  function splicedContent(fileContent, snippet2) {
@@ -8306,7 +8637,7 @@ function lookupInstrumentation(library, installedVersion) {
8306
8637
  }
8307
8638
  async function describeProjectInstrumentation(ctx) {
8308
8639
  const hookFiles = await findHookFiles(ctx.scanPath);
8309
- const envNeat = await fileExists2(import_node_path45.default.join(ctx.scanPath, ".env.neat"));
8640
+ const envNeat = await fileExists2(import_node_path46.default.join(ctx.scanPath, ".env.neat"));
8310
8641
  const registryInstrPackages = new Set(
8311
8642
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
8312
8643
  );
@@ -8328,7 +8659,7 @@ async function applyExtension(ctx, args, options) {
8328
8659
  );
8329
8660
  }
8330
8661
  for (const file of hookFiles) {
8331
- const content = await import_node_fs28.promises.readFile(import_node_path45.default.join(ctx.scanPath, file), "utf8");
8662
+ const content = await import_node_fs28.promises.readFile(import_node_path46.default.join(ctx.scanPath, file), "utf8");
8332
8663
  if (content.includes(args.registration_snippet)) {
8333
8664
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
8334
8665
  }
@@ -8340,10 +8671,10 @@ async function applyExtension(ctx, args, options) {
8340
8671
  );
8341
8672
  }
8342
8673
  const primaryFile = primary.file;
8343
- const primaryPath = import_node_path45.default.join(ctx.scanPath, primaryFile);
8674
+ const primaryPath = import_node_path46.default.join(ctx.scanPath, primaryFile);
8344
8675
  const filesTouched = [];
8345
8676
  const depsAdded = [];
8346
- const pkgPath = import_node_path45.default.join(ctx.scanPath, "package.json");
8677
+ const pkgPath = import_node_path46.default.join(ctx.scanPath, "package.json");
8347
8678
  const pkg = await readPackageJson(ctx.scanPath);
8348
8679
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
8349
8680
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
@@ -8382,7 +8713,7 @@ async function dryRunExtension(ctx, args) {
8382
8713
  };
8383
8714
  }
8384
8715
  for (const file of hookFiles) {
8385
- const content = await import_node_fs28.promises.readFile(import_node_path45.default.join(ctx.scanPath, file), "utf8");
8716
+ const content = await import_node_fs28.promises.readFile(import_node_path46.default.join(ctx.scanPath, file), "utf8");
8386
8717
  if (content.includes(args.registration_snippet)) {
8387
8718
  return {
8388
8719
  library: args.library,
@@ -8423,7 +8754,7 @@ async function rollbackExtension(ctx, args) {
8423
8754
  if (!match) {
8424
8755
  return { undone: false, message: "no apply found for library" };
8425
8756
  }
8426
- const pkgPath = import_node_path45.default.join(ctx.scanPath, "package.json");
8757
+ const pkgPath = import_node_path46.default.join(ctx.scanPath, "package.json");
8427
8758
  if (await fileExists2(pkgPath)) {
8428
8759
  const pkg = await readPackageJson(ctx.scanPath);
8429
8760
  if (pkg.dependencies?.[match.instrumentation_package]) {
@@ -8434,7 +8765,7 @@ async function rollbackExtension(ctx, args) {
8434
8765
  }
8435
8766
  const hookFiles = await findHookFiles(ctx.scanPath);
8436
8767
  for (const file of hookFiles) {
8437
- const filePath = import_node_path45.default.join(ctx.scanPath, file);
8768
+ const filePath = import_node_path46.default.join(ctx.scanPath, file);
8438
8769
  const content = await import_node_fs28.promises.readFile(filePath, "utf8");
8439
8770
  if (content.includes(match.registration_snippet)) {
8440
8771
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
@@ -8572,23 +8903,23 @@ function canonicalJson(value) {
8572
8903
 
8573
8904
  // src/projects.ts
8574
8905
  init_cjs_shims();
8575
- var import_node_path46 = __toESM(require("path"), 1);
8906
+ var import_node_path47 = __toESM(require("path"), 1);
8576
8907
  function pathsForProject(project, baseDir) {
8577
8908
  if (project === DEFAULT_PROJECT) {
8578
8909
  return {
8579
- snapshotPath: import_node_path46.default.join(baseDir, "graph.json"),
8580
- errorsPath: import_node_path46.default.join(baseDir, "errors.ndjson"),
8581
- staleEventsPath: import_node_path46.default.join(baseDir, "stale-events.ndjson"),
8582
- embeddingsCachePath: import_node_path46.default.join(baseDir, "embeddings.json"),
8583
- policyViolationsPath: import_node_path46.default.join(baseDir, "policy-violations.ndjson")
8910
+ snapshotPath: import_node_path47.default.join(baseDir, "graph.json"),
8911
+ errorsPath: import_node_path47.default.join(baseDir, "errors.ndjson"),
8912
+ staleEventsPath: import_node_path47.default.join(baseDir, "stale-events.ndjson"),
8913
+ embeddingsCachePath: import_node_path47.default.join(baseDir, "embeddings.json"),
8914
+ policyViolationsPath: import_node_path47.default.join(baseDir, "policy-violations.ndjson")
8584
8915
  };
8585
8916
  }
8586
8917
  return {
8587
- snapshotPath: import_node_path46.default.join(baseDir, `${project}.json`),
8588
- errorsPath: import_node_path46.default.join(baseDir, `errors.${project}.ndjson`),
8589
- staleEventsPath: import_node_path46.default.join(baseDir, `stale-events.${project}.ndjson`),
8590
- embeddingsCachePath: import_node_path46.default.join(baseDir, `embeddings.${project}.json`),
8591
- policyViolationsPath: import_node_path46.default.join(baseDir, `policy-violations.${project}.ndjson`)
8918
+ snapshotPath: import_node_path47.default.join(baseDir, `${project}.json`),
8919
+ errorsPath: import_node_path47.default.join(baseDir, `errors.${project}.ndjson`),
8920
+ staleEventsPath: import_node_path47.default.join(baseDir, `stale-events.${project}.ndjson`),
8921
+ embeddingsCachePath: import_node_path47.default.join(baseDir, `embeddings.${project}.json`),
8922
+ policyViolationsPath: import_node_path47.default.join(baseDir, `policy-violations.${project}.ndjson`)
8592
8923
  };
8593
8924
  }
8594
8925
  var Projects = class {
@@ -8626,26 +8957,26 @@ var Projects = class {
8626
8957
  init_cjs_shims();
8627
8958
  var import_node_fs30 = require("fs");
8628
8959
  var import_node_os3 = __toESM(require("os"), 1);
8629
- var import_node_path47 = __toESM(require("path"), 1);
8630
- var import_types33 = require("@neat.is/types");
8960
+ var import_node_path48 = __toESM(require("path"), 1);
8961
+ var import_types34 = require("@neat.is/types");
8631
8962
  var LOCK_TIMEOUT_MS = 5e3;
8632
8963
  var LOCK_RETRY_MS = 50;
8633
8964
  function neatHome() {
8634
8965
  const override = process.env.NEAT_HOME;
8635
- if (override && override.length > 0) return import_node_path47.default.resolve(override);
8636
- return import_node_path47.default.join(import_node_os3.default.homedir(), ".neat");
8966
+ if (override && override.length > 0) return import_node_path48.default.resolve(override);
8967
+ return import_node_path48.default.join(import_node_os3.default.homedir(), ".neat");
8637
8968
  }
8638
8969
  function registryPath() {
8639
- return import_node_path47.default.join(neatHome(), "projects.json");
8970
+ return import_node_path48.default.join(neatHome(), "projects.json");
8640
8971
  }
8641
8972
  function registryLockPath() {
8642
- return import_node_path47.default.join(neatHome(), "projects.json.lock");
8973
+ return import_node_path48.default.join(neatHome(), "projects.json.lock");
8643
8974
  }
8644
8975
  function daemonPidPath() {
8645
- return import_node_path47.default.join(neatHome(), "neatd.pid");
8976
+ return import_node_path48.default.join(neatHome(), "neatd.pid");
8646
8977
  }
8647
8978
  function daemonsDir() {
8648
- return import_node_path47.default.join(neatHome(), "daemons");
8979
+ return import_node_path48.default.join(neatHome(), "daemons");
8649
8980
  }
8650
8981
  function isFiniteInt(v) {
8651
8982
  return typeof v === "number" && Number.isFinite(v);
@@ -8686,7 +9017,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
8686
9017
  const out = [];
8687
9018
  for (const name of names) {
8688
9019
  if (!name.endsWith(".json")) continue;
8689
- const file = import_node_path47.default.join(dir, name);
9020
+ const file = import_node_path48.default.join(dir, name);
8690
9021
  let raw;
8691
9022
  try {
8692
9023
  raw = await import_node_fs30.promises.readFile(file, "utf8");
@@ -8807,7 +9138,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
8807
9138
  }
8808
9139
  }
8809
9140
  async function normalizeProjectPath(input) {
8810
- const resolved = import_node_path47.default.resolve(input);
9141
+ const resolved = import_node_path48.default.resolve(input);
8811
9142
  try {
8812
9143
  return await import_node_fs30.promises.realpath(resolved);
8813
9144
  } catch {
@@ -8815,7 +9146,7 @@ async function normalizeProjectPath(input) {
8815
9146
  }
8816
9147
  }
8817
9148
  async function writeAtomically(target, contents) {
8818
- await import_node_fs30.promises.mkdir(import_node_path47.default.dirname(target), { recursive: true });
9149
+ await import_node_fs30.promises.mkdir(import_node_path48.default.dirname(target), { recursive: true });
8819
9150
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
8820
9151
  const fd = await import_node_fs30.promises.open(tmp, "w");
8821
9152
  try {
@@ -8828,7 +9159,7 @@ async function writeAtomically(target, contents) {
8828
9159
  }
8829
9160
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
8830
9161
  const deadline = Date.now() + timeoutMs;
8831
- await import_node_fs30.promises.mkdir(import_node_path47.default.dirname(lockPath), { recursive: true });
9162
+ await import_node_fs30.promises.mkdir(import_node_path48.default.dirname(lockPath), { recursive: true });
8832
9163
  let probedHolder = false;
8833
9164
  while (true) {
8834
9165
  try {
@@ -8881,10 +9212,10 @@ async function readRegistry() {
8881
9212
  throw err;
8882
9213
  }
8883
9214
  const parsed = JSON.parse(raw);
8884
- return import_types33.RegistryFileSchema.parse(parsed);
9215
+ return import_types34.RegistryFileSchema.parse(parsed);
8885
9216
  }
8886
9217
  async function writeRegistry(reg) {
8887
- const validated = import_types33.RegistryFileSchema.parse(reg);
9218
+ const validated = import_types34.RegistryFileSchema.parse(reg);
8888
9219
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
8889
9220
  }
8890
9221
  var ProjectNameCollisionError = class extends Error {
@@ -9070,7 +9401,7 @@ init_auth();
9070
9401
  // src/connectors-config.ts
9071
9402
  init_cjs_shims();
9072
9403
  var import_node_os4 = __toESM(require("os"), 1);
9073
- var import_node_path48 = __toESM(require("path"), 1);
9404
+ var import_node_path49 = __toESM(require("path"), 1);
9074
9405
  var import_node_fs31 = require("fs");
9075
9406
  var CONNECTORS_CONFIG_VERSION = 1;
9076
9407
  var EnvRefUnsetError = class extends Error {
@@ -9085,11 +9416,11 @@ var EnvRefUnsetError = class extends Error {
9085
9416
  };
9086
9417
  function neatHome2() {
9087
9418
  const override = process.env.NEAT_HOME;
9088
- if (override && override.length > 0) return import_node_path48.default.resolve(override);
9089
- return import_node_path48.default.join(import_node_os4.default.homedir(), ".neat");
9419
+ if (override && override.length > 0) return import_node_path49.default.resolve(override);
9420
+ return import_node_path49.default.join(import_node_os4.default.homedir(), ".neat");
9090
9421
  }
9091
9422
  function connectorsConfigPath(home = neatHome2()) {
9092
- return import_node_path48.default.join(home, "connectors.json");
9423
+ return import_node_path49.default.join(home, "connectors.json");
9093
9424
  }
9094
9425
  var MODE_MASK_LOOSER_THAN_0600 = 63;
9095
9426
  async function warnIfModeLooserThan0600(file) {
@@ -9220,7 +9551,7 @@ function connectorMatchesProject(entry2, project) {
9220
9551
  var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
9221
9552
  var CONNECTORS_LOCK_RETRY_MS = 50;
9222
9553
  function connectorsConfigLockPath(home = neatHome2()) {
9223
- return import_node_path48.default.join(home, "connectors.json.lock");
9554
+ return import_node_path49.default.join(home, "connectors.json.lock");
9224
9555
  }
9225
9556
  function isEnvRef(value) {
9226
9557
  return value.length > 1 && value.startsWith("$");
@@ -9233,7 +9564,7 @@ function redactCredentialRef(ref) {
9233
9564
  return out;
9234
9565
  }
9235
9566
  async function writeConfigAtomically0600(file, contents) {
9236
- await import_node_fs31.promises.mkdir(import_node_path48.default.dirname(file), { recursive: true });
9567
+ await import_node_fs31.promises.mkdir(import_node_path49.default.dirname(file), { recursive: true });
9237
9568
  const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
9238
9569
  const fd = await import_node_fs31.promises.open(tmp, "w", 384);
9239
9570
  try {
@@ -9247,7 +9578,7 @@ async function writeConfigAtomically0600(file, contents) {
9247
9578
  }
9248
9579
  async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
9249
9580
  const deadline = Date.now() + timeoutMs;
9250
- await import_node_fs31.promises.mkdir(import_node_path48.default.dirname(lockPath), { recursive: true });
9581
+ await import_node_fs31.promises.mkdir(import_node_path49.default.dirname(lockPath), { recursive: true });
9251
9582
  for (; ; ) {
9252
9583
  try {
9253
9584
  const fd = await import_node_fs31.promises.open(lockPath, "wx");
@@ -9530,11 +9861,11 @@ function registerRoutes(scope, ctx) {
9530
9861
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
9531
9862
  const parsed = [];
9532
9863
  for (const c of candidates) {
9533
- const r = import_types34.DivergenceTypeSchema.safeParse(c);
9864
+ const r = import_types35.DivergenceTypeSchema.safeParse(c);
9534
9865
  if (!r.success) {
9535
9866
  return reply.code(400).send({
9536
9867
  error: `unknown divergence type "${c}"`,
9537
- allowed: import_types34.DivergenceTypeSchema.options
9868
+ allowed: import_types35.DivergenceTypeSchema.options
9538
9869
  });
9539
9870
  }
9540
9871
  parsed.push(r.data);
@@ -9805,7 +10136,7 @@ function registerRoutes(scope, ctx) {
9805
10136
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
9806
10137
  let violations = await log.readAll();
9807
10138
  if (req.query.severity) {
9808
- const sev = import_types34.PolicySeveritySchema.safeParse(req.query.severity);
10139
+ const sev = import_types35.PolicySeveritySchema.safeParse(req.query.severity);
9809
10140
  if (!sev.success) {
9810
10141
  return reply.code(400).send({
9811
10142
  error: "invalid severity",
@@ -9844,7 +10175,7 @@ function registerRoutes(scope, ctx) {
9844
10175
  scope.post("/policies/check", async (req, reply) => {
9845
10176
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
9846
10177
  if (!proj) return;
9847
- const parsed = import_types34.PoliciesCheckBodySchema.safeParse(req.body ?? {});
10178
+ const parsed = import_types35.PoliciesCheckBodySchema.safeParse(req.body ?? {});
9848
10179
  if (!parsed.success) {
9849
10180
  return reply.code(400).send({
9850
10181
  error: "invalid /policies/check body",
@@ -9916,6 +10247,42 @@ function registerRoutes(scope, ctx) {
9916
10247
  return reply.code(500).send({ error: err.message });
9917
10248
  }
9918
10249
  });
10250
+ scope.get("/instrumentation", async (req, reply) => {
10251
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
10252
+ if (!proj) return;
10253
+ if (!proj.scanPath) {
10254
+ return { engaged: null };
10255
+ }
10256
+ try {
10257
+ const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath });
10258
+ const uninstrumented = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath });
10259
+ if (state.hookFiles.length === 0) {
10260
+ return {
10261
+ engaged: false,
10262
+ diagnosis: {
10263
+ reason: "No instrumented entry point found \u2014 NEAT hasn't written an OTel init hook for this project.",
10264
+ fixCommand: "neat init",
10265
+ detail: "Run `neat init` so NEAT writes the instrumentation hook next to your entry point, then run your app."
10266
+ }
10267
+ };
10268
+ }
10269
+ if (uninstrumented.length > 0) {
10270
+ const names = uninstrumented.map((u) => u.library);
10271
+ const more = names.length > 1 ? ` (and ${names.length - 1} more)` : "";
10272
+ return {
10273
+ engaged: false,
10274
+ diagnosis: {
10275
+ reason: `\`${names[0]}\`${more} isn't in the auto-instrumentation set, so spans from it won't reach the graph.`,
10276
+ fixCommand: "neat extend",
10277
+ detail: `Uninstrumented on your hot path: ${names.join(", ")}. Run \`neat extend\` to wire the missing instrumentation.`
10278
+ }
10279
+ };
10280
+ }
10281
+ return { engaged: true };
10282
+ } catch {
10283
+ return { engaged: null };
10284
+ }
10285
+ });
9919
10286
  scope.post("/extend/apply", async (req, reply) => {
9920
10287
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
9921
10288
  if (!proj) return;
@@ -10110,13 +10477,13 @@ init_otel();
10110
10477
  // src/daemon.ts
10111
10478
  init_cjs_shims();
10112
10479
  var import_node_fs33 = require("fs");
10113
- var import_node_path52 = __toESM(require("path"), 1);
10480
+ var import_node_path53 = __toESM(require("path"), 1);
10114
10481
  var import_node_module = require("module");
10115
10482
  init_otel();
10116
10483
 
10117
10484
  // src/connectors/index.ts
10118
10485
  init_cjs_shims();
10119
- var import_types35 = require("@neat.is/types");
10486
+ var import_types36 = require("@neat.is/types");
10120
10487
 
10121
10488
  // src/connectors/registry.ts
10122
10489
  init_cjs_shims();
@@ -10194,7 +10561,12 @@ var JUNCTION_DEFAULT_RATE_LIMITS = {
10194
10561
  supabase: { capacity: 30, refillMs: 1e4 },
10195
10562
  // Not a documented API limit at all — a self-imposed ceiling on the raw
10196
10563
  // pg_stat_statements connection (see module header above).
10197
- "supabase-postgres": { capacity: 20, refillMs: 3e3 }
10564
+ "supabase-postgres": { capacity: 20, refillMs: 3e3 },
10565
+ // Push provider (ADR-146): the Drains REST API is touched only by `neat
10566
+ // connector add/remove/test` (provision/deprovision/validate), never a poll
10567
+ // loop, so this bucket is exercised a handful of times per command. Kept
10568
+ // conservative pending a documented Drains-API rate limit.
10569
+ vercel: { capacity: 20, refillMs: 5e3 }
10198
10570
  };
10199
10571
  var JUNCTION_GENERIC_RATE_LIMIT = { capacity: 20, refillMs: 5e3 };
10200
10572
  function defaultRateLimitFor(provider) {
@@ -10486,10 +10858,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
10486
10858
  // src/connectors/supabase/map.ts
10487
10859
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
10488
10860
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
10489
- function targetFromRestPath(path62) {
10490
- const rpcMatch = REST_RPC_PATH_RE.exec(path62);
10861
+ function targetFromRestPath(path63) {
10862
+ const rpcMatch = REST_RPC_PATH_RE.exec(path63);
10491
10863
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
10492
- const tableMatch = REST_TABLE_PATH_RE.exec(path62);
10864
+ const tableMatch = REST_TABLE_PATH_RE.exec(path63);
10493
10865
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
10494
10866
  return null;
10495
10867
  }
@@ -10598,23 +10970,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
10598
10970
 
10599
10971
  // src/connectors/supabase/resolve.ts
10600
10972
  init_cjs_shims();
10601
- var import_types37 = require("@neat.is/types");
10973
+ var import_types38 = require("@neat.is/types");
10602
10974
  function createSupabaseResolveTarget(graph, config) {
10603
10975
  return (signal, _ctx) => {
10604
10976
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
10605
10977
  return null;
10606
10978
  }
10607
- const subResourceId = (0, import_types37.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
10979
+ const subResourceId = (0, import_types38.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
10608
10980
  if (graph.hasNode(subResourceId)) {
10609
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10981
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types38.EdgeType.CALLS };
10610
10982
  }
10611
- const bareResourceId = (0, import_types37.infraId)(signal.targetKind, signal.targetName);
10983
+ const bareResourceId = (0, import_types38.infraId)(signal.targetKind, signal.targetName);
10612
10984
  if (graph.hasNode(bareResourceId)) {
10613
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10985
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types38.EdgeType.CALLS };
10614
10986
  }
10615
- const projectLevelId = (0, import_types37.infraId)("supabase", config.nodeRef);
10987
+ const projectLevelId = (0, import_types38.infraId)("supabase", config.nodeRef);
10616
10988
  if (graph.hasNode(projectLevelId)) {
10617
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10989
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types38.EdgeType.CALLS };
10618
10990
  }
10619
10991
  return null;
10620
10992
  };
@@ -10707,7 +11079,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
10707
11079
 
10708
11080
  // src/connectors/railway/index.ts
10709
11081
  init_cjs_shims();
10710
- var import_types41 = require("@neat.is/types");
11082
+ var import_types42 = require("@neat.is/types");
10711
11083
 
10712
11084
  // src/connectors/railway/client.ts
10713
11085
  init_cjs_shims();
@@ -10833,7 +11205,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
10833
11205
  const out = [];
10834
11206
  graph.forEachNode((_id, attrs) => {
10835
11207
  const node = attrs;
10836
- if (node.type !== import_types41.NodeType.RouteNode) return;
11208
+ if (node.type !== import_types42.NodeType.RouteNode) return;
10837
11209
  const route = attrs;
10838
11210
  if (route.service !== serviceName) return;
10839
11211
  out.push({
@@ -10937,12 +11309,12 @@ function createRailwayResolveTarget(config) {
10937
11309
  const serviceName = config.serviceNameById[config.serviceId];
10938
11310
  if (!serviceName) return null;
10939
11311
  if (signal.targetKind === ROUTE_TARGET_KIND) {
10940
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types41.EdgeType.CALLS };
11312
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types42.EdgeType.CALLS };
10941
11313
  }
10942
11314
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
10943
11315
  const peerName = config.serviceNameById[signal.targetName];
10944
11316
  if (!peerName) return null;
10945
- return { targetNodeId: (0, import_types41.serviceId)(peerName), serviceName, edgeType: import_types41.EdgeType.CONNECTS_TO };
11317
+ return { targetNodeId: (0, import_types42.serviceId)(peerName), serviceName, edgeType: import_types42.EdgeType.CONNECTS_TO };
10946
11318
  }
10947
11319
  return null;
10948
11320
  };
@@ -11066,9 +11438,9 @@ function parseFirebaseTargetName(targetName) {
11066
11438
  const secondSep = rest.indexOf(FIELD_SEP);
11067
11439
  if (secondSep === -1) return null;
11068
11440
  const method = rest.slice(0, secondSep);
11069
- const path62 = rest.slice(secondSep + 1);
11070
- if (!resourceName || !method || !path62) return null;
11071
- return { resourceName, method, path: path62 };
11441
+ const path63 = rest.slice(secondSep + 1);
11442
+ if (!resourceName || !method || !path63) return null;
11443
+ return { resourceName, method, path: path63 };
11072
11444
  }
11073
11445
  function resourceNameFor(type, labels) {
11074
11446
  if (!labels) return null;
@@ -11106,14 +11478,14 @@ function mapLogEntryToSignal(entry2) {
11106
11478
  if (!req) return null;
11107
11479
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
11108
11480
  const method = req.requestMethod.toUpperCase();
11109
- const path62 = pathFromRequestUrl(req.requestUrl);
11110
- if (path62 === null) return null;
11481
+ const path63 = pathFromRequestUrl(req.requestUrl);
11482
+ if (path63 === null) return null;
11111
11483
  const timestamp = entry2.timestamp;
11112
11484
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
11113
11485
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
11114
11486
  return {
11115
11487
  targetKind: resourceType,
11116
- targetName: packFirebaseTargetName({ resourceName, method, path: path62 }),
11488
+ targetName: packFirebaseTargetName({ resourceName, method, path: path63 }),
11117
11489
  callCount: 1,
11118
11490
  errorCount: isError ? 1 : 0,
11119
11491
  lastObservedIso: timestamp
@@ -11130,7 +11502,7 @@ function mapLogEntriesToSignals(entries) {
11130
11502
 
11131
11503
  // src/connectors/firebase/resolve.ts
11132
11504
  init_cjs_shims();
11133
- var import_types42 = require("@neat.is/types");
11505
+ var import_types43 = require("@neat.is/types");
11134
11506
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
11135
11507
  switch (resourceType) {
11136
11508
  case "cloud_function":
@@ -11145,7 +11517,7 @@ function routeEntriesFor(graph, serviceName) {
11145
11517
  const entries = [];
11146
11518
  graph.forEachNode((_id, attrs) => {
11147
11519
  const node = attrs;
11148
- if (node.type !== import_types42.NodeType.RouteNode) return;
11520
+ if (node.type !== import_types43.NodeType.RouteNode) return;
11149
11521
  const route = attrs;
11150
11522
  if (route.service !== serviceName) return;
11151
11523
  entries.push({
@@ -11177,7 +11549,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
11177
11549
  return {
11178
11550
  targetNodeId: match.routeNodeId,
11179
11551
  serviceName,
11180
- edgeType: import_types42.EdgeType.CALLS
11552
+ edgeType: import_types43.EdgeType.CALLS
11181
11553
  };
11182
11554
  };
11183
11555
  }
@@ -11204,7 +11576,7 @@ init_cjs_shims();
11204
11576
 
11205
11577
  // src/connectors/cloudflare/connector.ts
11206
11578
  init_cjs_shims();
11207
- var import_types44 = require("@neat.is/types");
11579
+ var import_types45 = require("@neat.is/types");
11208
11580
 
11209
11581
  // src/connectors/cloudflare/client.ts
11210
11582
  init_cjs_shims();
@@ -11320,7 +11692,7 @@ function mapEventToSignal(event) {
11320
11692
  if (Number.isNaN(observedAt.getTime())) return null;
11321
11693
  const statusCode = metadata?.statusCode;
11322
11694
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
11323
- const path62 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
11695
+ const path63 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
11324
11696
  return {
11325
11697
  targetKind: CLOUDFLARE_TARGET_KIND,
11326
11698
  targetName: scriptName,
@@ -11328,7 +11700,7 @@ function mapEventToSignal(event) {
11328
11700
  errorCount: isError ? 1 : 0,
11329
11701
  lastObservedIso: observedAt.toISOString(),
11330
11702
  method,
11331
- ...path62 ? { path: path62 } : {},
11703
+ ...path63 ? { path: path63 } : {},
11332
11704
  ...typeof statusCode === "number" ? { statusCode } : {},
11333
11705
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
11334
11706
  };
@@ -11368,19 +11740,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
11368
11740
  graph.forEachNode((id, attrs) => {
11369
11741
  if (found) return;
11370
11742
  const a = attrs;
11371
- if (a.type === import_types44.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
11743
+ if (a.type === import_types45.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
11372
11744
  found = id;
11373
11745
  }
11374
11746
  });
11375
11747
  return found;
11376
11748
  }
11377
- function findMatchingRouteNode(graph, serviceName, method, path62) {
11378
- const normalizedPath = normalizePathTemplate(path62);
11749
+ function findMatchingRouteNode(graph, serviceName, method, path63) {
11750
+ const normalizedPath = normalizePathTemplate(path63);
11379
11751
  let found = null;
11380
11752
  graph.forEachNode((id, attrs) => {
11381
11753
  if (found) return;
11382
11754
  const a = attrs;
11383
- if (a.type !== import_types44.NodeType.RouteNode || a.service !== serviceName) return;
11755
+ if (a.type !== import_types45.NodeType.RouteNode || a.service !== serviceName) return;
11384
11756
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
11385
11757
  const routeMethod = (a.method ?? "").toUpperCase();
11386
11758
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -11392,18 +11764,18 @@ function createCloudflareResolveTarget(config, graph) {
11392
11764
  return (signal) => {
11393
11765
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
11394
11766
  const scriptName = signal.targetName;
11395
- const { method, path: path62 } = signal;
11767
+ const { method, path: path63 } = signal;
11396
11768
  const resolveRouteGrain = (serviceName, wholeFileId) => {
11397
- if (!method || !path62) return wholeFileId;
11398
- return findMatchingRouteNode(graph, serviceName, method, path62) ?? wholeFileId;
11769
+ if (!method || !path63) return wholeFileId;
11770
+ return findMatchingRouteNode(graph, serviceName, method, path63) ?? wholeFileId;
11399
11771
  };
11400
11772
  const mapping = config.workers?.[scriptName];
11401
11773
  if (mapping) {
11402
- const wholeFileId = (0, import_types44.fileId)(mapping.service, mapping.entryFile);
11774
+ const wholeFileId = (0, import_types45.fileId)(mapping.service, mapping.entryFile);
11403
11775
  return {
11404
11776
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
11405
11777
  serviceName: mapping.service,
11406
- edgeType: import_types44.EdgeType.CALLS
11778
+ edgeType: import_types45.EdgeType.CALLS
11407
11779
  };
11408
11780
  }
11409
11781
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -11412,18 +11784,124 @@ function createCloudflareResolveTarget(config, graph) {
11412
11784
  return {
11413
11785
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
11414
11786
  serviceName: fileNode.service,
11415
- edgeType: import_types44.EdgeType.CALLS
11787
+ edgeType: import_types45.EdgeType.CALLS
11416
11788
  };
11417
11789
  }
11418
11790
  return {
11419
- targetNodeId: (0, import_types44.infraId)("cloudflare-worker", scriptName),
11791
+ targetNodeId: (0, import_types45.infraId)("cloudflare-worker", scriptName),
11420
11792
  serviceName: scriptName,
11421
- edgeType: import_types44.EdgeType.CALLS,
11793
+ edgeType: import_types45.EdgeType.CALLS,
11422
11794
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
11423
11795
  };
11424
11796
  };
11425
11797
  }
11426
11798
 
11799
+ // src/connectors/vercel/index.ts
11800
+ init_cjs_shims();
11801
+
11802
+ // src/connectors/vercel/client.ts
11803
+ init_cjs_shims();
11804
+ var DEFAULT_API_BASE_URL = "https://api.vercel.com";
11805
+ var DEFAULT_DRAIN_NAME = "neat-otlp";
11806
+ var TRACE_SCHEMAS = { trace: { version: "v1" } };
11807
+ function apiBase(config) {
11808
+ return config.apiBaseUrl ?? DEFAULT_API_BASE_URL;
11809
+ }
11810
+ function teamQuery(config) {
11811
+ return `?teamId=${encodeURIComponent(config.teamId)}`;
11812
+ }
11813
+ function drainDelivery(config, otelToken) {
11814
+ return {
11815
+ type: "http",
11816
+ endpoint: config.endpoint,
11817
+ encoding: "json",
11818
+ headers: bearerAuthHeader(otelToken),
11819
+ ...config.secret ? { secret: config.secret } : {}
11820
+ };
11821
+ }
11822
+ async function describeError(res) {
11823
+ try {
11824
+ const data = await res.json();
11825
+ const message = data?.error?.message;
11826
+ return typeof message === "string" && message.length > 0 ? ` \u2014 ${message}` : "";
11827
+ } catch {
11828
+ return "";
11829
+ }
11830
+ }
11831
+ async function createVercelDrain(config, credentials, fetchImpl = fetch) {
11832
+ const projectIds = config.projectIds ?? [];
11833
+ const body = {
11834
+ name: config.drainName ?? DEFAULT_DRAIN_NAME,
11835
+ projects: projectIds.length > 0 ? "some" : "all",
11836
+ ...projectIds.length > 0 ? { projectIds } : {},
11837
+ schemas: TRACE_SCHEMAS,
11838
+ delivery: drainDelivery(config, credentials.otelToken),
11839
+ source: { kind: "self-served" }
11840
+ };
11841
+ const res = await junctionFetch(
11842
+ `${apiBase(config)}/v1/drains${teamQuery(config)}`,
11843
+ {
11844
+ method: "POST",
11845
+ headers: { "Content-Type": "application/json", ...bearerAuthHeader(credentials.token) },
11846
+ body: JSON.stringify(body)
11847
+ },
11848
+ { provider: "vercel", accountKey: config.teamId, fetchImpl }
11849
+ );
11850
+ if (!res.ok) {
11851
+ throw new Error(
11852
+ `vercel connector: create drain failed (${res.status} ${res.statusText}${await describeError(res)})`
11853
+ );
11854
+ }
11855
+ const payload = await res.json().catch(() => null);
11856
+ if (!payload || typeof payload.id !== "string" || payload.id.length === 0) {
11857
+ throw new Error(
11858
+ "vercel connector: create drain returned no drain id \u2014 the Drains API response shape may have changed"
11859
+ );
11860
+ }
11861
+ return {
11862
+ id: payload.id,
11863
+ ...payload.status ? { status: payload.status } : {},
11864
+ ...payload.disabledReason ? { disabledReason: payload.disabledReason } : {}
11865
+ };
11866
+ }
11867
+ async function deleteVercelDrain(config, drainId, credentials, fetchImpl = fetch) {
11868
+ const res = await junctionFetch(
11869
+ `${apiBase(config)}/v1/drains/${encodeURIComponent(drainId)}${teamQuery(config)}`,
11870
+ { method: "DELETE", headers: { ...bearerAuthHeader(credentials.token) } },
11871
+ { provider: "vercel", accountKey: config.teamId, fetchImpl }
11872
+ );
11873
+ if (res.ok || res.status === 404) return;
11874
+ throw new Error(
11875
+ `vercel connector: delete drain failed (${res.status} ${res.statusText}${await describeError(res)})`
11876
+ );
11877
+ }
11878
+ async function testVercelDrainDelivery(config, credentials, fetchImpl = fetch) {
11879
+ const res = await junctionFetch(
11880
+ `${apiBase(config)}/v1/drains/test${teamQuery(config)}`,
11881
+ {
11882
+ method: "POST",
11883
+ headers: { "Content-Type": "application/json", ...bearerAuthHeader(credentials.token) },
11884
+ body: JSON.stringify({ schemas: TRACE_SCHEMAS, delivery: drainDelivery(config, credentials.otelToken) })
11885
+ },
11886
+ { provider: "vercel", accountKey: config.teamId, fetchImpl }
11887
+ );
11888
+ if (res.status === 401 || res.status === 403) {
11889
+ return { status: "failure", error: `vercel rejected the API token (HTTP ${res.status})` };
11890
+ }
11891
+ if (!res.ok) {
11892
+ return {
11893
+ status: "failure",
11894
+ error: `vercel drain validation failed (${res.status} ${res.statusText}${await describeError(res)})`
11895
+ };
11896
+ }
11897
+ const payload = await res.json().catch(() => null);
11898
+ return {
11899
+ ...payload?.status ? { status: payload.status } : {},
11900
+ ...payload?.error ? { error: payload.error } : {},
11901
+ ...payload?.endpoint ? { endpoint: payload.endpoint } : {}
11902
+ };
11903
+ }
11904
+
11427
11905
  // src/connectors/registry.ts
11428
11906
  var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
11429
11907
  async function authProbe(input) {
@@ -11560,23 +12038,117 @@ var PROVIDER_DISPATCH = {
11560
12038
  resolveTarget: createCloudflareResolveTarget(config, graph)
11561
12039
  };
11562
12040
  },
11563
- // GET /user/tokens/verify — Cloudflare's own purpose-built "is this API
11564
- // token live" endpoint. 200 on a valid token, 401 on an invalid one.
12041
+ // GET /accounts/{accountId}/tokens/verify — the *account-scoped* token-verify
12042
+ // endpoint. A Workers connector token is scoped to the account, and the
12043
+ // user-level `GET /user/tokens/verify` returns 401 "Invalid API Token" for
12044
+ // such a token even though it authenticates fine against the account's own
12045
+ // resources (confirmed live). Probing the account-scoped verify endpoint —
12046
+ // `accountId` is already required for this provider — returns 200
12047
+ // `{status:"active"}` for a working token and 401 for a bad one, so a valid
12048
+ // Workers token is no longer falsely rejected at `neat connector add`.
11565
12049
  validate({ credentials, options, fetchImpl }) {
11566
12050
  const cfg = options;
11567
12051
  const baseUrl = cfg.baseUrl ?? CLOUDFLARE_API_BASE_URL;
11568
12052
  return authProbe({
11569
12053
  provider: "cloudflare",
11570
12054
  accountKey: cfg.accountId ?? "validate",
11571
- url: `${baseUrl}/user/tokens/verify`,
12055
+ url: `${baseUrl}/accounts/${cfg.accountId ?? ""}/tokens/verify`,
11572
12056
  token: String(credentials.apiToken ?? ""),
11573
12057
  ...fetchImpl ? { fetchImpl } : {}
11574
12058
  });
11575
12059
  }
11576
12060
  }
11577
12061
  };
11578
- function getProviderDispatch(provider) {
11579
- return PROVIDER_DISPATCH[provider];
12062
+ function vercelCredsFrom(credentials) {
12063
+ return { token: String(credentials.token ?? ""), otelToken: String(credentials.otelToken ?? "") };
12064
+ }
12065
+ function vercelConfigFromOptions(options) {
12066
+ const raw = options.projectIds;
12067
+ let projectIds;
12068
+ if (Array.isArray(raw)) projectIds = raw.filter((p) => typeof p === "string");
12069
+ else if (typeof raw === "string" && raw.trim().length > 0) {
12070
+ projectIds = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
12071
+ }
12072
+ return {
12073
+ teamId: String(options.teamId ?? ""),
12074
+ endpoint: String(options.endpoint ?? ""),
12075
+ ...projectIds && projectIds.length > 0 ? { projectIds } : {},
12076
+ ...typeof options.drainId === "string" ? { drainId: options.drainId } : {},
12077
+ ...typeof options.drainName === "string" ? { drainName: options.drainName } : {},
12078
+ ...typeof options.apiBaseUrl === "string" ? { apiBaseUrl: options.apiBaseUrl } : {},
12079
+ ...typeof options.secret === "string" ? { secret: options.secret } : {}
12080
+ };
12081
+ }
12082
+ var PUSH_PROVIDER_DISPATCH = {
12083
+ vercel: {
12084
+ provider: "vercel",
12085
+ // The Vercel access token is the "primary" secret a single `--token`
12086
+ // populates; `otelToken` (the daemon's OTLP bearer) is the second field.
12087
+ primaryCredentialKey: "token",
12088
+ requiredCredentialFields: ["token", "otelToken"],
12089
+ // teamId scopes every Drains call; endpoint is where the drain delivers.
12090
+ // projectIds is optional (absent → the drain covers the whole team).
12091
+ requiredOptionFields: ["teamId", "endpoint"],
12092
+ // POST /v1/drains/test — authenticates the token and pings the endpoint
12093
+ // with a sample event, so `success` means the credential is live *and* the
12094
+ // daemon's OTLP endpoint is reachable and accepted the drain's bearer.
12095
+ async validate({ credentials, options, fetchImpl }) {
12096
+ const result = await testVercelDrainDelivery(
12097
+ vercelConfigFromOptions(options),
12098
+ vercelCredsFrom(credentials),
12099
+ fetchImpl
12100
+ );
12101
+ if (result.status === "success") return { ok: true };
12102
+ return {
12103
+ ok: false,
12104
+ reason: result.error ?? `vercel drain delivery test returned "${result.status ?? "no status"}"`
12105
+ };
12106
+ },
12107
+ // POST /v1/drains — creates the trace drain, returns its id to store in
12108
+ // `options.drainId`. A created-but-not-enabled drain is surfaced as a note,
12109
+ // not a failure (the entry still points at a real drain).
12110
+ async provision({ credentials, options, fetchImpl }) {
12111
+ try {
12112
+ const created = await createVercelDrain(
12113
+ vercelConfigFromOptions(options),
12114
+ vercelCredsFrom(credentials),
12115
+ fetchImpl
12116
+ );
12117
+ 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;
12118
+ return { ok: true, options: { drainId: created.id }, ...note ? { note } : {} };
12119
+ } catch (err) {
12120
+ return { ok: false, reason: err.message };
12121
+ }
12122
+ },
12123
+ // DELETE /v1/drains/{id} — idempotent (deleteVercelDrain treats 404 as
12124
+ // success). No recorded drainId → nothing to delete, still a success.
12125
+ async deprovision({ credentials, options, fetchImpl }) {
12126
+ const drainId = typeof options.drainId === "string" ? options.drainId : "";
12127
+ if (!drainId) {
12128
+ return { ok: true, note: "no drain id was recorded \u2014 nothing to delete on the Vercel side" };
12129
+ }
12130
+ try {
12131
+ await deleteVercelDrain(
12132
+ vercelConfigFromOptions(options),
12133
+ drainId,
12134
+ vercelCredsFrom(credentials),
12135
+ fetchImpl
12136
+ );
12137
+ return { ok: true };
12138
+ } catch (err) {
12139
+ return { ok: false, reason: err.message };
12140
+ }
12141
+ }
12142
+ }
12143
+ };
12144
+ function isPushProvider(provider) {
12145
+ return provider in PUSH_PROVIDER_DISPATCH;
12146
+ }
12147
+ function getProviderFieldSchema(provider) {
12148
+ return PROVIDER_DISPATCH[provider] ?? PUSH_PROVIDER_DISPATCH[provider];
12149
+ }
12150
+ function knownProviderNames() {
12151
+ return [...Object.keys(PROVIDER_DISPATCH), ...Object.keys(PUSH_PROVIDER_DISPATCH)].sort();
11580
12152
  }
11581
12153
  function resolveEntryCredentials(dispatch, entry2, env) {
11582
12154
  let credentials;
@@ -11598,7 +12170,7 @@ function resolveEntryCredentials(dispatch, entry2, env) {
11598
12170
  return { ok: true, credentials };
11599
12171
  }
11600
12172
  async function validateConnectorEntry(entry2, env = process.env, fetchImpl) {
11601
- const dispatch = PROVIDER_DISPATCH[entry2.provider];
12173
+ const dispatch = PROVIDER_DISPATCH[entry2.provider] ?? PUSH_PROVIDER_DISPATCH[entry2.provider];
11602
12174
  if (!dispatch) {
11603
12175
  return { status: "unknown-provider", reason: `unknown provider "${entry2.provider}"` };
11604
12176
  }
@@ -11622,6 +12194,54 @@ async function validateConnectorEntry(entry2, env = process.env, fetchImpl) {
11622
12194
  });
11623
12195
  return result.ok ? { status: "ok" } : { status: "rejected", reason: result.reason };
11624
12196
  }
12197
+ function resolvePushEntry(entry2, env) {
12198
+ const dispatch = PUSH_PROVIDER_DISPATCH[entry2.provider];
12199
+ if (!dispatch) {
12200
+ return PROVIDER_DISPATCH[entry2.provider] ? {
12201
+ ok: false,
12202
+ outcome: {
12203
+ status: "not-push",
12204
+ reason: `provider "${entry2.provider}" is polled, not provisioned \u2014 there is no drain to manage`
12205
+ }
12206
+ } : { ok: false, outcome: { status: "unknown-provider", reason: `unknown provider "${entry2.provider}"` } };
12207
+ }
12208
+ const creds = resolveEntryCredentials(dispatch, entry2, env);
12209
+ if (!creds.ok) {
12210
+ const status2 = creds.kind === "unset-env" ? "unset-env" : creds.kind === "missing-field" ? "missing-field" : "failed";
12211
+ return { ok: false, outcome: { status: status2, reason: creds.reason } };
12212
+ }
12213
+ const options = entry2.options ?? {};
12214
+ const missingOpts = dispatch.requiredOptionFields.filter((k) => !(k in options));
12215
+ if (missingOpts.length > 0) {
12216
+ return {
12217
+ ok: false,
12218
+ outcome: { status: "missing-field", reason: `options missing required field(s): ${missingOpts.join(", ")}` }
12219
+ };
12220
+ }
12221
+ return { ok: true, dispatch, credentials: creds.credentials, options };
12222
+ }
12223
+ async function provisionConnector(entry2, env = process.env, fetchImpl) {
12224
+ const resolved = resolvePushEntry(entry2, env);
12225
+ if (!resolved.ok) return resolved.outcome;
12226
+ const result = await resolved.dispatch.provision({
12227
+ credentials: resolved.credentials,
12228
+ options: resolved.options,
12229
+ ...fetchImpl ? { fetchImpl } : {}
12230
+ });
12231
+ if (!result.ok) return { status: "failed", reason: result.reason };
12232
+ return { status: "ok", ...result.options ? { options: result.options } : {}, ...result.note ? { note: result.note } : {} };
12233
+ }
12234
+ async function deprovisionConnector(entry2, env = process.env, fetchImpl) {
12235
+ const resolved = resolvePushEntry(entry2, env);
12236
+ if (!resolved.ok) return resolved.outcome;
12237
+ const result = await resolved.dispatch.deprovision({
12238
+ credentials: resolved.credentials,
12239
+ options: resolved.options,
12240
+ ...fetchImpl ? { fetchImpl } : {}
12241
+ });
12242
+ if (!result.ok) return { status: "failed", reason: result.reason };
12243
+ return { status: "ok", ...result.note ? { note: result.note } : {} };
12244
+ }
11625
12245
 
11626
12246
  // src/daemon.ts
11627
12247
  init_auth();
@@ -11629,28 +12249,28 @@ init_auth();
11629
12249
  // src/unrouted.ts
11630
12250
  init_cjs_shims();
11631
12251
  var import_node_fs32 = require("fs");
11632
- var import_node_path51 = __toESM(require("path"), 1);
12252
+ var import_node_path52 = __toESM(require("path"), 1);
11633
12253
 
11634
12254
  // src/daemon.ts
11635
- var import_types47 = require("@neat.is/types");
12255
+ var import_types48 = require("@neat.is/types");
11636
12256
  function daemonJsonPath(scanPath) {
11637
- return import_node_path52.default.join(scanPath, "neat-out", "daemon.json");
12257
+ return import_node_path53.default.join(scanPath, "neat-out", "daemon.json");
11638
12258
  }
11639
12259
  function daemonsDiscoveryDir(home) {
11640
12260
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
11641
- return import_node_path52.default.join(base, "daemons");
12261
+ return import_node_path53.default.join(base, "daemons");
11642
12262
  }
11643
12263
  function daemonDiscoveryPath(project, home) {
11644
- return import_node_path52.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
12264
+ return import_node_path53.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
11645
12265
  }
11646
12266
  function sanitizeDiscoveryName(project) {
11647
12267
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
11648
12268
  }
11649
12269
  function neatHomeFromEnv() {
11650
12270
  const env = process.env.NEAT_HOME;
11651
- if (env && env.length > 0) return import_node_path52.default.resolve(env);
12271
+ if (env && env.length > 0) return import_node_path53.default.resolve(env);
11652
12272
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
11653
- return import_node_path52.default.join(home, ".neat");
12273
+ return import_node_path53.default.join(home, ".neat");
11654
12274
  }
11655
12275
  async function readDaemonRecord(scanPath) {
11656
12276
  try {
@@ -11721,7 +12341,7 @@ init_otel_grpc();
11721
12341
  // src/search.ts
11722
12342
  init_cjs_shims();
11723
12343
  var import_node_fs34 = require("fs");
11724
- var import_node_path53 = __toESM(require("path"), 1);
12344
+ var import_node_path54 = __toESM(require("path"), 1);
11725
12345
  var import_node_crypto4 = require("crypto");
11726
12346
  var DEFAULT_LIMIT = 10;
11727
12347
  var NOMIC_DIM = 768;
@@ -11884,7 +12504,7 @@ async function readCache(cachePath) {
11884
12504
  }
11885
12505
  }
11886
12506
  async function writeCache(cachePath, cache) {
11887
- await import_node_fs34.promises.mkdir(import_node_path53.default.dirname(cachePath), { recursive: true });
12507
+ await import_node_fs34.promises.mkdir(import_node_path54.default.dirname(cachePath), { recursive: true });
11888
12508
  await import_node_fs34.promises.writeFile(cachePath, JSON.stringify(cache));
11889
12509
  }
11890
12510
  var VectorIndex = class {
@@ -12042,8 +12662,8 @@ var ALL_PHASES = [
12042
12662
  ];
12043
12663
  function classifyChange(relPath) {
12044
12664
  const phases = /* @__PURE__ */ new Set();
12045
- const base = import_node_path54.default.basename(relPath).toLowerCase();
12046
- const segments = relPath.split(import_node_path54.default.sep).map((s) => s.toLowerCase());
12665
+ const base = import_node_path55.default.basename(relPath).toLowerCase();
12666
+ const segments = relPath.split(import_node_path55.default.sep).map((s) => s.toLowerCase());
12047
12667
  if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
12048
12668
  phases.add("services");
12049
12669
  phases.add("aliases");
@@ -12171,9 +12791,9 @@ function countWatchableDirs(scanPath, limit) {
12171
12791
  for (const e of entries) {
12172
12792
  if (count >= limit) return;
12173
12793
  if (!e.isDirectory()) continue;
12174
- if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path54.default.join(dir, e.name) + import_node_path54.default.sep))) continue;
12794
+ if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path55.default.join(dir, e.name) + import_node_path55.default.sep))) continue;
12175
12795
  count++;
12176
- if (depth < 2) visit(import_node_path54.default.join(dir, e.name), depth + 1);
12796
+ if (depth < 2) visit(import_node_path55.default.join(dir, e.name), depth + 1);
12177
12797
  }
12178
12798
  };
12179
12799
  visit(scanPath, 0);
@@ -12191,8 +12811,8 @@ async function startWatch(graph, opts) {
12191
12811
  const projectName = opts.project ?? DEFAULT_PROJECT;
12192
12812
  await loadGraphFromDisk(graph, opts.outPath);
12193
12813
  const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
12194
- const policyFilePath = import_node_path54.default.join(opts.scanPath, "policy.json");
12195
- const policyViolationsPath = import_node_path54.default.join(import_node_path54.default.dirname(opts.outPath), "policy-violations.ndjson");
12814
+ const policyFilePath = import_node_path55.default.join(opts.scanPath, "policy.json");
12815
+ const policyViolationsPath = import_node_path55.default.join(import_node_path55.default.dirname(opts.outPath), "policy-violations.ndjson");
12196
12816
  let policies = [];
12197
12817
  try {
12198
12818
  policies = await loadPolicyFile(policyFilePath);
@@ -12243,7 +12863,7 @@ async function startWatch(graph, opts) {
12243
12863
  assertBindAuthority(host, auth.authToken);
12244
12864
  const port = opts.port ?? 8080;
12245
12865
  const otelPort = opts.otelPort ?? 4318;
12246
- const cachePath = opts.embeddingsCachePath ?? import_node_path54.default.join(import_node_path54.default.dirname(opts.outPath), "embeddings.json");
12866
+ const cachePath = opts.embeddingsCachePath ?? import_node_path55.default.join(import_node_path55.default.dirname(opts.outPath), "embeddings.json");
12247
12867
  let searchIndex;
12248
12868
  try {
12249
12869
  searchIndex = await buildSearchIndex(graph, { cachePath });
@@ -12261,7 +12881,7 @@ async function startWatch(graph, opts) {
12261
12881
  // Paths are derived from the explicit options the watch caller passes
12262
12882
  // — pathsForProject is only used to fill in the embeddings/snapshot
12263
12883
  // fields so the registry shape is complete.
12264
- ...pathsForProject(projectName, import_node_path54.default.dirname(opts.outPath)),
12884
+ ...pathsForProject(projectName, import_node_path55.default.dirname(opts.outPath)),
12265
12885
  snapshotPath: opts.outPath,
12266
12886
  errorsPath: opts.errorsPath,
12267
12887
  staleEventsPath: opts.staleEventsPath
@@ -12379,9 +12999,9 @@ async function startWatch(graph, opts) {
12379
12999
  };
12380
13000
  const onPath = (absPath) => {
12381
13001
  if (shouldIgnore(absPath)) return;
12382
- const rel = import_node_path54.default.relative(opts.scanPath, absPath);
13002
+ const rel = import_node_path55.default.relative(opts.scanPath, absPath);
12383
13003
  if (!rel || rel.startsWith("..")) return;
12384
- pendingPaths.add(rel.split(import_node_path54.default.sep).join("/"));
13004
+ pendingPaths.add(rel.split(import_node_path55.default.sep).join("/"));
12385
13005
  const phases = classifyChange(rel);
12386
13006
  if (phases.size === 0) {
12387
13007
  for (const p of ALL_PHASES) pending.add(p);
@@ -12438,7 +13058,7 @@ async function startWatch(graph, opts) {
12438
13058
  // src/deploy/detect.ts
12439
13059
  init_cjs_shims();
12440
13060
  var import_node_fs36 = require("fs");
12441
- var import_node_path55 = __toESM(require("path"), 1);
13061
+ var import_node_path56 = __toESM(require("path"), 1);
12442
13062
  var import_node_child_process2 = require("child_process");
12443
13063
  var import_node_crypto5 = require("crypto");
12444
13064
  function generateToken() {
@@ -12538,7 +13158,7 @@ async function runDeploy(opts = {}) {
12538
13158
  const token = generateToken();
12539
13159
  switch (substrate) {
12540
13160
  case "docker-compose": {
12541
- const artifactPath = import_node_path55.default.join(cwd, "docker-compose.neat.yml");
13161
+ const artifactPath = import_node_path56.default.join(cwd, "docker-compose.neat.yml");
12542
13162
  const contents = emitDockerCompose(cwd);
12543
13163
  await import_node_fs36.promises.writeFile(artifactPath, contents, "utf8");
12544
13164
  return {
@@ -12546,11 +13166,11 @@ async function runDeploy(opts = {}) {
12546
13166
  artifactPath,
12547
13167
  token,
12548
13168
  contents,
12549
- startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path55.default.basename(artifactPath)} up -d`
13169
+ startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path56.default.basename(artifactPath)} up -d`
12550
13170
  };
12551
13171
  }
12552
13172
  case "systemd": {
12553
- const artifactPath = import_node_path55.default.join(cwd, "neat.service");
13173
+ const artifactPath = import_node_path56.default.join(cwd, "neat.service");
12554
13174
  const contents = emitSystemdUnit(cwd);
12555
13175
  await import_node_fs36.promises.writeFile(artifactPath, contents, "utf8");
12556
13176
  return {
@@ -12586,7 +13206,7 @@ init_cjs_shims();
12586
13206
  // src/installers/javascript.ts
12587
13207
  init_cjs_shims();
12588
13208
  var import_node_fs37 = require("fs");
12589
- var import_node_path56 = __toESM(require("path"), 1);
13209
+ var import_node_path57 = __toESM(require("path"), 1);
12590
13210
  var import_semver2 = __toESM(require("semver"), 1);
12591
13211
 
12592
13212
  // src/installers/templates.ts
@@ -12964,9 +13584,12 @@ ${OTEL_ENDPOINT_RESOLVER_ESM}
12964
13584
  ${OTEL_OTLP_PROTOCOL_JS}
12965
13585
  ${OTEL_OTLP_HEADERS_JS}
12966
13586
 
12967
- import { NodeSDK } from '@opentelemetry/sdk-node'
12968
- import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
12969
- import { trace, context } from '@opentelemetry/api'
13587
+ // Keep dependency loading inside the guard. Static ESM imports are evaluated
13588
+ // before any try/catch and would crash the host app when an install failed.
13589
+ try {
13590
+ const { NodeSDK } = await import('@opentelemetry/sdk-node')
13591
+ const { getNodeAutoInstrumentations } = await import('@opentelemetry/auto-instrumentations-node')
13592
+ const { trace, context } = await import('@opentelemetry/api')
12970
13593
 
12971
13594
  ${CALLSITE_PROCESSOR_JS}
12972
13595
 
@@ -12975,6 +13598,14 @@ __INSTRUMENTATION_BLOCK__
12975
13598
  const sdk = new NodeSDK({ instrumentations })
12976
13599
  sdk.start()
12977
13600
  ${neatWireCaptureSource(false)}
13601
+ } catch (__neatOtelErr) {
13602
+ const __neatMsg = String((__neatOtelErr && __neatOtelErr.message) || __neatOtelErr)
13603
+ if (/Cannot find (?:module|package)|MODULE_NOT_FOUND|ERR_MODULE_NOT_FOUND/.test(__neatMsg)) {
13604
+ console.warn('[neat] OpenTelemetry is not active: its packages are not installed, so this app is running without OBSERVED tracing. Run your package manager install and restart to enable it.')
13605
+ } else {
13606
+ console.warn('[neat] OpenTelemetry failed to start; the app is running without OBSERVED tracing: ' + __neatMsg)
13607
+ }
13608
+ }
12978
13609
  `;
12979
13610
  var OTEL_INIT_TS = `${OTEL_INIT_HEADER}
12980
13611
  ${OTEL_INIT_STAMP}
@@ -12989,9 +13620,12 @@ ${OTEL_ENDPOINT_RESOLVER_ESM}
12989
13620
  ${OTEL_OTLP_PROTOCOL_JS}
12990
13621
  ${OTEL_OTLP_HEADERS_JS}
12991
13622
 
12992
- import { NodeSDK } from '@opentelemetry/sdk-node'
12993
- import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
12994
- import { trace, context } from '@opentelemetry/api'
13623
+ // Top-level await preserves the static import's ordering: the host entry does
13624
+ // not run until instrumentation has started (or safely degraded).
13625
+ try {
13626
+ const { NodeSDK } = await import('@opentelemetry/sdk-node')
13627
+ const { getNodeAutoInstrumentations } = await import('@opentelemetry/auto-instrumentations-node')
13628
+ const { trace, context } = await import('@opentelemetry/api')
12995
13629
 
12996
13630
  ${CALLSITE_PROCESSOR_TS}
12997
13631
 
@@ -13000,6 +13634,14 @@ __INSTRUMENTATION_BLOCK__
13000
13634
  const sdk = new NodeSDK({ instrumentations })
13001
13635
  sdk.start()
13002
13636
  ${neatWireCaptureSource(true)}
13637
+ } catch (__neatOtelErr) {
13638
+ const __neatMsg = String((__neatOtelErr && __neatOtelErr.message) || __neatOtelErr)
13639
+ if (/Cannot find (?:module|package)|MODULE_NOT_FOUND|ERR_MODULE_NOT_FOUND/.test(__neatMsg)) {
13640
+ console.warn('[neat] OpenTelemetry is not active: its packages are not installed, so this app is running without OBSERVED tracing. Run your package manager install and restart to enable it.')
13641
+ } else {
13642
+ console.warn('[neat] OpenTelemetry failed to start; the app is running without OBSERVED tracing: ' + __neatMsg)
13643
+ }
13644
+ }
13003
13645
  `;
13004
13646
  function renderNodeOtelInit(template, serviceName, projectName, registrations = []) {
13005
13647
  const block = registrations.length === 0 ? "" : `
@@ -13217,11 +13859,11 @@ var OTEL_ENV = {
13217
13859
  value: "http://localhost:4318/projects/<project>/v1/traces"
13218
13860
  };
13219
13861
  function serviceNodeName(pkg, serviceDir) {
13220
- return pkg.name ?? import_node_path56.default.basename(serviceDir);
13862
+ return pkg.name ?? import_node_path57.default.basename(serviceDir);
13221
13863
  }
13222
13864
  function projectToken(pkg, serviceDir, project) {
13223
13865
  if (project && project.length > 0) return project;
13224
- return pkg.name ?? import_node_path56.default.basename(serviceDir);
13866
+ return pkg.name ?? import_node_path57.default.basename(serviceDir);
13225
13867
  }
13226
13868
  async function readJsonFile(p) {
13227
13869
  try {
@@ -13234,16 +13876,16 @@ async function readJsonFile(p) {
13234
13876
  async function detectRuntimeKind(pkgRoot, pkg) {
13235
13877
  const deps = allDeps(pkg);
13236
13878
  if ("react-native" in deps || "expo" in deps) return "react-native";
13237
- const appJson = await readJsonFile(import_node_path56.default.join(pkgRoot, "app.json"));
13879
+ const appJson = await readJsonFile(import_node_path57.default.join(pkgRoot, "app.json"));
13238
13880
  if (appJson && typeof appJson === "object" && "expo" in appJson) {
13239
13881
  return "react-native";
13240
13882
  }
13241
- if (await exists3(import_node_path56.default.join(pkgRoot, "vite.config.js")) || await exists3(import_node_path56.default.join(pkgRoot, "vite.config.ts")) || await exists3(import_node_path56.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
13883
+ if (await exists3(import_node_path57.default.join(pkgRoot, "vite.config.js")) || await exists3(import_node_path57.default.join(pkgRoot, "vite.config.ts")) || await exists3(import_node_path57.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
13242
13884
  return "browser-bundle";
13243
13885
  }
13244
- if (await exists3(import_node_path56.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
13245
- if (await exists3(import_node_path56.default.join(pkgRoot, "bun.lockb"))) return "bun";
13246
- if (await exists3(import_node_path56.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path56.default.join(pkgRoot, "deno.lock"))) {
13886
+ if (await exists3(import_node_path57.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
13887
+ if (await exists3(import_node_path57.default.join(pkgRoot, "bun.lockb"))) return "bun";
13888
+ if (await exists3(import_node_path57.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path57.default.join(pkgRoot, "deno.lock"))) {
13247
13889
  return "deno";
13248
13890
  }
13249
13891
  const engines = pkg.engines ?? {};
@@ -13252,7 +13894,7 @@ async function detectRuntimeKind(pkgRoot, pkg) {
13252
13894
  }
13253
13895
  async function readPackageJson2(serviceDir) {
13254
13896
  try {
13255
- const raw = await import_node_fs37.promises.readFile(import_node_path56.default.join(serviceDir, "package.json"), "utf8");
13897
+ const raw = await import_node_fs37.promises.readFile(import_node_path57.default.join(serviceDir, "package.json"), "utf8");
13256
13898
  return JSON.parse(raw);
13257
13899
  } catch {
13258
13900
  return null;
@@ -13296,7 +13938,7 @@ function needsVersionUpgrade(installed, expected) {
13296
13938
  var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
13297
13939
  async function findNextConfig(serviceDir) {
13298
13940
  for (const name of NEXT_CONFIG_CANDIDATES) {
13299
- const candidate = import_node_path56.default.join(serviceDir, name);
13941
+ const candidate = import_node_path57.default.join(serviceDir, name);
13300
13942
  if (await exists3(candidate)) return candidate;
13301
13943
  }
13302
13944
  return null;
@@ -13365,7 +14007,7 @@ function hasRemixDependency(pkg) {
13365
14007
  }
13366
14008
  async function findRemixEntry(serviceDir) {
13367
14009
  for (const rel of REMIX_ENTRY_CANDIDATES) {
13368
- const candidate = import_node_path56.default.join(serviceDir, rel);
14010
+ const candidate = import_node_path57.default.join(serviceDir, rel);
13369
14011
  if (await exists3(candidate)) return candidate;
13370
14012
  }
13371
14013
  return null;
@@ -13377,14 +14019,14 @@ function hasSvelteKitDependency(pkg) {
13377
14019
  }
13378
14020
  async function findSvelteKitHooks(serviceDir) {
13379
14021
  for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
13380
- const candidate = import_node_path56.default.join(serviceDir, rel);
14022
+ const candidate = import_node_path57.default.join(serviceDir, rel);
13381
14023
  if (await exists3(candidate)) return candidate;
13382
14024
  }
13383
14025
  return null;
13384
14026
  }
13385
14027
  async function findSvelteKitConfig(serviceDir) {
13386
14028
  for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
13387
- const candidate = import_node_path56.default.join(serviceDir, rel);
14029
+ const candidate = import_node_path57.default.join(serviceDir, rel);
13388
14030
  if (await exists3(candidate)) return candidate;
13389
14031
  }
13390
14032
  return null;
@@ -13395,7 +14037,7 @@ function hasNuxtDependency(pkg) {
13395
14037
  }
13396
14038
  async function findNuxtConfig(serviceDir) {
13397
14039
  for (const name of NUXT_CONFIG_CANDIDATES) {
13398
- const candidate = import_node_path56.default.join(serviceDir, name);
14040
+ const candidate = import_node_path57.default.join(serviceDir, name);
13399
14041
  if (await exists3(candidate)) return candidate;
13400
14042
  }
13401
14043
  return null;
@@ -13406,7 +14048,7 @@ function hasAstroDependency(pkg) {
13406
14048
  }
13407
14049
  async function findAstroConfig(serviceDir) {
13408
14050
  for (const name of ASTRO_CONFIG_CANDIDATES) {
13409
- const candidate = import_node_path56.default.join(serviceDir, name);
14051
+ const candidate = import_node_path57.default.join(serviceDir, name);
13410
14052
  if (await exists3(candidate)) return candidate;
13411
14053
  }
13412
14054
  return null;
@@ -13420,7 +14062,7 @@ function parseNextMajor(range) {
13420
14062
  return Number.isFinite(n) ? n : null;
13421
14063
  }
13422
14064
  async function isTypeScriptProject(serviceDir) {
13423
- return exists3(import_node_path56.default.join(serviceDir, "tsconfig.json"));
14065
+ return exists3(import_node_path57.default.join(serviceDir, "tsconfig.json"));
13424
14066
  }
13425
14067
  var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
13426
14068
  var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
@@ -13469,7 +14111,7 @@ function entryFromScript(script) {
13469
14111
  }
13470
14112
  async function resolveEntry(serviceDir, pkg) {
13471
14113
  if (typeof pkg.main === "string" && pkg.main.length > 0) {
13472
- const candidate = import_node_path56.default.resolve(serviceDir, pkg.main);
14114
+ const candidate = import_node_path57.default.resolve(serviceDir, pkg.main);
13473
14115
  if (await exists3(candidate)) return candidate;
13474
14116
  }
13475
14117
  if (pkg.bin) {
@@ -13483,40 +14125,40 @@ async function resolveEntry(serviceDir, pkg) {
13483
14125
  if (typeof first === "string") binEntry = first;
13484
14126
  }
13485
14127
  if (binEntry) {
13486
- const candidate = import_node_path56.default.resolve(serviceDir, binEntry);
14128
+ const candidate = import_node_path57.default.resolve(serviceDir, binEntry);
13487
14129
  if (await exists3(candidate)) return candidate;
13488
14130
  }
13489
14131
  }
13490
14132
  const startEntry = entryFromScript(pkg.scripts?.start);
13491
14133
  if (startEntry) {
13492
- const candidate = import_node_path56.default.resolve(serviceDir, startEntry);
14134
+ const candidate = import_node_path57.default.resolve(serviceDir, startEntry);
13493
14135
  if (await exists3(candidate)) return candidate;
13494
14136
  }
13495
14137
  const devEntry = entryFromScript(pkg.scripts?.dev);
13496
14138
  if (devEntry) {
13497
- const candidate = import_node_path56.default.resolve(serviceDir, devEntry);
14139
+ const candidate = import_node_path57.default.resolve(serviceDir, devEntry);
13498
14140
  if (await exists3(candidate)) return candidate;
13499
14141
  }
13500
14142
  for (const rel of SRC_INDEX_CANDIDATES) {
13501
- const candidate = import_node_path56.default.join(serviceDir, rel);
14143
+ const candidate = import_node_path57.default.join(serviceDir, rel);
13502
14144
  if (await exists3(candidate)) return candidate;
13503
14145
  }
13504
14146
  for (const rel of SRC_NAMED_CANDIDATES) {
13505
- const candidate = import_node_path56.default.join(serviceDir, rel);
14147
+ const candidate = import_node_path57.default.join(serviceDir, rel);
13506
14148
  if (await exists3(candidate)) return candidate;
13507
14149
  }
13508
14150
  for (const rel of ROOT_NAMED_CANDIDATES) {
13509
- const candidate = import_node_path56.default.join(serviceDir, rel);
14151
+ const candidate = import_node_path57.default.join(serviceDir, rel);
13510
14152
  if (await exists3(candidate)) return candidate;
13511
14153
  }
13512
14154
  for (const name of INDEX_CANDIDATES) {
13513
- const candidate = import_node_path56.default.join(serviceDir, name);
14155
+ const candidate = import_node_path57.default.join(serviceDir, name);
13514
14156
  if (await exists3(candidate)) return candidate;
13515
14157
  }
13516
14158
  return null;
13517
14159
  }
13518
14160
  function dispatchEntry(entryFile, pkg) {
13519
- const ext = import_node_path56.default.extname(entryFile).toLowerCase();
14161
+ const ext = import_node_path57.default.extname(entryFile).toLowerCase();
13520
14162
  if (ext === ".ts" || ext === ".tsx") return "ts";
13521
14163
  if (ext === ".mjs") return "esm";
13522
14164
  if (ext === ".cjs") return "cjs";
@@ -13533,9 +14175,9 @@ function otelInitContents(flavor) {
13533
14175
  return OTEL_INIT_CJS;
13534
14176
  }
13535
14177
  function injectionLine(flavor, entryFile, otelInitFile) {
13536
- let rel = import_node_path56.default.relative(import_node_path56.default.dirname(entryFile), otelInitFile);
14178
+ let rel = import_node_path57.default.relative(import_node_path57.default.dirname(entryFile), otelInitFile);
13537
14179
  if (!rel.startsWith(".")) rel = `./${rel}`;
13538
- rel = rel.split(import_node_path56.default.sep).join("/");
14180
+ rel = rel.split(import_node_path57.default.sep).join("/");
13539
14181
  if (flavor === "cjs") return `require('${rel}')`;
13540
14182
  if (flavor === "esm") return `import '${rel}'`;
13541
14183
  const tsRel = rel.replace(/\.ts$/, "");
@@ -13548,27 +14190,27 @@ function lineIsOtelInjection(line) {
13548
14190
  }
13549
14191
  async function detectsSrcLayout(serviceDir) {
13550
14192
  const [hasSrcApp, hasSrcPages, hasRootApp, hasRootPages] = await Promise.all([
13551
- exists3(import_node_path56.default.join(serviceDir, "src", "app")),
13552
- exists3(import_node_path56.default.join(serviceDir, "src", "pages")),
13553
- exists3(import_node_path56.default.join(serviceDir, "app")),
13554
- exists3(import_node_path56.default.join(serviceDir, "pages"))
14193
+ exists3(import_node_path57.default.join(serviceDir, "src", "app")),
14194
+ exists3(import_node_path57.default.join(serviceDir, "src", "pages")),
14195
+ exists3(import_node_path57.default.join(serviceDir, "app")),
14196
+ exists3(import_node_path57.default.join(serviceDir, "pages"))
13555
14197
  ]);
13556
14198
  return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
13557
14199
  }
13558
14200
  async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
13559
14201
  const useTs = await isTypeScriptProject(serviceDir);
13560
14202
  const srcLayout = await detectsSrcLayout(serviceDir);
13561
- const baseDir = srcLayout ? import_node_path56.default.join(serviceDir, "src") : serviceDir;
13562
- const instrumentationFile = import_node_path56.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
13563
- const instrumentationNodeFile = import_node_path56.default.join(
14203
+ const baseDir = srcLayout ? import_node_path57.default.join(serviceDir, "src") : serviceDir;
14204
+ const instrumentationFile = import_node_path57.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
14205
+ const instrumentationNodeFile = import_node_path57.default.join(
13564
14206
  baseDir,
13565
14207
  useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
13566
14208
  );
13567
- const instrumentationEdgeFile = import_node_path56.default.join(
14209
+ const instrumentationEdgeFile = import_node_path57.default.join(
13568
14210
  baseDir,
13569
14211
  useTs ? "instrumentation.edge.ts" : "instrumentation.edge.js"
13570
14212
  );
13571
- const envNeatFile = import_node_path56.default.join(baseDir, ".env.neat");
14213
+ const envNeatFile = import_node_path57.default.join(baseDir, ".env.neat");
13572
14214
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
13573
14215
  const dependencyEdits = [];
13574
14216
  for (const sdk of SDK_PACKAGES) {
@@ -13693,7 +14335,7 @@ function buildDependencyEdits(pkg, manifestPath) {
13693
14335
  return edits;
13694
14336
  }
13695
14337
  async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
13696
- const envNeatFile = import_node_path56.default.join(serviceDir, ".env.neat");
14338
+ const envNeatFile = import_node_path57.default.join(serviceDir, ".env.neat");
13697
14339
  if (!await exists3(envNeatFile)) {
13698
14340
  generatedFiles.push({
13699
14341
  file: envNeatFile,
@@ -13728,7 +14370,7 @@ function fileImportsOtelHook(raw, specifiers) {
13728
14370
  }
13729
14371
  async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
13730
14372
  const useTs = await isTypeScriptProject(serviceDir);
13731
- const otelServerFile = import_node_path56.default.join(
14373
+ const otelServerFile = import_node_path57.default.join(
13732
14374
  serviceDir,
13733
14375
  useTs ? "app/otel.server.ts" : "app/otel.server.js"
13734
14376
  );
@@ -13785,11 +14427,11 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
13785
14427
  }
13786
14428
  async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
13787
14429
  const useTs = await isTypeScriptProject(serviceDir);
13788
- const otelInitFile = import_node_path56.default.join(
14430
+ const otelInitFile = import_node_path57.default.join(
13789
14431
  serviceDir,
13790
14432
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
13791
14433
  );
13792
- const resolvedHooksFile = hooksFile ?? import_node_path56.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
14434
+ const resolvedHooksFile = hooksFile ?? import_node_path57.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
13793
14435
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
13794
14436
  const generatedFiles = [];
13795
14437
  const entrypointEdits = [];
@@ -13851,11 +14493,11 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
13851
14493
  }
13852
14494
  async function planNuxt(serviceDir, pkg, manifestPath, project) {
13853
14495
  const useTs = await isTypeScriptProject(serviceDir);
13854
- const otelPluginFile = import_node_path56.default.join(
14496
+ const otelPluginFile = import_node_path57.default.join(
13855
14497
  serviceDir,
13856
14498
  useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
13857
14499
  );
13858
- const otelInitFile = import_node_path56.default.join(
14500
+ const otelInitFile = import_node_path57.default.join(
13859
14501
  serviceDir,
13860
14502
  useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
13861
14503
  );
@@ -13906,19 +14548,19 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
13906
14548
  var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
13907
14549
  async function findAstroMiddleware(serviceDir) {
13908
14550
  for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
13909
- const candidate = import_node_path56.default.join(serviceDir, rel);
14551
+ const candidate = import_node_path57.default.join(serviceDir, rel);
13910
14552
  if (await exists3(candidate)) return candidate;
13911
14553
  }
13912
14554
  return null;
13913
14555
  }
13914
14556
  async function planAstro(serviceDir, pkg, manifestPath, project) {
13915
14557
  const useTs = await isTypeScriptProject(serviceDir);
13916
- const otelInitFile = import_node_path56.default.join(
14558
+ const otelInitFile = import_node_path57.default.join(
13917
14559
  serviceDir,
13918
14560
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
13919
14561
  );
13920
14562
  const existingMiddleware = await findAstroMiddleware(serviceDir);
13921
- const middlewareFile = existingMiddleware ?? import_node_path56.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
14563
+ const middlewareFile = existingMiddleware ?? import_node_path57.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
13922
14564
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
13923
14565
  const generatedFiles = [];
13924
14566
  const entrypointEdits = [];
@@ -14014,7 +14656,7 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
14014
14656
  }
14015
14657
  async function plan(serviceDir, opts) {
14016
14658
  const pkg = await readPackageJson2(serviceDir);
14017
- const manifestPath = import_node_path56.default.join(serviceDir, "package.json");
14659
+ const manifestPath = import_node_path57.default.join(serviceDir, "package.json");
14018
14660
  const project = opts?.project;
14019
14661
  const empty = {
14020
14662
  language: "javascript",
@@ -14049,8 +14691,8 @@ async function plan(serviceDir, opts) {
14049
14691
  return { ...empty, libOnly: true };
14050
14692
  }
14051
14693
  const flavor = dispatchEntry(entryFile, pkg);
14052
- const otelInitFile = import_node_path56.default.join(import_node_path56.default.dirname(entryFile), otelInitFilename(flavor));
14053
- const envNeatFile = import_node_path56.default.join(serviceDir, ".env.neat");
14694
+ const otelInitFile = import_node_path57.default.join(import_node_path57.default.dirname(entryFile), otelInitFilename(flavor));
14695
+ const envNeatFile = import_node_path57.default.join(serviceDir, ".env.neat");
14054
14696
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
14055
14697
  const dependencyEdits = [];
14056
14698
  for (const sdk of SDK_PACKAGES) {
@@ -14119,13 +14761,13 @@ async function plan(serviceDir, opts) {
14119
14761
  };
14120
14762
  }
14121
14763
  function isAllowedWritePath(serviceDir, target) {
14122
- const rel = import_node_path56.default.relative(serviceDir, target);
14764
+ const rel = import_node_path57.default.relative(serviceDir, target);
14123
14765
  if (rel.startsWith("..")) return false;
14124
- const base = import_node_path56.default.basename(target);
14766
+ const base = import_node_path57.default.basename(target);
14125
14767
  if (base === "package.json") return true;
14126
14768
  if (base === ".env.neat") return true;
14127
14769
  if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
14128
- const relPosix = rel.split(import_node_path56.default.sep).join("/");
14770
+ const relPosix = rel.split(import_node_path57.default.sep).join("/");
14129
14771
  if (/^instrumentation(?:\.(?:node|edge))?\.(?:js|cjs|mjs|ts)$/.test(base)) {
14130
14772
  if (relPosix === base) return true;
14131
14773
  if (relPosix === `src/${base}`) return true;
@@ -14142,7 +14784,7 @@ function isAllowedWritePath(serviceDir, target) {
14142
14784
  return false;
14143
14785
  }
14144
14786
  async function writeAtomic(file, contents) {
14145
- await import_node_fs37.promises.mkdir(import_node_path56.default.dirname(file), { recursive: true });
14787
+ await import_node_fs37.promises.mkdir(import_node_path57.default.dirname(file), { recursive: true });
14146
14788
  const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
14147
14789
  await import_node_fs37.promises.writeFile(tmp, contents, "utf8");
14148
14790
  await import_node_fs37.promises.rename(tmp, file);
@@ -14326,7 +14968,7 @@ async function rollback(installPlan, originals, createdFiles) {
14326
14968
  ...removed.map((f) => `removed: ${f}`),
14327
14969
  ""
14328
14970
  ];
14329
- const rollbackPath = import_node_path56.default.join(installPlan.serviceDir, "neat-rollback.patch");
14971
+ const rollbackPath = import_node_path57.default.join(installPlan.serviceDir, "neat-rollback.patch");
14330
14972
  await import_node_fs37.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
14331
14973
  }
14332
14974
  function injectInstrumentationHook(raw) {
@@ -14357,7 +14999,7 @@ var javascriptInstaller = {
14357
14999
  // src/installers/python.ts
14358
15000
  init_cjs_shims();
14359
15001
  var import_node_fs38 = require("fs");
14360
- var import_node_path57 = __toESM(require("path"), 1);
15002
+ var import_node_path58 = __toESM(require("path"), 1);
14361
15003
  var SDK_PACKAGES2 = [
14362
15004
  { name: "opentelemetry-distro", version: ">=0.49b0" },
14363
15005
  { name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
@@ -14378,7 +15020,7 @@ async function exists4(p) {
14378
15020
  async function detect2(serviceDir) {
14379
15021
  const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
14380
15022
  for (const m of markers) {
14381
- if (await exists4(import_node_path57.default.join(serviceDir, m))) return true;
15023
+ if (await exists4(import_node_path58.default.join(serviceDir, m))) return true;
14382
15024
  }
14383
15025
  return false;
14384
15026
  }
@@ -14388,7 +15030,7 @@ function reqPackageName(line) {
14388
15030
  return head.replace(/[<>=!~].*$/, "").toLowerCase();
14389
15031
  }
14390
15032
  async function planRequirementsTxtEdits(serviceDir) {
14391
- const file = import_node_path57.default.join(serviceDir, "requirements.txt");
15033
+ const file = import_node_path58.default.join(serviceDir, "requirements.txt");
14392
15034
  if (!await exists4(file)) return null;
14393
15035
  const raw = await import_node_fs38.promises.readFile(file, "utf8");
14394
15036
  const presentNames = new Set(
@@ -14398,7 +15040,7 @@ async function planRequirementsTxtEdits(serviceDir) {
14398
15040
  return { manifest: file, missing: [...missing] };
14399
15041
  }
14400
15042
  async function planProcfileEdits(serviceDir) {
14401
- const procfile = import_node_path57.default.join(serviceDir, "Procfile");
15043
+ const procfile = import_node_path58.default.join(serviceDir, "Procfile");
14402
15044
  if (!await exists4(procfile)) return [];
14403
15045
  const raw = await import_node_fs38.promises.readFile(procfile, "utf8");
14404
15046
  const edits = [];
@@ -14487,7 +15129,7 @@ async function apply2(installPlan) {
14487
15129
  if (raw === void 0) {
14488
15130
  throw new Error(`python installer: cannot read ${file} during apply`);
14489
15131
  }
14490
- const base = import_node_path57.default.basename(file);
15132
+ const base = import_node_path58.default.basename(file);
14491
15133
  if (base === "requirements.txt") {
14492
15134
  const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
14493
15135
  if (edits.length > 0) {
@@ -14526,7 +15168,7 @@ async function rollback2(installPlan, originals) {
14526
15168
  ...restored.map((f) => `restored: ${f}`),
14527
15169
  ""
14528
15170
  ];
14529
- const rollbackPath = import_node_path57.default.join(installPlan.serviceDir, "neat-rollback.patch");
15171
+ const rollbackPath = import_node_path58.default.join(installPlan.serviceDir, "neat-rollback.patch");
14530
15172
  await import_node_fs38.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
14531
15173
  }
14532
15174
  var pythonInstaller = {
@@ -14653,7 +15295,7 @@ init_cjs_shims();
14653
15295
  var import_node_fs39 = require("fs");
14654
15296
  var import_node_http = __toESM(require("http"), 1);
14655
15297
  var import_node_net = __toESM(require("net"), 1);
14656
- var import_node_path58 = __toESM(require("path"), 1);
15298
+ var import_node_path59 = __toESM(require("path"), 1);
14657
15299
  var import_node_url4 = require("url");
14658
15300
  var import_node_child_process3 = require("child_process");
14659
15301
  var import_node_readline = __toESM(require("readline"), 1);
@@ -14663,7 +15305,7 @@ async function extractAndPersist(opts) {
14663
15305
  const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
14664
15306
  resetGraph(graphKey);
14665
15307
  const graph = getGraph(graphKey);
14666
- const projectPaths = pathsForProject(graphKey, import_node_path58.default.join(opts.scanPath, "neat-out"));
15308
+ const projectPaths = pathsForProject(graphKey, import_node_path59.default.join(opts.scanPath, "neat-out"));
14667
15309
  const extraction = await extractFromDirectory(graph, opts.scanPath, {
14668
15310
  errorsPath: projectPaths.errorsPath
14669
15311
  });
@@ -14715,7 +15357,7 @@ async function applyInstallersOver(services, project, options = {}) {
14715
15357
  libOnly++;
14716
15358
  const appDeps = svc.pkg ? appFrameworkDependencies(svc.pkg) : [];
14717
15359
  if (appDeps.length > 0) {
14718
- const svcName = import_node_path58.default.basename(svc.dir);
15360
+ const svcName = import_node_path59.default.basename(svc.dir);
14719
15361
  const list = appDeps.join(", ");
14720
15362
  console.warn(
14721
15363
  `neat: runtime layer won't engage for ${svcName}: no entry point found.
@@ -14728,7 +15370,7 @@ async function applyInstallersOver(services, project, options = {}) {
14728
15370
  console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
14729
15371
  } else if (outcome.outcome === "react-native") {
14730
15372
  reactNative++;
14731
- const svcName = import_node_path58.default.basename(svc.dir);
15373
+ const svcName = import_node_path59.default.basename(svc.dir);
14732
15374
  console.log(
14733
15375
  `neat: ${svc.dir} detected as React Native / Expo
14734
15376
  The installer doesn't cover this runtime deterministically.
@@ -14739,7 +15381,7 @@ async function applyInstallersOver(services, project, options = {}) {
14739
15381
  );
14740
15382
  } else if (outcome.outcome === "bun") {
14741
15383
  bun++;
14742
- const svcName = import_node_path58.default.basename(svc.dir);
15384
+ const svcName = import_node_path59.default.basename(svc.dir);
14743
15385
  console.log(
14744
15386
  `neat: ${svc.dir} detected as Bun
14745
15387
  The installer doesn't cover this runtime deterministically.
@@ -14750,7 +15392,7 @@ async function applyInstallersOver(services, project, options = {}) {
14750
15392
  );
14751
15393
  } else if (outcome.outcome === "deno") {
14752
15394
  deno++;
14753
- const svcName = import_node_path58.default.basename(svc.dir);
15395
+ const svcName = import_node_path59.default.basename(svc.dir);
14754
15396
  console.log(
14755
15397
  `neat: ${svc.dir} detected as Deno
14756
15398
  The installer doesn't cover this runtime deterministically.
@@ -14761,7 +15403,7 @@ async function applyInstallersOver(services, project, options = {}) {
14761
15403
  );
14762
15404
  } else if (outcome.outcome === "cloudflare-workers") {
14763
15405
  cloudflareWorkers++;
14764
- const svcName = import_node_path58.default.basename(svc.dir);
15406
+ const svcName = import_node_path59.default.basename(svc.dir);
14765
15407
  console.log(
14766
15408
  `neat: ${svc.dir} detected as Cloudflare Workers
14767
15409
  The installer doesn't cover this runtime deterministically.
@@ -14772,7 +15414,7 @@ async function applyInstallersOver(services, project, options = {}) {
14772
15414
  );
14773
15415
  } else if (outcome.outcome === "electron") {
14774
15416
  electron++;
14775
- const svcName = import_node_path58.default.basename(svc.dir);
15417
+ const svcName = import_node_path59.default.basename(svc.dir);
14776
15418
  console.log(
14777
15419
  `neat: ${svc.dir} detected as Electron
14778
15420
  The installer doesn't cover this runtime deterministically.
@@ -14785,7 +15427,7 @@ async function applyInstallersOver(services, project, options = {}) {
14785
15427
  if (svc.pkg && (outcome.outcome === "instrumented" || outcome.outcome === "already-instrumented")) {
14786
15428
  const gaps = uninstrumentedLibraries(svc.pkg);
14787
15429
  if (gaps.length > 0) {
14788
- const svcName = import_node_path58.default.basename(svc.dir);
15430
+ const svcName = import_node_path59.default.basename(svc.dir);
14789
15431
  const list = gaps.join(", ");
14790
15432
  const subject = gaps.length === 1 ? "this library" : "these libraries";
14791
15433
  const aux = gaps.length === 1 ? "isn't" : "aren't";
@@ -14991,8 +15633,8 @@ async function persistedPortsFor(scanPath) {
14991
15633
  return { rest: record.ports.rest, otlp: record.ports.otlp, web: record.ports.web };
14992
15634
  }
14993
15635
  async function acquireSpawnLock(scanPath) {
14994
- const lockPath = import_node_path58.default.join(scanPath, "neat-out", "daemon.spawn.lock");
14995
- await import_node_fs39.promises.mkdir(import_node_path58.default.dirname(lockPath), { recursive: true });
15636
+ const lockPath = import_node_path59.default.join(scanPath, "neat-out", "daemon.spawn.lock");
15637
+ await import_node_fs39.promises.mkdir(import_node_path59.default.dirname(lockPath), { recursive: true });
14996
15638
  const STALE_LOCK_MS = 6e4;
14997
15639
  try {
14998
15640
  const fd = await import_node_fs39.promises.open(lockPath, "wx");
@@ -15037,13 +15679,13 @@ async function healthIsForProject(restPort, project) {
15037
15679
  return false;
15038
15680
  }
15039
15681
  function daemonLogPath(projectPath2) {
15040
- return import_node_path58.default.join(projectPath2, "neat-out", "daemon.log");
15682
+ return import_node_path59.default.join(projectPath2, "neat-out", "daemon.log");
15041
15683
  }
15042
15684
  function spawnDaemonDetached(spec) {
15043
- const here = import_node_path58.default.dirname((0, import_node_url4.fileURLToPath)(importMetaUrl));
15685
+ const here = import_node_path59.default.dirname((0, import_node_url4.fileURLToPath)(importMetaUrl));
15044
15686
  const candidates = [
15045
- import_node_path58.default.join(here, "neatd.cjs"),
15046
- import_node_path58.default.join(here, "neatd.js")
15687
+ import_node_path59.default.join(here, "neatd.cjs"),
15688
+ import_node_path59.default.join(here, "neatd.js")
15047
15689
  ];
15048
15690
  let entry2 = null;
15049
15691
  const fsSync = require("fs");
@@ -15073,7 +15715,7 @@ function spawnDaemonDetached(spec) {
15073
15715
  let logFd = null;
15074
15716
  if (spec) {
15075
15717
  const logPath = daemonLogPath(spec.projectPath);
15076
- fsSync.mkdirSync(import_node_path58.default.dirname(logPath), { recursive: true });
15718
+ fsSync.mkdirSync(import_node_path59.default.dirname(logPath), { recursive: true });
15077
15719
  logFd = fsSync.openSync(logPath, "a");
15078
15720
  }
15079
15721
  const child = (0, import_node_child_process3.spawn)(process.execPath, [entry2, "start"], {
@@ -15272,7 +15914,7 @@ async function runOrchestrator(opts) {
15272
15914
  result.steps.browser = openBrowser(dashboardUrl);
15273
15915
  }
15274
15916
  const daemonRunning = result.steps.daemon === "spawned" || result.steps.daemon === "already-running";
15275
- const daemonLog = daemonRunning ? import_node_path58.default.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
15917
+ const daemonLog = daemonRunning ? import_node_path59.default.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
15276
15918
  printSummary(result, graph, dashboardUrl, daemonLog);
15277
15919
  return result;
15278
15920
  }
@@ -15298,9 +15940,26 @@ function printSummary(result, graph, dashboardUrl, daemonLog) {
15298
15940
  } else {
15299
15941
  console.log("running locally \u2014 open the dashboard, no token needed");
15300
15942
  }
15943
+ const failedInstalls = (result.steps.apply.packageManagerInstalls ?? []).filter(
15944
+ (i) => i.exitCode !== 0
15945
+ );
15301
15946
  if (daemonLog !== null) {
15302
15947
  console.log("");
15303
15948
  console.log(`daemon running in the background (logs: ${daemonLog})`);
15949
+ }
15950
+ if (failedInstalls.length > 0) {
15951
+ if (daemonLog === null) console.log("");
15952
+ console.log(
15953
+ "instrumentation is wired into your manifests, but it is NOT yet active:"
15954
+ );
15955
+ console.log(
15956
+ "the dependency install did not complete, so the OTel SDK never landed and"
15957
+ );
15958
+ console.log("OBSERVED edges will stay empty until you finish the install. Fix it:");
15959
+ for (const i of failedInstalls) {
15960
+ console.log(` run \`${i.pm} install\` in ${i.cwd}`);
15961
+ }
15962
+ } else if (daemonLog !== null) {
15304
15963
  console.log(
15305
15964
  "next: run your app or your test suite \u2014 OBSERVED edges fill in as it executes,"
15306
15965
  );
@@ -15352,6 +16011,8 @@ function parseConnectorArgs(args) {
15352
16011
  positional: [],
15353
16012
  skipValidate: false,
15354
16013
  plaintext: false,
16014
+ help: false,
16015
+ json: false,
15355
16016
  fields: {}
15356
16017
  };
15357
16018
  const positional = [];
@@ -15365,6 +16026,14 @@ function parseConnectorArgs(args) {
15365
16026
  out.plaintext = true;
15366
16027
  continue;
15367
16028
  }
16029
+ if (arg === "--help" || arg === "-h") {
16030
+ out.help = true;
16031
+ continue;
16032
+ }
16033
+ if (arg === "--json") {
16034
+ out.json = true;
16035
+ continue;
16036
+ }
15368
16037
  if (arg.startsWith("--")) {
15369
16038
  let flag = arg;
15370
16039
  let value;
@@ -15441,19 +16110,20 @@ function plaintextFields(ref) {
15441
16110
  async function connectorAdd(args, deps) {
15442
16111
  let provider = args.positional[0];
15443
16112
  if (!provider && deps.interactive) {
15444
- provider = (await deps.prompt(`Provider (${Object.keys(PROVIDER_DISPATCH).sort().join(", ")}):`)).trim();
16113
+ provider = (await deps.prompt(`Provider (${knownProviderNames().join(", ")}):`)).trim();
15445
16114
  }
15446
16115
  if (!provider) {
15447
16116
  deps.err("neat connector add: a provider is required (e.g. `neat connector add supabase`)");
15448
16117
  return 2;
15449
16118
  }
15450
- const dispatch = getProviderDispatch(provider);
16119
+ const dispatch = getProviderFieldSchema(provider);
15451
16120
  if (!dispatch) {
15452
16121
  deps.err(
15453
- `neat connector add: unknown provider "${provider}". known providers: ${Object.keys(PROVIDER_DISPATCH).sort().join(", ")}`
16122
+ `neat connector add: unknown provider "${provider}". known providers: ${knownProviderNames().join(", ")}`
15454
16123
  );
15455
16124
  return 2;
15456
16125
  }
16126
+ const push = isPushProvider(provider);
15457
16127
  let project = args.project;
15458
16128
  if (project === void 0 && deps.interactive) {
15459
16129
  const answer = (await deps.prompt("Project (blank = the project the daemon is bootstrapping):")).trim();
@@ -15492,7 +16162,7 @@ async function connectorAdd(args, deps) {
15492
16162
  credential,
15493
16163
  ...Object.keys(options).length > 0 ? { options } : {}
15494
16164
  };
15495
- if (!args.skipValidate) {
16165
+ if (!push && !args.skipValidate) {
15496
16166
  const outcome = await validateConnectorEntry(entry2, deps.env, deps.fetchImpl);
15497
16167
  const code = reportPreWriteValidation(outcome, deps);
15498
16168
  if (code !== 0) return code;
@@ -15503,14 +16173,32 @@ async function connectorAdd(args, deps) {
15503
16173
  `neat connector add: storing a literal secret at rest for ${plaintext.join(", ")} \u2014 prefer an env-var reference like \`$PROVIDER_TOKEN\` (pass --plaintext to silence this).`
15504
16174
  );
15505
16175
  }
16176
+ if (push) {
16177
+ if (args.skipValidate) {
16178
+ deps.out("note: --skip-validate has no effect for a push provider \u2014 provisioning the drain is the add.");
16179
+ }
16180
+ const outcome = await provisionConnector(entry2, deps.env, deps.fetchImpl);
16181
+ if (outcome.status !== "ok") {
16182
+ deps.err(
16183
+ `neat connector add: could not provision the ${provider} connector \u2014 ${outcome.reason}. Nothing was written.`
16184
+ );
16185
+ return 1;
16186
+ }
16187
+ if (outcome.options) entry2.options = { ...entry2.options ?? {}, ...outcome.options };
16188
+ if (outcome.note) deps.out(`note: ${outcome.note}`);
16189
+ }
15506
16190
  const { replaced } = await upsertConnectorEntry(entry2, deps.home);
15507
16191
  const verb = replaced ? "updated" : "added";
15508
16192
  const where = project ? `project "${project}"` : "the bootstrapping project";
15509
16193
  deps.out(`${verb} connector "${id}" (${provider}) for ${where}.`);
15510
- if (args.skipValidate) {
15511
- deps.out("skipped validation (--skip-validate) \u2014 the credential is checked at the next daemon poll.");
16194
+ if (push) {
16195
+ deps.out("The drain is live \u2014 traces arrive at the daemon's OTLP receiver. No poll or restart needed.");
16196
+ } else {
16197
+ if (args.skipValidate) {
16198
+ deps.out("skipped validation (--skip-validate) \u2014 the credential is checked at the next daemon poll.");
16199
+ }
16200
+ deps.out("Restart the project's daemon (or start it) to begin polling this connector.");
15512
16201
  }
15513
- deps.out("Restart the project's daemon (or start it) to begin polling this connector.");
15514
16202
  return 0;
15515
16203
  }
15516
16204
  function reportPreWriteValidation(outcome, deps) {
@@ -15536,10 +16224,26 @@ function reportPreWriteValidation(outcome, deps) {
15536
16224
  return 2;
15537
16225
  }
15538
16226
  }
15539
- async function connectorList(deps, filterProject) {
16227
+ async function connectorList(deps, filterProject, json = false) {
15540
16228
  const config = await readConnectorsConfig(deps.home);
15541
16229
  let entries = config.connectors;
15542
16230
  if (filterProject) entries = entries.filter((e) => e.project === filterProject);
16231
+ if (json) {
16232
+ const rows = entries.map((e) => ({
16233
+ id: e.id,
16234
+ provider: e.provider,
16235
+ project: e.project ?? null,
16236
+ kind: isPushProvider(e.provider) ? "push" : "pull",
16237
+ credential: describeCredential(e.credential, deps.env).map((c) => ({
16238
+ ...c.field ? { field: c.field } : {},
16239
+ ref: c.display,
16240
+ ...c.status ? { status: c.status } : { kind: c.kind }
16241
+ })),
16242
+ ...e.options ? { options: e.options } : {}
16243
+ }));
16244
+ deps.out(JSON.stringify(rows, null, 2));
16245
+ return 0;
16246
+ }
15543
16247
  if (entries.length === 0) {
15544
16248
  deps.out(
15545
16249
  filterProject ? `no connectors configured for project "${filterProject}".` : "no connectors configured. run `neat connector add <provider>` to add one."
@@ -15562,6 +16266,22 @@ async function connectorRemove(deps, id) {
15562
16266
  deps.err("neat connector remove: missing <id>. run `neat connector list` to see configured ids.");
15563
16267
  return 2;
15564
16268
  }
16269
+ const config = await readConnectorsConfig(deps.home);
16270
+ const entry2 = config.connectors.find((c) => c.id === id);
16271
+ if (!entry2) {
16272
+ deps.err(`neat connector remove: no connector with id "${id}". run \`neat connector list\` to see configured ids.`);
16273
+ return 1;
16274
+ }
16275
+ if (isPushProvider(entry2.provider)) {
16276
+ const outcome = await deprovisionConnector(entry2, deps.env, deps.fetchImpl);
16277
+ if (outcome.status !== "ok") {
16278
+ deps.err(
16279
+ `neat connector remove: could not delete the ${entry2.provider} drain \u2014 ${outcome.reason}. The entry was kept so you can retry (or delete the drain in the Vercel dashboard, then re-run).`
16280
+ );
16281
+ return 1;
16282
+ }
16283
+ if (outcome.note) deps.out(`note: ${outcome.note}`);
16284
+ }
15565
16285
  const removed = await removeConnectorEntry(id, deps.home);
15566
16286
  if (!removed) {
15567
16287
  deps.err(`neat connector remove: no connector with id "${id}". run \`neat connector list\` to see configured ids.`);
@@ -15602,13 +16322,20 @@ async function connectorTest(deps, id) {
15602
16322
  }
15603
16323
  function printConnectorUsage(write) {
15604
16324
  write("usage: neat connector <add|list|remove|test> [args]");
16325
+ write(` providers: ${knownProviderNames().join(", ")}`);
16326
+ write(" pull (polled): supabase, railway, firebase, cloudflare");
16327
+ write(" push (drains): vercel \u2014 provisions a Vercel trace Drain that forwards");
16328
+ write(" traces to the daemon's OTLP receiver; no app instrumentation");
15605
16329
  write(" add <provider> add a connector; validates the credential first (--skip-validate to skip)");
15606
16330
  write(" flags: --project <name> --credential/--token <$VAR|value> --id <id>");
15607
16331
  write(" --<option> <value> (provider-specific) --plaintext --skip-validate");
16332
+ write(" vercel: neat connector add vercel --token $VERCEL_TOKEN \\");
16333
+ write(" --otel-token $NEAT_OTEL_TOKEN --team-id <teamId> \\");
16334
+ write(" --endpoint https://<public-host>/v1/traces [--project-ids <id,id>]");
15608
16335
  write(" list list configured connectors (credentials shown redacted)");
15609
16336
  write(" flags: --project <name>");
15610
- write(" remove <id> remove a connector by id");
15611
- write(" test <id> re-run the credential validation for an existing connector");
16337
+ write(" remove <id> remove a connector by id (a push provider also has its drain deleted)");
16338
+ write(" test <id> re-run validation for an existing connector (a push provider re-tests its drain)");
15612
16339
  }
15613
16340
  async function runConnectorCommand(rawArgs, deps = {}) {
15614
16341
  const resolved = resolveDeps(deps);
@@ -15618,11 +16345,15 @@ async function runConnectorCommand(rawArgs, deps = {}) {
15618
16345
  return 2;
15619
16346
  }
15620
16347
  const a = parsed.value;
16348
+ if (a.help) {
16349
+ printConnectorUsage(resolved.out);
16350
+ return 0;
16351
+ }
15621
16352
  switch (a.subcommand) {
15622
16353
  case "add":
15623
16354
  return connectorAdd(a, resolved);
15624
16355
  case "list":
15625
- return connectorList(resolved, a.project);
16356
+ return connectorList(resolved, a.project, a.json);
15626
16357
  case "remove":
15627
16358
  return connectorRemove(resolved, a.positional[0]);
15628
16359
  case "test":
@@ -15640,7 +16371,7 @@ async function runConnectorCommand(rawArgs, deps = {}) {
15640
16371
 
15641
16372
  // src/hooks-cli.ts
15642
16373
  init_cjs_shims();
15643
- var import_node_path59 = __toESM(require("path"), 1);
16374
+ var import_node_path60 = __toESM(require("path"), 1);
15644
16375
  var import_node_os5 = __toESM(require("os"), 1);
15645
16376
  var import_node_fs40 = require("fs");
15646
16377
  var import_node_url5 = require("url");
@@ -15649,14 +16380,14 @@ var GUIDE_FILENAME = "GRAPH_FIRST.md";
15649
16380
  var GUIDE_INSTALL_NAME = "neat-graph-first.md";
15650
16381
  var HOOK_MATCHER = "Grep|Glob|Bash";
15651
16382
  function moduleDir() {
15652
- return typeof __dirname !== "undefined" ? __dirname : import_node_path59.default.dirname((0, import_node_url5.fileURLToPath)(importMetaUrl));
16383
+ return typeof __dirname !== "undefined" ? __dirname : import_node_path60.default.dirname((0, import_node_url5.fileURLToPath)(importMetaUrl));
15653
16384
  }
15654
16385
  async function readSkillAsset(rel) {
15655
16386
  const here = moduleDir();
15656
16387
  const candidates = [
15657
- import_node_path59.default.resolve(here, "../../claude-skill", rel),
15658
- import_node_path59.default.resolve(here, "../../../claude-skill", rel),
15659
- import_node_path59.default.resolve(here, "../claude-skill", rel)
16388
+ import_node_path60.default.resolve(here, "../../claude-skill", rel),
16389
+ import_node_path60.default.resolve(here, "../../../claude-skill", rel),
16390
+ import_node_path60.default.resolve(here, "../claude-skill", rel)
15660
16391
  ];
15661
16392
  for (const candidate of candidates) {
15662
16393
  try {
@@ -15670,17 +16401,17 @@ async function readSkillAsset(rel) {
15670
16401
  }
15671
16402
  function neatHome3() {
15672
16403
  const override = process.env.NEAT_HOME;
15673
- if (override && override.length > 0) return import_node_path59.default.resolve(override);
15674
- return import_node_path59.default.join(import_node_os5.default.homedir(), ".neat");
16404
+ if (override && override.length > 0) return import_node_path60.default.resolve(override);
16405
+ return import_node_path60.default.join(import_node_os5.default.homedir(), ".neat");
15675
16406
  }
15676
16407
  function claudeSettingsPath() {
15677
16408
  const override = process.env.NEAT_CLAUDE_SETTINGS;
15678
- if (override && override.length > 0) return import_node_path59.default.resolve(override);
16409
+ if (override && override.length > 0) return import_node_path60.default.resolve(override);
15679
16410
  const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os5.default.homedir();
15680
- return import_node_path59.default.join(home, ".claude", "settings.json");
16411
+ return import_node_path60.default.join(home, ".claude", "settings.json");
15681
16412
  }
15682
16413
  function installedHookPath() {
15683
- return import_node_path59.default.join(neatHome3(), "hooks", HOOK_FILENAME);
16414
+ return import_node_path60.default.join(neatHome3(), "hooks", HOOK_FILENAME);
15684
16415
  }
15685
16416
  function isNeatSearchEntry(entry2) {
15686
16417
  return (entry2.hooks ?? []).some(
@@ -15713,9 +16444,9 @@ async function runHooks(opts) {
15713
16444
  const hookScript = await readSkillAsset(`hooks/${HOOK_FILENAME}`);
15714
16445
  const guide = await readSkillAsset(GUIDE_FILENAME);
15715
16446
  const scriptPath = installedHookPath();
15716
- await import_node_fs40.promises.mkdir(import_node_path59.default.dirname(scriptPath), { recursive: true });
16447
+ await import_node_fs40.promises.mkdir(import_node_path60.default.dirname(scriptPath), { recursive: true });
15717
16448
  await import_node_fs40.promises.writeFile(scriptPath, hookScript, { mode: 493 });
15718
- const guidePath = import_node_path59.default.join(neatHome3(), GUIDE_INSTALL_NAME);
16449
+ const guidePath = import_node_path60.default.join(neatHome3(), GUIDE_INSTALL_NAME);
15719
16450
  await import_node_fs40.promises.writeFile(guidePath, guide, "utf8");
15720
16451
  const settingsFile = claudeSettingsPath();
15721
16452
  let settings = {};
@@ -15742,7 +16473,7 @@ async function runHooks(opts) {
15742
16473
  ...settings,
15743
16474
  hooks: { ...hooks, PreToolUse: preToolUse }
15744
16475
  };
15745
- await import_node_fs40.promises.mkdir(import_node_path59.default.dirname(settingsFile), { recursive: true });
16476
+ await import_node_fs40.promises.mkdir(import_node_path60.default.dirname(settingsFile), { recursive: true });
15746
16477
  await import_node_fs40.promises.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
15747
16478
  console.log(`neat hooks: installed the search-nudge hook`);
15748
16479
  console.log(` script: ${scriptPath}`);
@@ -15814,11 +16545,11 @@ async function runHooksCommand(args) {
15814
16545
 
15815
16546
  // src/cli-verbs.ts
15816
16547
  init_cjs_shims();
15817
- var import_node_path60 = __toESM(require("path"), 1);
16548
+ var import_node_path61 = __toESM(require("path"), 1);
15818
16549
 
15819
16550
  // src/cli-client.ts
15820
16551
  init_cjs_shims();
15821
- var import_types48 = require("@neat.is/types");
16552
+ var import_types49 = require("@neat.is/types");
15822
16553
  var HttpError = class extends Error {
15823
16554
  constructor(status2, message, responseBody = "") {
15824
16555
  super(message);
@@ -15843,10 +16574,10 @@ function createHttpClient(baseUrl, bearerToken) {
15843
16574
  const root = baseUrl.replace(/\/$/, "");
15844
16575
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
15845
16576
  return {
15846
- async get(path62) {
16577
+ async get(path63) {
15847
16578
  let res;
15848
16579
  try {
15849
- res = await fetch(`${root}${path62}`, {
16580
+ res = await fetch(`${root}${path63}`, {
15850
16581
  headers: { ...authHeader }
15851
16582
  });
15852
16583
  } catch (err) {
@@ -15858,16 +16589,16 @@ function createHttpClient(baseUrl, bearerToken) {
15858
16589
  const body = await res.text().catch(() => "");
15859
16590
  throw new HttpError(
15860
16591
  res.status,
15861
- `${res.status} ${res.statusText} on GET ${path62}: ${body}`,
16592
+ `${res.status} ${res.statusText} on GET ${path63}: ${body}`,
15862
16593
  body
15863
16594
  );
15864
16595
  }
15865
16596
  return await res.json();
15866
16597
  },
15867
- async post(path62, body) {
16598
+ async post(path63, body) {
15868
16599
  let res;
15869
16600
  try {
15870
- res = await fetch(`${root}${path62}`, {
16601
+ res = await fetch(`${root}${path63}`, {
15871
16602
  method: "POST",
15872
16603
  headers: { "content-type": "application/json", ...authHeader },
15873
16604
  body: JSON.stringify(body)
@@ -15881,7 +16612,7 @@ function createHttpClient(baseUrl, bearerToken) {
15881
16612
  const text = await res.text().catch(() => "");
15882
16613
  throw new HttpError(
15883
16614
  res.status,
15884
- `${res.status} ${res.statusText} on POST ${path62}: ${text}`,
16615
+ `${res.status} ${res.statusText} on POST ${path63}: ${text}`,
15885
16616
  text
15886
16617
  );
15887
16618
  }
@@ -15895,12 +16626,12 @@ function projectPath(project, suffix) {
15895
16626
  }
15896
16627
  async function runRootCause(client, input) {
15897
16628
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
15898
- const path62 = projectPath(
16629
+ const path63 = projectPath(
15899
16630
  input.project,
15900
16631
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
15901
16632
  );
15902
16633
  try {
15903
- const result = await client.get(path62);
16634
+ const result = await client.get(path63);
15904
16635
  const arrowPath = result.traversalPath.join(" \u2190 ");
15905
16636
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
15906
16637
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -15926,12 +16657,12 @@ async function runRootCause(client, input) {
15926
16657
  }
15927
16658
  async function runBlastRadius(client, input) {
15928
16659
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
15929
- const path62 = projectPath(
16660
+ const path63 = projectPath(
15930
16661
  input.project,
15931
16662
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
15932
16663
  );
15933
16664
  try {
15934
- const result = await client.get(path62);
16665
+ const result = await client.get(path63);
15935
16666
  if (result.totalAffected === 0) {
15936
16667
  return {
15937
16668
  summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
@@ -15960,17 +16691,17 @@ async function runBlastRadius(client, input) {
15960
16691
  }
15961
16692
  }
15962
16693
  function formatBlastEntry(n) {
15963
- const tag = n.edgeProvenance === import_types48.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
16694
+ const tag = n.edgeProvenance === import_types49.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
15964
16695
  return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
15965
16696
  }
15966
16697
  async function runDependencies(client, input) {
15967
16698
  const depth = input.depth ?? 3;
15968
- const path62 = projectPath(
16699
+ const path63 = projectPath(
15969
16700
  input.project,
15970
16701
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
15971
16702
  );
15972
16703
  try {
15973
- const result = await client.get(path62);
16704
+ const result = await client.get(path63);
15974
16705
  if (result.total === 0) {
15975
16706
  return {
15976
16707
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -16017,7 +16748,7 @@ async function runObservedDependencies(client, input) {
16017
16748
  if (result.observed) {
16018
16749
  return {
16019
16750
  summary: `${input.nodeId} makes no outbound runtime calls, but OTel has observed it receiving traffic on ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 it's a pure receiver.`,
16020
- provenance: import_types48.Provenance.OBSERVED
16751
+ provenance: import_types49.Provenance.OBSERVED
16021
16752
  };
16022
16753
  }
16023
16754
  const note = result.hasExtractedOutbound ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
@@ -16027,7 +16758,7 @@ async function runObservedDependencies(client, input) {
16027
16758
  return {
16028
16759
  summary: `${input.nodeId} has ${result.dependencies.length} runtime dependenc${result.dependencies.length === 1 ? "y" : "ies"} confirmed by OTel.`,
16029
16760
  block: blockLines.join("\n"),
16030
- provenance: import_types48.Provenance.OBSERVED
16761
+ provenance: import_types49.Provenance.OBSERVED
16031
16762
  };
16032
16763
  } catch (err) {
16033
16764
  if (err instanceof HttpError && err.status === 404) {
@@ -16062,9 +16793,9 @@ function formatDuration(ms) {
16062
16793
  return `${Math.round(h / 24)}d`;
16063
16794
  }
16064
16795
  async function runIncidents(client, input) {
16065
- const path62 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
16796
+ const path63 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
16066
16797
  try {
16067
- const body = await client.get(path62);
16798
+ const body = await client.get(path63);
16068
16799
  const events = body.events;
16069
16800
  if (events.length === 0) {
16070
16801
  return {
@@ -16081,7 +16812,7 @@ async function runIncidents(client, input) {
16081
16812
  return {
16082
16813
  summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
16083
16814
  block: blockLines.join("\n"),
16084
- provenance: import_types48.Provenance.OBSERVED
16815
+ provenance: import_types49.Provenance.OBSERVED
16085
16816
  };
16086
16817
  } catch (err) {
16087
16818
  if (err instanceof HttpError && err.status === 404) {
@@ -16190,7 +16921,7 @@ async function runStaleEdges(client, input) {
16190
16921
  return {
16191
16922
  summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
16192
16923
  block: blockLines.join("\n"),
16193
- provenance: import_types48.Provenance.STALE
16924
+ provenance: import_types49.Provenance.STALE
16194
16925
  };
16195
16926
  }
16196
16927
  async function runPolicies(client, input) {
@@ -16354,7 +17085,7 @@ async function resolveProjectEntry(opts) {
16354
17085
  const cwd = opts.cwd ?? process.cwd();
16355
17086
  const resolvedCwd = await normalizeProjectPath(cwd);
16356
17087
  for (const entry2 of entries) {
16357
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path60.default.sep}`)) {
17088
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path61.default.sep}`)) {
16358
17089
  return entry2;
16359
17090
  }
16360
17091
  }
@@ -16507,7 +17238,7 @@ async function runSync(opts) {
16507
17238
  }
16508
17239
 
16509
17240
  // src/cli.ts
16510
- var import_types49 = require("@neat.is/types");
17241
+ var import_types50 = require("@neat.is/types");
16511
17242
  function isNpxInvocation() {
16512
17243
  if (process.env.npm_command === "exec") return true;
16513
17244
  const execpath = process.env.npm_execpath ?? "";
@@ -16576,8 +17307,9 @@ function usage2() {
16576
17307
  console.log(" --dry-run run extraction in-memory; do not write");
16577
17308
  console.log(" --no-instrument skip the SDK install apply step");
16578
17309
  console.log(" --json emit the delta summary as JSON");
16579
- console.log(" connector Configure pull-based OBSERVED connectors (supabase, railway,");
16580
- console.log(" firebase, cloudflare). Subcommands:");
17310
+ console.log(" connector Configure OBSERVED connectors \u2014 pull (supabase, railway,");
17311
+ console.log(" firebase, cloudflare) and push (vercel, via a trace Drain).");
17312
+ console.log(" Subcommands:");
16581
17313
  console.log(" add <provider> add a connector; validates the credential");
16582
17314
  console.log(" against the provider first (--skip-validate to skip)");
16583
17315
  console.log(" flags: --project <name>, --credential/--token <$VAR|value>,");
@@ -16817,12 +17549,12 @@ async function runInit(opts) {
16817
17549
  printDiscoveryReport(opts, services);
16818
17550
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
16819
17551
  const patch = renderPatch(sections);
16820
- const patchPath = import_node_path61.default.join(opts.scanPath, "neat.patch");
17552
+ const patchPath = import_node_path62.default.join(opts.scanPath, "neat.patch");
16821
17553
  if (opts.dryRun) {
16822
17554
  await import_node_fs41.promises.writeFile(patchPath, patch, "utf8");
16823
17555
  written.push(patchPath);
16824
17556
  console.log(`dry-run: patch written to ${patchPath}`);
16825
- const gitignorePath = import_node_path61.default.join(opts.scanPath, ".gitignore");
17557
+ const gitignorePath = import_node_path62.default.join(opts.scanPath, ".gitignore");
16826
17558
  const gitignoreExists = await import_node_fs41.promises.stat(gitignorePath).then(() => true).catch(() => false);
16827
17559
  const verb = gitignoreExists ? "append" : "create";
16828
17560
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
@@ -16834,9 +17566,9 @@ async function runInit(opts) {
16834
17566
  const graph = getGraph(graphKey);
16835
17567
  const projectPaths = pathsForProject(
16836
17568
  graphKey,
16837
- import_node_path61.default.join(opts.scanPath, "neat-out")
17569
+ import_node_path62.default.join(opts.scanPath, "neat-out")
16838
17570
  );
16839
- const errorsPath = import_node_path61.default.join(import_node_path61.default.dirname(opts.outPath), import_node_path61.default.basename(projectPaths.errorsPath));
17571
+ const errorsPath = import_node_path62.default.join(import_node_path62.default.dirname(opts.outPath), import_node_path62.default.basename(projectPaths.errorsPath));
16840
17572
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
16841
17573
  await saveGraphToDisk(graph, opts.outPath);
16842
17574
  written.push(opts.outPath);
@@ -16955,9 +17687,9 @@ var CLAUDE_SKILL_CONFIG = {
16955
17687
  };
16956
17688
  function claudeConfigPath() {
16957
17689
  const override = process.env.NEAT_CLAUDE_CONFIG;
16958
- if (override && override.length > 0) return import_node_path61.default.resolve(override);
17690
+ if (override && override.length > 0) return import_node_path62.default.resolve(override);
16959
17691
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
16960
- return import_node_path61.default.join(home, ".claude.json");
17692
+ return import_node_path62.default.join(home, ".claude.json");
16961
17693
  }
16962
17694
  async function runSkill(opts) {
16963
17695
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -16981,7 +17713,7 @@ async function runSkill(opts) {
16981
17713
  ...existing,
16982
17714
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
16983
17715
  };
16984
- await import_node_fs41.promises.mkdir(import_node_path61.default.dirname(target), { recursive: true });
17716
+ await import_node_fs41.promises.mkdir(import_node_path62.default.dirname(target), { recursive: true });
16985
17717
  await import_node_fs41.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
16986
17718
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
16987
17719
  console.log("restart Claude Code to pick up the new MCP server.");
@@ -17047,12 +17779,12 @@ async function main() {
17047
17779
  console.error("neat init: --apply and --dry-run are mutually exclusive");
17048
17780
  process.exit(2);
17049
17781
  }
17050
- const scanPath = import_node_path61.default.resolve(target);
17782
+ const scanPath = import_node_path62.default.resolve(target);
17051
17783
  const projectExplicit = parsed.project !== null;
17052
- const projectName = projectExplicit ? project : import_node_path61.default.basename(scanPath);
17784
+ const projectName = projectExplicit ? project : import_node_path62.default.basename(scanPath);
17053
17785
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
17054
- const fallback = pathsForProject(projectKey, import_node_path61.default.join(scanPath, "neat-out")).snapshotPath;
17055
- const outPath = import_node_path61.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
17786
+ const fallback = pathsForProject(projectKey, import_node_path62.default.join(scanPath, "neat-out")).snapshotPath;
17787
+ const outPath = import_node_path62.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
17056
17788
  const result = await runInit({
17057
17789
  scanPath,
17058
17790
  outPath,
@@ -17073,21 +17805,21 @@ async function main() {
17073
17805
  usage2();
17074
17806
  process.exit(2);
17075
17807
  }
17076
- const scanPath = import_node_path61.default.resolve(target);
17808
+ const scanPath = import_node_path62.default.resolve(target);
17077
17809
  const stat = await import_node_fs41.promises.stat(scanPath).catch(() => null);
17078
17810
  if (!stat || !stat.isDirectory()) {
17079
17811
  console.error(`neat watch: ${scanPath} is not a directory`);
17080
17812
  process.exit(2);
17081
17813
  }
17082
- const projectPaths = pathsForProject(project, import_node_path61.default.join(scanPath, "neat-out"));
17083
- const outPath = import_node_path61.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
17084
- const errorsPath = import_node_path61.default.resolve(
17085
- process.env.NEAT_ERRORS_PATH ?? import_node_path61.default.join(import_node_path61.default.dirname(outPath), import_node_path61.default.basename(projectPaths.errorsPath))
17814
+ const projectPaths = pathsForProject(project, import_node_path62.default.join(scanPath, "neat-out"));
17815
+ const outPath = import_node_path62.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
17816
+ const errorsPath = import_node_path62.default.resolve(
17817
+ process.env.NEAT_ERRORS_PATH ?? import_node_path62.default.join(import_node_path62.default.dirname(outPath), import_node_path62.default.basename(projectPaths.errorsPath))
17086
17818
  );
17087
- const staleEventsPath = import_node_path61.default.resolve(
17088
- process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path61.default.join(import_node_path61.default.dirname(outPath), import_node_path61.default.basename(projectPaths.staleEventsPath))
17819
+ const staleEventsPath = import_node_path62.default.resolve(
17820
+ process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path62.default.join(import_node_path62.default.dirname(outPath), import_node_path62.default.basename(projectPaths.staleEventsPath))
17089
17821
  );
17090
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path61.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
17822
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path62.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
17091
17823
  const handle = await startWatch(getGraph(project), {
17092
17824
  scanPath,
17093
17825
  outPath,
@@ -17270,11 +18002,11 @@ async function main() {
17270
18002
  process.exit(1);
17271
18003
  }
17272
18004
  async function tryOrchestrator(cmd, parsed) {
17273
- const scanPath = import_node_path61.default.resolve(cmd);
18005
+ const scanPath = import_node_path62.default.resolve(cmd);
17274
18006
  const stat = await import_node_fs41.promises.stat(scanPath).catch(() => null);
17275
18007
  if (!stat || !stat.isDirectory()) return null;
17276
18008
  const projectExplicit = parsed.project !== null;
17277
- const projectName = projectExplicit ? parsed.project : import_node_path61.default.basename(scanPath);
18009
+ const projectName = projectExplicit ? parsed.project : import_node_path62.default.basename(scanPath);
17278
18010
  const result = await runOrchestrator({
17279
18011
  scanPath,
17280
18012
  project: projectName,
@@ -17463,10 +18195,10 @@ async function runQueryVerb(cmd, parsed) {
17463
18195
  const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
17464
18196
  const out = [];
17465
18197
  for (const p of parts) {
17466
- const r = import_types49.DivergenceTypeSchema.safeParse(p);
18198
+ const r = import_types50.DivergenceTypeSchema.safeParse(p);
17467
18199
  if (!r.success) {
17468
18200
  console.error(
17469
- `neat divergences: unknown --type "${p}". allowed: ${import_types49.DivergenceTypeSchema.options.join(", ")}`
18201
+ `neat divergences: unknown --type "${p}". allowed: ${import_types50.DivergenceTypeSchema.options.join(", ")}`
17470
18202
  );
17471
18203
  return 2;
17472
18204
  }