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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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 path61 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
- if (exactUnauthPaths.has(path61) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path61)) {
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);
@@ -362,8 +362,8 @@ function websocketChannelPathOf(attrs) {
362
362
  const v = attrs[key];
363
363
  if (typeof v === "string" && v.length > 0) {
364
364
  const q = v.indexOf("?");
365
- const path61 = q === -1 ? v : v.slice(0, q);
366
- if (path61.length > 0) return path61;
365
+ const path63 = q === -1 ? v : v.slice(0, q);
366
+ if (path63.length > 0) return path63;
367
367
  }
368
368
  }
369
369
  return void 0;
@@ -398,6 +398,7 @@ function parseOtlpRequest(body) {
398
398
  attributes: attrs,
399
399
  dbSystem: typeof attrs["db.system"] === "string" ? attrs["db.system"] : void 0,
400
400
  dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
401
+ dbCollection: typeof attrs["db.collection.name"] === "string" ? attrs["db.collection.name"] : typeof attrs["db.mongodb.collection"] === "string" ? attrs["db.mongodb.collection"] : void 0,
401
402
  messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
402
403
  messagingDestination: messagingDestinationOf(attrs),
403
404
  graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
@@ -417,10 +418,10 @@ function parseOtlpRequest(body) {
417
418
  return out;
418
419
  }
419
420
  function loadProtoRoot() {
420
- const here = import_node_path50.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
421
- const protoRoot = import_node_path50.default.resolve(here, "..", "proto");
421
+ const here = import_node_path51.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
422
+ const protoRoot = import_node_path51.default.resolve(here, "..", "proto");
422
423
  const root = new import_protobufjs.default.Root();
423
- root.resolvePath = (_origin, target) => import_node_path50.default.resolve(protoRoot, target);
424
+ root.resolvePath = (_origin, target) => import_node_path51.default.resolve(protoRoot, target);
424
425
  root.loadSync(
425
426
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
426
427
  { keepCase: true }
@@ -648,12 +649,12 @@ async function listenSteppingOtlp(app, requestedPort, host) {
648
649
  }
649
650
  }
650
651
  }
651
- var import_node_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;
652
+ 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
653
  var init_otel = __esm({
653
654
  "src/otel.ts"() {
654
655
  "use strict";
655
656
  init_cjs_shims();
656
- import_node_path50 = __toESM(require("path"), 1);
657
+ import_node_path51 = __toESM(require("path"), 1);
657
658
  import_node_url3 = require("url");
658
659
  import_fastify2 = __toESM(require("fastify"), 1);
659
660
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -686,12 +687,12 @@ __export(cli_exports, {
686
687
  runInit: () => runInit,
687
688
  runQueryVerb: () => runQueryVerb,
688
689
  runSkill: () => runSkill,
689
- usage: () => usage
690
+ usage: () => usage2
690
691
  });
691
692
  module.exports = __toCommonJS(cli_exports);
692
693
  init_cjs_shims();
693
- var import_node_path60 = __toESM(require("path"), 1);
694
- var import_node_fs40 = require("fs");
694
+ var import_node_path62 = __toESM(require("path"), 1);
695
+ var import_node_fs41 = require("fs");
695
696
 
696
697
  // src/banner.ts
697
698
  init_cjs_shims();
@@ -1256,19 +1257,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1256
1257
  function longestIncomingWalk(graph, start, maxDepth) {
1257
1258
  let best = { path: [start], edges: [] };
1258
1259
  const visited = /* @__PURE__ */ new Set([start]);
1259
- function step(node, path61, edges) {
1260
- if (path61.length > best.path.length) {
1261
- best = { path: [...path61], edges: [...edges] };
1260
+ function step(node, path63, edges) {
1261
+ if (path63.length > best.path.length) {
1262
+ best = { path: [...path63], edges: [...edges] };
1262
1263
  }
1263
- if (path61.length - 1 >= maxDepth) return;
1264
+ if (path63.length - 1 >= maxDepth) return;
1264
1265
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1265
1266
  for (const [srcId, edge] of incoming) {
1266
1267
  if (visited.has(srcId)) continue;
1267
1268
  visited.add(srcId);
1268
- path61.push(srcId);
1269
+ path63.push(srcId);
1269
1270
  edges.push(edge);
1270
- step(srcId, path61, edges);
1271
- path61.pop();
1271
+ step(srcId, path63, edges);
1272
+ path63.pop();
1272
1273
  edges.pop();
1273
1274
  visited.delete(srcId);
1274
1275
  }
@@ -1462,26 +1463,26 @@ function dominantFailingCall(graph, serviceId6, visited) {
1462
1463
  return best;
1463
1464
  }
1464
1465
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1465
- const path61 = [originServiceId];
1466
+ const path63 = [originServiceId];
1466
1467
  const edges = [];
1467
1468
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1468
1469
  let current = originServiceId;
1469
1470
  for (let depth = 0; depth < maxDepth; depth++) {
1470
1471
  const hop = dominantFailingCall(graph, current, visited);
1471
1472
  if (!hop) break;
1472
- path61.push(hop.nextService);
1473
+ path63.push(hop.nextService);
1473
1474
  edges.push(hop.edge);
1474
1475
  visited.add(hop.nextService);
1475
1476
  current = hop.nextService;
1476
1477
  }
1477
1478
  if (edges.length === 0) return null;
1478
- return { path: path61, edges, culprit: current };
1479
+ return { path: path63, edges, culprit: current };
1479
1480
  }
1480
1481
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1481
1482
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1482
1483
  if (!chain) return null;
1483
1484
  const culprit = chain.culprit;
1484
- const path61 = [...chain.path];
1485
+ const path63 = [...chain.path];
1485
1486
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1486
1487
  const baseConfidence = confidenceFromMix(chain.edges);
1487
1488
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1489,14 +1490,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1489
1490
  if (loc) {
1490
1491
  let rootCauseNode = culprit;
1491
1492
  if (loc.fileNode) {
1492
- path61.push(loc.fileNode);
1493
+ path63.push(loc.fileNode);
1493
1494
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1494
1495
  rootCauseNode = loc.fileNode;
1495
1496
  }
1496
1497
  return import_types.RootCauseResultSchema.parse({
1497
1498
  rootCauseNode,
1498
1499
  rootCauseReason: loc.rootCauseReason,
1499
- traversalPath: path61,
1500
+ traversalPath: path63,
1500
1501
  edgeProvenances,
1501
1502
  confidence,
1502
1503
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1508,7 +1509,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1508
1509
  return import_types.RootCauseResultSchema.parse({
1509
1510
  rootCauseNode: culprit,
1510
1511
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1511
- traversalPath: path61,
1512
+ traversalPath: path63,
1512
1513
  edgeProvenances,
1513
1514
  confidence,
1514
1515
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2585,6 +2586,19 @@ function ensureServiceNode(graph, serviceName, env) {
2585
2586
  graph.addNode(id, node);
2586
2587
  return id;
2587
2588
  }
2589
+ function ensureInfraNode(graph, kind, name, provider) {
2590
+ const id = (0, import_types3.infraId)(kind, name);
2591
+ if (graph.hasNode(id)) return id;
2592
+ const node = {
2593
+ id,
2594
+ type: import_types3.NodeType.InfraNode,
2595
+ name,
2596
+ provider,
2597
+ kind
2598
+ };
2599
+ graph.addNode(id, node);
2600
+ return id;
2601
+ }
2588
2602
  function ensureDatabaseNode(graph, host, engine) {
2589
2603
  const id = (0, import_types3.databaseId)(host);
2590
2604
  if (graph.hasNode(id)) return id;
@@ -2922,6 +2936,18 @@ async function handleSpan(ctx, span) {
2922
2936
  callSiteEvidence
2923
2937
  );
2924
2938
  if (result) affectedNode = targetId;
2939
+ if (span.dbSystem === "mongodb" && span.dbCollection) {
2940
+ const collectionId = ensureInfraNode(ctx.graph, "mongodb-collection", span.dbCollection, "self");
2941
+ upsertObservedEdge(
2942
+ ctx.graph,
2943
+ import_types3.EdgeType.CALLS,
2944
+ observedSource(),
2945
+ collectionId,
2946
+ ts,
2947
+ isError,
2948
+ callSiteEvidence
2949
+ );
2950
+ }
2925
2951
  }
2926
2952
  } else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
2927
2953
  const targetId = ensureMessagingDestinationNode(
@@ -4678,14 +4704,21 @@ function engineFromImage(image) {
4678
4704
  // src/extract/databases/dotenv.ts
4679
4705
  var CONNECTION_KEYS = /* @__PURE__ */ new Set([
4680
4706
  "DATABASE_URL",
4707
+ "DATABASE_URI",
4681
4708
  "DB_URL",
4709
+ "DB_URI",
4682
4710
  "POSTGRES_URL",
4711
+ "POSTGRES_URI",
4683
4712
  "POSTGRESQL_URL",
4713
+ "POSTGRESQL_URI",
4684
4714
  "MYSQL_URL",
4715
+ "MYSQL_URI",
4716
+ "MONGODB_URL",
4685
4717
  "MONGODB_URI",
4686
4718
  "MONGO_URL",
4687
4719
  "MONGO_URI",
4688
- "REDIS_URL"
4720
+ "REDIS_URL",
4721
+ "REDIS_URI"
4689
4722
  ]);
4690
4723
  function parseDotenvLine(line) {
4691
4724
  const trimmed = line.trim();
@@ -5774,7 +5807,7 @@ async function addGrpcMethods(graph, services) {
5774
5807
 
5775
5808
  // src/extract/calls/index.ts
5776
5809
  init_cjs_shims();
5777
- var import_types20 = require("@neat.is/types");
5810
+ var import_types21 = require("@neat.is/types");
5778
5811
 
5779
5812
  // src/extract/calls/http.ts
5780
5813
  init_cjs_shims();
@@ -6487,15 +6520,311 @@ function supabaseEndpointsFromFile(file, serviceDir) {
6487
6520
  return out;
6488
6521
  }
6489
6522
 
6523
+ // src/extract/calls/mongoose.ts
6524
+ init_cjs_shims();
6525
+ var import_node_path32 = __toESM(require("path"), 1);
6526
+ var import_types20 = require("@neat.is/types");
6527
+ var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
6528
+ var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
6529
+ var PLURALIZE_DISABLED_RE = /\bpluralize\s*\(\s*(?:null|false)\s*\)/;
6530
+ var UNCOUNTABLES = /* @__PURE__ */ new Set([
6531
+ "advice",
6532
+ "energy",
6533
+ "excretion",
6534
+ "digestion",
6535
+ "cooperation",
6536
+ "health",
6537
+ "justice",
6538
+ "labour",
6539
+ "machinery",
6540
+ "equipment",
6541
+ "information",
6542
+ "pollution",
6543
+ "sewage",
6544
+ "paper",
6545
+ "money",
6546
+ "species",
6547
+ "series",
6548
+ "rain",
6549
+ "rice",
6550
+ "fish",
6551
+ "sheep",
6552
+ "moose",
6553
+ "deer",
6554
+ "news",
6555
+ "expertise",
6556
+ "status",
6557
+ "media"
6558
+ ]);
6559
+ var PLURALIZE_RULES = [
6560
+ [/human$/gi, "humans"],
6561
+ [/(m)an$/gi, "$1en"],
6562
+ [/(pe)rson$/gi, "$1ople"],
6563
+ [/(child)$/gi, "$1ren"],
6564
+ [/^(ox)$/gi, "$1en"],
6565
+ [/(ax|test)is$/gi, "$1es"],
6566
+ [/(octop|vir)us$/gi, "$1i"],
6567
+ [/(alias|status)$/gi, "$1es"],
6568
+ [/(bu)s$/gi, "$1ses"],
6569
+ [/(buffal|tomat|potat)o$/gi, "$1oes"],
6570
+ [/([ti])um$/gi, "$1a"],
6571
+ [/sis$/gi, "ses"],
6572
+ [/(?:([^f])fe|([lr])f)$/gi, "$1$2ves"],
6573
+ [/(hive)$/gi, "$1s"],
6574
+ [/([^aeiouy]|qu)y$/gi, "$1ies"],
6575
+ [/(x|ch|ss|sh)$/gi, "$1es"],
6576
+ [/(matr|vert|ind)ix|ex$/gi, "$1ices"],
6577
+ [/([m|l])ouse$/gi, "$1ice"],
6578
+ [/(kn|w|l)ife$/gi, "$1ives"],
6579
+ [/(quiz)$/gi, "$1zes"],
6580
+ [/s$/gi, "s"],
6581
+ [/([^a-z])$/, "$1"],
6582
+ [/$/gi, "s"]
6583
+ ];
6584
+ function pluralizeCollection(name) {
6585
+ const str = name.toLowerCase();
6586
+ if (UNCOUNTABLES.has(str)) return str;
6587
+ for (const [re, repl] of PLURALIZE_RULES) {
6588
+ if (str.match(re)) return str.replace(re, repl);
6589
+ }
6590
+ return str;
6591
+ }
6592
+ var MODEL_METHODS = [
6593
+ "find",
6594
+ "findOne",
6595
+ "findById",
6596
+ "findByIdAndUpdate",
6597
+ "findByIdAndDelete",
6598
+ "findOneAndUpdate",
6599
+ "findOneAndDelete",
6600
+ "findOneAndReplace",
6601
+ "countDocuments",
6602
+ "estimatedDocumentCount",
6603
+ "distinct",
6604
+ "aggregate",
6605
+ "exists",
6606
+ "where",
6607
+ "populate",
6608
+ "watch",
6609
+ "hydrate",
6610
+ "create",
6611
+ "insertMany",
6612
+ "insertOne",
6613
+ "updateOne",
6614
+ "updateMany",
6615
+ "replaceOne",
6616
+ "deleteOne",
6617
+ "deleteMany",
6618
+ "bulkWrite",
6619
+ "bulkSave",
6620
+ "save",
6621
+ // pre-v7, removed in current mongoose but present in older codebases:
6622
+ "count",
6623
+ "update",
6624
+ "remove",
6625
+ "findOneAndRemove",
6626
+ "findByIdAndRemove"
6627
+ ];
6628
+ var MODEL_METHODS_ALT = MODEL_METHODS.join("|");
6629
+ function schemaCollectionVars(content) {
6630
+ const out = /* @__PURE__ */ new Map();
6631
+ const re = /(?:const|let|var)\s+(\w+)\s*=\s*new\s+(?:mongoose\s*\.\s*)?Schema\s*\(/g;
6632
+ let m;
6633
+ while ((m = re.exec(content)) !== null) {
6634
+ const windowText = content.slice(m.index, m.index + 2e3);
6635
+ const opt = /\bcollection\s*:\s*['"`]([\w.-]+)['"`]/.exec(windowText);
6636
+ if (opt) out.set(m[1], opt[1]);
6637
+ }
6638
+ return out;
6639
+ }
6640
+ function resolveModel(modelName, rest, schemaColl, pluralizeOn) {
6641
+ const trimmed = rest.trim();
6642
+ const literalThird = /,\s*['"`]([\w.-]+)['"`]\s*$/.exec(trimmed);
6643
+ if (literalThird) return { name: literalThird[1], kind: "mongodb-collection" };
6644
+ const firstIdent = /^([A-Za-z_$][\w$]*)/.exec(trimmed)?.[1];
6645
+ if (firstIdent && schemaColl.has(firstIdent)) {
6646
+ return { name: schemaColl.get(firstIdent), kind: "mongodb-collection" };
6647
+ }
6648
+ if (/,\s*[A-Za-z_$][\w$]*\s*$/.test(trimmed)) {
6649
+ return { name: modelName, kind: "mongodb-model" };
6650
+ }
6651
+ return { name: pluralizeOn ? pluralizeCollection(modelName) : modelName, kind: "mongodb-collection" };
6652
+ }
6653
+ function collectModelDefs(content, pluralizeOn) {
6654
+ const schemaColl = schemaCollectionVars(content);
6655
+ const out = [];
6656
+ 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;
6657
+ let m;
6658
+ while ((m = re.exec(content)) !== null) {
6659
+ out.push({
6660
+ modelName: m[2],
6661
+ varName: m[1] ?? null,
6662
+ resolved: resolveModel(m[2], m[3] ?? "", schemaColl, pluralizeOn),
6663
+ matchText: m[0]
6664
+ });
6665
+ }
6666
+ return out;
6667
+ }
6668
+ function endpoint(r, file, serviceDir, matchText) {
6669
+ const line = lineOf(file.content, matchText);
6670
+ return {
6671
+ infraId: (0, import_types20.infraId)(r.kind, r.name),
6672
+ name: r.name,
6673
+ kind: r.kind,
6674
+ edgeType: "CALLS",
6675
+ confidenceKind: "verified-call-site",
6676
+ evidence: { file: import_node_path32.default.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
6677
+ };
6678
+ }
6679
+ function mongooseEndpointsFromFile(file, serviceDir) {
6680
+ const hasMongoose = MONGOOSE_IMPORT_RE.test(file.content);
6681
+ const hasMongodb = MONGODB_IMPORT_RE.test(file.content);
6682
+ if (!hasMongoose && !hasMongodb) return [];
6683
+ const content = file.content;
6684
+ const out = [];
6685
+ const seen = /* @__PURE__ */ new Set();
6686
+ const push = (r, matchText) => {
6687
+ const key = `${r.kind}/${r.name}`;
6688
+ if (seen.has(key)) return;
6689
+ seen.add(key);
6690
+ out.push(endpoint(r, file, serviceDir, matchText));
6691
+ };
6692
+ if (hasMongoose) {
6693
+ const pluralizeOn = !PLURALIZE_DISABLED_RE.test(content);
6694
+ for (const def of collectModelDefs(content, pluralizeOn)) push(def.resolved, def.matchText);
6695
+ }
6696
+ const collRe = /\.\s*collection\s*\(\s*['"`]([\w.$-]+)['"`]\s*\)/g;
6697
+ let c;
6698
+ while ((c = collRe.exec(content)) !== null) {
6699
+ push({ name: c[1], kind: "mongodb-collection" }, c[0]);
6700
+ }
6701
+ return out;
6702
+ }
6703
+ function parseDestructure(inner, specifier, out) {
6704
+ for (const raw of inner.split(",")) {
6705
+ const part = raw.trim();
6706
+ if (!part) continue;
6707
+ const asM = /^(\w+)\s+as\s+(\w+)$/.exec(part);
6708
+ const colonM = /^(\w+)\s*:\s*(\w+)$/.exec(part);
6709
+ if (asM) out.push({ local: asM[2], kind: "named", exportName: asM[1], specifier });
6710
+ else if (colonM) out.push({ local: colonM[2], kind: "named", exportName: colonM[1], specifier });
6711
+ else {
6712
+ const id = /^(\w+)$/.exec(part);
6713
+ if (id) out.push({ local: id[1], kind: "named", exportName: id[1], specifier });
6714
+ }
6715
+ }
6716
+ }
6717
+ function parseImportBindings(content) {
6718
+ const out = [];
6719
+ const esm = /import\s+([^;'"`\n]+?)\s+from\s*['"`]([^'"`]+)['"`]/g;
6720
+ let m;
6721
+ while ((m = esm.exec(content)) !== null) {
6722
+ const clause = m[1].trim();
6723
+ const spec = m[2];
6724
+ const ns = /^\*\s+as\s+(\w+)$/.exec(clause);
6725
+ if (ns) {
6726
+ out.push({ local: ns[1], kind: "namespace", specifier: spec });
6727
+ continue;
6728
+ }
6729
+ const named = /\{([^}]*)\}/.exec(clause);
6730
+ if (named) parseDestructure(named[1], spec, out);
6731
+ const def = /^(\w+)\s*(?:,|$)/.exec(clause);
6732
+ if (def && !clause.startsWith("{")) out.push({ local: def[1], kind: "default", specifier: spec });
6733
+ }
6734
+ const cjs = /(?:const|let|var)\s+(\{[^}]*\}|\w+)\s*=\s*require\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
6735
+ while ((m = cjs.exec(content)) !== null) {
6736
+ const lhs = m[1];
6737
+ const spec = m[2];
6738
+ if (lhs.startsWith("{")) parseDestructure(lhs.slice(1, -1), spec, out);
6739
+ else out.push({ local: lhs, kind: "whole", specifier: spec });
6740
+ }
6741
+ return out;
6742
+ }
6743
+ function fileExportsOf(content, pluralizeOn) {
6744
+ const defs = collectModelDefs(content, pluralizeOn).filter((d) => d.resolved.kind === "mongodb-collection");
6745
+ if (defs.length === 0) return null;
6746
+ const byVar = /* @__PURE__ */ new Map();
6747
+ for (const d of defs) if (d.varName) byVar.set(d.varName, d.resolved.name);
6748
+ const byName = new Map(byVar);
6749
+ let def;
6750
+ const named = /(?:module\.exports|export\s+default)\s*=\s*(\w+)\b/.exec(content);
6751
+ if (named && byVar.has(named[1])) def = byVar.get(named[1]);
6752
+ if (!def) {
6753
+ const inline = /(?:module\.exports|export\s+default)\s*=\s*(?:await\s+)?(?:\w+\s*\.\s*)?model\s*\(\s*['"`]([\w$]+)['"`]/.exec(content);
6754
+ if (inline) def = pluralizeOn ? pluralizeCollection(inline[1]) : inline[1];
6755
+ }
6756
+ if (!def && byVar.size === 1) def = [...byVar.values()][0];
6757
+ return { byName, ...def ? { default: def } : {} };
6758
+ }
6759
+ async function mongooseCrossFileEndpoints(files, serviceDir) {
6760
+ const mongooseFiles = files.filter((f) => MONGOOSE_IMPORT_RE.test(f.content));
6761
+ if (mongooseFiles.length === 0) return [];
6762
+ const pluralizeOn = !files.some((f) => PLURALIZE_DISABLED_RE.test(f.content));
6763
+ const registry = /* @__PURE__ */ new Map();
6764
+ for (const f of mongooseFiles) {
6765
+ const fx = fileExportsOf(f.content, pluralizeOn);
6766
+ if (fx) registry.set(toPosix2(import_node_path32.default.relative(serviceDir, f.path)), fx);
6767
+ }
6768
+ if (registry.size === 0) return [];
6769
+ const out = [];
6770
+ const seen = /* @__PURE__ */ new Set();
6771
+ for (const f of files) {
6772
+ const bindings = parseImportBindings(f.content);
6773
+ if (bindings.length === 0) continue;
6774
+ const directColl = /* @__PURE__ */ new Map();
6775
+ const nsExports = /* @__PURE__ */ new Map();
6776
+ for (const b of bindings) {
6777
+ const resolvedRel = await resolveJsImport(b.specifier, import_node_path32.default.dirname(f.path), serviceDir, null);
6778
+ if (!resolvedRel) continue;
6779
+ const fx = registry.get(resolvedRel);
6780
+ if (!fx) continue;
6781
+ if (b.kind === "named" && b.exportName) {
6782
+ const coll = fx.byName.get(b.exportName);
6783
+ if (coll) directColl.set(b.local, coll);
6784
+ } else if (b.kind === "default") {
6785
+ if (fx.default) directColl.set(b.local, fx.default);
6786
+ } else {
6787
+ if (fx.default) directColl.set(b.local, fx.default);
6788
+ nsExports.set(b.local, fx);
6789
+ }
6790
+ }
6791
+ if (directColl.size === 0 && nsExports.size === 0) continue;
6792
+ const localDefs = new Set(
6793
+ collectModelDefs(f.content, pluralizeOn).map((d) => d.varName).filter((v) => v !== null)
6794
+ );
6795
+ const emit = (collection, matchText) => {
6796
+ const key = `${f.path}::${collection}`;
6797
+ if (seen.has(key)) return;
6798
+ seen.add(key);
6799
+ out.push(endpoint({ name: collection, kind: "mongodb-collection" }, f, serviceDir, matchText));
6800
+ };
6801
+ for (const [local, collection] of directColl) {
6802
+ if (localDefs.has(local)) continue;
6803
+ const qre = new RegExp(`\\b${local}\\s*\\.\\s*(?:${MODEL_METHODS_ALT})\\s*\\(`, "g");
6804
+ let qm;
6805
+ while ((qm = qre.exec(f.content)) !== null) emit(collection, qm[0]);
6806
+ }
6807
+ for (const [local, fx] of nsExports) {
6808
+ const qre = new RegExp(`\\b${local}\\s*\\.\\s*(\\w+)\\s*\\.\\s*(?:${MODEL_METHODS_ALT})\\s*\\(`, "g");
6809
+ let qm;
6810
+ while ((qm = qre.exec(f.content)) !== null) {
6811
+ const coll = fx.byName.get(qm[1]);
6812
+ if (coll) emit(coll, qm[0]);
6813
+ }
6814
+ }
6815
+ }
6816
+ return out;
6817
+ }
6818
+
6490
6819
  // src/extract/calls/index.ts
6491
6820
  function edgeTypeFromEndpoint(ep) {
6492
6821
  switch (ep.edgeType) {
6493
6822
  case "PUBLISHES_TO":
6494
- return import_types20.EdgeType.PUBLISHES_TO;
6823
+ return import_types21.EdgeType.PUBLISHES_TO;
6495
6824
  case "CONSUMES_FROM":
6496
- return import_types20.EdgeType.CONSUMES_FROM;
6825
+ return import_types21.EdgeType.CONSUMES_FROM;
6497
6826
  default:
6498
- return import_types20.EdgeType.CALLS;
6827
+ return import_types21.EdgeType.CALLS;
6499
6828
  }
6500
6829
  }
6501
6830
  function isAwsKind(kind) {
@@ -6507,23 +6836,27 @@ async function addExternalEndpointEdges(graph, services) {
6507
6836
  for (const service of services) {
6508
6837
  const files = await loadSourceFiles(service.dir);
6509
6838
  const endpoints = [];
6839
+ const maskedFiles = [];
6510
6840
  for (const file of files) {
6511
6841
  if (isTestPath(file.path)) continue;
6512
6842
  const masked = maskCommentsInSource(file.content);
6513
6843
  const maskedFile = { path: file.path, content: masked };
6844
+ maskedFiles.push(maskedFile);
6514
6845
  endpoints.push(...kafkaEndpointsFromFile(maskedFile, service.dir));
6515
6846
  endpoints.push(...redisEndpointsFromFile(maskedFile, service.dir));
6516
6847
  endpoints.push(...awsEndpointsFromFile(maskedFile, service.dir));
6517
6848
  endpoints.push(...grpcEndpointsFromFile(maskedFile, service.dir));
6518
6849
  endpoints.push(...supabaseEndpointsFromFile(maskedFile, service.dir));
6850
+ endpoints.push(...mongooseEndpointsFromFile(maskedFile, service.dir));
6519
6851
  }
6852
+ endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
6520
6853
  if (endpoints.length === 0) continue;
6521
6854
  const seenEdges = /* @__PURE__ */ new Set();
6522
6855
  for (const ep of endpoints) {
6523
6856
  if (!graph.hasNode(ep.infraId)) {
6524
6857
  const node = {
6525
6858
  id: ep.infraId,
6526
- type: import_types20.NodeType.InfraNode,
6859
+ type: import_types21.NodeType.InfraNode,
6527
6860
  name: ep.name,
6528
6861
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
6529
6862
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -6535,7 +6868,7 @@ async function addExternalEndpointEdges(graph, services) {
6535
6868
  nodesAdded++;
6536
6869
  }
6537
6870
  const edgeType = edgeTypeFromEndpoint(ep);
6538
- const confidence = (0, import_types20.confidenceForExtracted)(ep.confidenceKind);
6871
+ const confidence = (0, import_types21.confidenceForExtracted)(ep.confidenceKind);
6539
6872
  const relFile = toPosix2(ep.evidence.file);
6540
6873
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
6541
6874
  graph,
@@ -6545,7 +6878,7 @@ async function addExternalEndpointEdges(graph, services) {
6545
6878
  );
6546
6879
  nodesAdded += n;
6547
6880
  edgesAdded += e;
6548
- if (!(0, import_types20.passesExtractedFloor)(confidence)) {
6881
+ if (!(0, import_types21.passesExtractedFloor)(confidence)) {
6549
6882
  noteExtractedDropped({
6550
6883
  source: fileNodeId,
6551
6884
  target: ep.infraId,
@@ -6565,7 +6898,7 @@ async function addExternalEndpointEdges(graph, services) {
6565
6898
  source: fileNodeId,
6566
6899
  target: ep.infraId,
6567
6900
  type: edgeType,
6568
- provenance: import_types20.Provenance.EXTRACTED,
6901
+ provenance: import_types21.Provenance.EXTRACTED,
6569
6902
  confidence,
6570
6903
  evidence: ep.evidence
6571
6904
  };
@@ -6591,16 +6924,16 @@ init_cjs_shims();
6591
6924
 
6592
6925
  // src/extract/infra/docker-compose.ts
6593
6926
  init_cjs_shims();
6594
- var import_node_path32 = __toESM(require("path"), 1);
6595
- var import_types22 = require("@neat.is/types");
6927
+ var import_node_path33 = __toESM(require("path"), 1);
6928
+ var import_types23 = require("@neat.is/types");
6596
6929
 
6597
6930
  // src/extract/infra/shared.ts
6598
6931
  init_cjs_shims();
6599
- var import_types21 = require("@neat.is/types");
6932
+ var import_types22 = require("@neat.is/types");
6600
6933
  function makeInfraNode(kind, name, provider = "self", extras) {
6601
6934
  return {
6602
- id: (0, import_types21.infraId)(kind, name),
6603
- type: import_types21.NodeType.InfraNode,
6935
+ id: (0, import_types22.infraId)(kind, name),
6936
+ type: import_types22.NodeType.InfraNode,
6604
6937
  name,
6605
6938
  provider,
6606
6939
  kind,
@@ -6644,8 +6977,8 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
6644
6977
  source: anchorId,
6645
6978
  target: node.id,
6646
6979
  type: edgeType,
6647
- provenance: import_types21.Provenance.EXTRACTED,
6648
- confidence: (0, import_types21.confidenceForExtracted)("structural"),
6980
+ provenance: import_types22.Provenance.EXTRACTED,
6981
+ confidence: (0, import_types22.confidenceForExtracted)("structural"),
6649
6982
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
6650
6983
  };
6651
6984
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6662,7 +6995,7 @@ function dependsOnList(value) {
6662
6995
  }
6663
6996
  function serviceNameToServiceNode(name, services) {
6664
6997
  for (const s of services) {
6665
- if (s.node.name === name || import_node_path32.default.basename(s.dir) === name) return s.node.id;
6998
+ if (s.node.name === name || import_node_path33.default.basename(s.dir) === name) return s.node.id;
6666
6999
  }
6667
7000
  return null;
6668
7001
  }
@@ -6671,7 +7004,7 @@ async function addComposeInfra(graph, scanPath, services) {
6671
7004
  let edgesAdded = 0;
6672
7005
  let composePath = null;
6673
7006
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
6674
- const abs = import_node_path32.default.join(scanPath, name);
7007
+ const abs = import_node_path33.default.join(scanPath, name);
6675
7008
  if (await exists(abs)) {
6676
7009
  composePath = abs;
6677
7010
  break;
@@ -6684,13 +7017,13 @@ async function addComposeInfra(graph, scanPath, services) {
6684
7017
  } catch (err) {
6685
7018
  recordExtractionError(
6686
7019
  "infra docker-compose",
6687
- import_node_path32.default.relative(scanPath, composePath),
7020
+ import_node_path33.default.relative(scanPath, composePath),
6688
7021
  err
6689
7022
  );
6690
7023
  return { nodesAdded, edgesAdded };
6691
7024
  }
6692
7025
  if (!compose?.services) return { nodesAdded, edgesAdded };
6693
- const evidenceFile = import_node_path32.default.relative(scanPath, composePath).split(import_node_path32.default.sep).join("/");
7026
+ const evidenceFile = import_node_path33.default.relative(scanPath, composePath).split(import_node_path33.default.sep).join("/");
6694
7027
  const composeNameToNodeId = /* @__PURE__ */ new Map();
6695
7028
  for (const [composeName, svc] of Object.entries(compose.services)) {
6696
7029
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -6712,15 +7045,15 @@ async function addComposeInfra(graph, scanPath, services) {
6712
7045
  for (const dep of dependsOnList(svc.depends_on)) {
6713
7046
  const targetId = composeNameToNodeId.get(dep);
6714
7047
  if (!targetId) continue;
6715
- const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types22.EdgeType.DEPENDS_ON);
7048
+ const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types23.EdgeType.DEPENDS_ON);
6716
7049
  if (graph.hasEdge(edgeId)) continue;
6717
7050
  const edge = {
6718
7051
  id: edgeId,
6719
7052
  source: sourceId,
6720
7053
  target: targetId,
6721
- type: import_types22.EdgeType.DEPENDS_ON,
6722
- provenance: import_types22.Provenance.EXTRACTED,
6723
- confidence: (0, import_types22.confidenceForExtracted)("structural"),
7054
+ type: import_types23.EdgeType.DEPENDS_ON,
7055
+ provenance: import_types23.Provenance.EXTRACTED,
7056
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
6724
7057
  evidence: { file: evidenceFile }
6725
7058
  };
6726
7059
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6732,9 +7065,9 @@ async function addComposeInfra(graph, scanPath, services) {
6732
7065
 
6733
7066
  // src/extract/infra/dockerfile.ts
6734
7067
  init_cjs_shims();
6735
- var import_node_path33 = __toESM(require("path"), 1);
7068
+ var import_node_path34 = __toESM(require("path"), 1);
6736
7069
  var import_node_fs17 = require("fs");
6737
- var import_types23 = require("@neat.is/types");
7070
+ var import_types24 = require("@neat.is/types");
6738
7071
  function readDockerfile(content) {
6739
7072
  let image = null;
6740
7073
  const ports = [];
@@ -6763,7 +7096,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6763
7096
  let nodesAdded = 0;
6764
7097
  let edgesAdded = 0;
6765
7098
  for (const service of services) {
6766
- const dockerfilePath = import_node_path33.default.join(service.dir, "Dockerfile");
7099
+ const dockerfilePath = import_node_path34.default.join(service.dir, "Dockerfile");
6767
7100
  if (!await exists(dockerfilePath)) continue;
6768
7101
  let content;
6769
7102
  try {
@@ -6771,7 +7104,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6771
7104
  } catch (err) {
6772
7105
  recordExtractionError(
6773
7106
  "infra dockerfile",
6774
- import_node_path33.default.relative(scanPath, dockerfilePath),
7107
+ import_node_path34.default.relative(scanPath, dockerfilePath),
6775
7108
  err
6776
7109
  );
6777
7110
  continue;
@@ -6783,8 +7116,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6783
7116
  graph.addNode(node.id, node);
6784
7117
  nodesAdded++;
6785
7118
  }
6786
- const relDockerfile = toPosix2(import_node_path33.default.relative(service.dir, dockerfilePath));
6787
- const evidenceFile = toPosix2(import_node_path33.default.relative(scanPath, dockerfilePath));
7119
+ const relDockerfile = toPosix2(import_node_path34.default.relative(service.dir, dockerfilePath));
7120
+ const evidenceFile = toPosix2(import_node_path34.default.relative(scanPath, dockerfilePath));
6788
7121
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
6789
7122
  graph,
6790
7123
  service.pkg.name,
@@ -6793,15 +7126,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6793
7126
  );
6794
7127
  nodesAdded += fn;
6795
7128
  edgesAdded += fe;
6796
- const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types23.EdgeType.RUNS_ON);
7129
+ const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types24.EdgeType.RUNS_ON);
6797
7130
  if (!graph.hasEdge(edgeId)) {
6798
7131
  const edge = {
6799
7132
  id: edgeId,
6800
7133
  source: fileNodeId,
6801
7134
  target: node.id,
6802
- type: import_types23.EdgeType.RUNS_ON,
6803
- provenance: import_types23.Provenance.EXTRACTED,
6804
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
7135
+ type: import_types24.EdgeType.RUNS_ON,
7136
+ provenance: import_types24.Provenance.EXTRACTED,
7137
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
6805
7138
  evidence: {
6806
7139
  file: evidenceFile,
6807
7140
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -6816,15 +7149,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6816
7149
  graph.addNode(portNode.id, portNode);
6817
7150
  nodesAdded++;
6818
7151
  }
6819
- const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types23.EdgeType.CONNECTS_TO);
7152
+ const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types24.EdgeType.CONNECTS_TO);
6820
7153
  if (graph.hasEdge(portEdgeId)) continue;
6821
7154
  const portEdge = {
6822
7155
  id: portEdgeId,
6823
7156
  source: fileNodeId,
6824
7157
  target: portNode.id,
6825
- type: import_types23.EdgeType.CONNECTS_TO,
6826
- provenance: import_types23.Provenance.EXTRACTED,
6827
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
7158
+ type: import_types24.EdgeType.CONNECTS_TO,
7159
+ provenance: import_types24.Provenance.EXTRACTED,
7160
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
6828
7161
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
6829
7162
  };
6830
7163
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -6837,8 +7170,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
6837
7170
  // src/extract/infra/terraform.ts
6838
7171
  init_cjs_shims();
6839
7172
  var import_node_fs18 = require("fs");
6840
- var import_node_path34 = __toESM(require("path"), 1);
6841
- var import_types24 = require("@neat.is/types");
7173
+ var import_node_path35 = __toESM(require("path"), 1);
7174
+ var import_types25 = require("@neat.is/types");
6842
7175
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
6843
7176
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
6844
7177
  async function walkTfFiles(start, depth = 0, max = 5) {
@@ -6848,11 +7181,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
6848
7181
  for (const entry2 of entries) {
6849
7182
  if (entry2.isDirectory()) {
6850
7183
  if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
6851
- const child = import_node_path34.default.join(start, entry2.name);
7184
+ const child = import_node_path35.default.join(start, entry2.name);
6852
7185
  if (await isPythonVenvDir(child)) continue;
6853
7186
  out.push(...await walkTfFiles(child, depth + 1, max));
6854
7187
  } else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
6855
- out.push(import_node_path34.default.join(start, entry2.name));
7188
+ out.push(import_node_path35.default.join(start, entry2.name));
6856
7189
  }
6857
7190
  }
6858
7191
  return out;
@@ -6884,7 +7217,7 @@ async function addTerraformResources(graph, scanPath) {
6884
7217
  const files = await walkTfFiles(scanPath);
6885
7218
  for (const file of files) {
6886
7219
  const content = await import_node_fs18.promises.readFile(file, "utf8");
6887
- const evidenceFile = toPosix2(import_node_path34.default.relative(scanPath, file));
7220
+ const evidenceFile = toPosix2(import_node_path35.default.relative(scanPath, file));
6888
7221
  const resources = [];
6889
7222
  const byKey = /* @__PURE__ */ new Map();
6890
7223
  RESOURCE_RE.lastIndex = 0;
@@ -6919,16 +7252,16 @@ async function addTerraformResources(graph, scanPath) {
6919
7252
  if (!target) continue;
6920
7253
  if (seen.has(target.nodeId)) continue;
6921
7254
  seen.add(target.nodeId);
6922
- const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types24.EdgeType.DEPENDS_ON);
7255
+ const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types25.EdgeType.DEPENDS_ON);
6923
7256
  if (graph.hasEdge(edgeId)) continue;
6924
7257
  const line = lineAt2(content, resource.bodyOffset + ref.index);
6925
7258
  const edge = {
6926
7259
  id: edgeId,
6927
7260
  source: resource.nodeId,
6928
7261
  target: target.nodeId,
6929
- type: import_types24.EdgeType.DEPENDS_ON,
6930
- provenance: import_types24.Provenance.EXTRACTED,
6931
- confidence: (0, import_types24.confidenceForExtracted)("structural"),
7262
+ type: import_types25.EdgeType.DEPENDS_ON,
7263
+ provenance: import_types25.Provenance.EXTRACTED,
7264
+ confidence: (0, import_types25.confidenceForExtracted)("structural"),
6932
7265
  evidence: { file: evidenceFile, line, snippet: key }
6933
7266
  };
6934
7267
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -6942,7 +7275,7 @@ async function addTerraformResources(graph, scanPath) {
6942
7275
  // src/extract/infra/k8s.ts
6943
7276
  init_cjs_shims();
6944
7277
  var import_node_fs19 = require("fs");
6945
- var import_node_path35 = __toESM(require("path"), 1);
7278
+ var import_node_path36 = __toESM(require("path"), 1);
6946
7279
  var import_yaml3 = require("yaml");
6947
7280
  var K8S_KIND_TO_INFRA_KIND = {
6948
7281
  Service: "k8s-service",
@@ -6960,11 +7293,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
6960
7293
  for (const entry2 of entries) {
6961
7294
  if (entry2.isDirectory()) {
6962
7295
  if (IGNORED_DIRS.has(entry2.name)) continue;
6963
- const child = import_node_path35.default.join(start, entry2.name);
7296
+ const child = import_node_path36.default.join(start, entry2.name);
6964
7297
  if (await isPythonVenvDir(child)) continue;
6965
7298
  out.push(...await walkYamlFiles2(child, depth + 1, max));
6966
- } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path35.default.extname(entry2.name))) {
6967
- out.push(import_node_path35.default.join(start, entry2.name));
7299
+ } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path36.default.extname(entry2.name))) {
7300
+ out.push(import_node_path36.default.join(start, entry2.name));
6968
7301
  }
6969
7302
  }
6970
7303
  return out;
@@ -6998,13 +7331,13 @@ async function addK8sResources(graph, scanPath) {
6998
7331
  // src/extract/infra/cloudflare.ts
6999
7332
  init_cjs_shims();
7000
7333
  var import_node_fs20 = require("fs");
7001
- var import_node_path36 = __toESM(require("path"), 1);
7334
+ var import_node_path37 = __toESM(require("path"), 1);
7002
7335
  var import_smol_toml2 = require("smol-toml");
7003
- var import_types25 = require("@neat.is/types");
7336
+ var import_types26 = require("@neat.is/types");
7004
7337
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
7005
7338
  async function readWranglerConfig(dir) {
7006
7339
  for (const filename of WRANGLER_FILENAMES) {
7007
- const abs = import_node_path36.default.join(dir, filename);
7340
+ const abs = import_node_path37.default.join(dir, filename);
7008
7341
  if (!await exists(abs)) continue;
7009
7342
  const raw = await import_node_fs20.promises.readFile(abs, "utf8");
7010
7343
  const config = filename === "wrangler.toml" ? (0, import_smol_toml2.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -7048,8 +7381,8 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
7048
7381
  source: anchorId,
7049
7382
  target: node.id,
7050
7383
  type: edgeType,
7051
- provenance: import_types25.Provenance.EXTRACTED,
7052
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7384
+ provenance: import_types26.Provenance.EXTRACTED,
7385
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7053
7386
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
7054
7387
  };
7055
7388
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7067,11 +7400,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7067
7400
  try {
7068
7401
  read = await readWranglerConfig(service.dir);
7069
7402
  } catch (err) {
7070
- recordExtractionError("infra cloudflare", import_node_path36.default.relative(scanPath, service.dir), err);
7403
+ recordExtractionError("infra cloudflare", import_node_path37.default.relative(scanPath, service.dir), err);
7071
7404
  continue;
7072
7405
  }
7073
7406
  if (!read || !read.config.name) continue;
7074
- const evidenceFile = toPosix2(import_node_path36.default.relative(scanPath, import_node_path36.default.join(service.dir, read.relFile)));
7407
+ const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, read.relFile)));
7075
7408
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
7076
7409
  }
7077
7410
  for (const worker of discovered) {
@@ -7083,7 +7416,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7083
7416
  }
7084
7417
  let anchorId = service.node.id;
7085
7418
  if (config.main) {
7086
- const entryRelPath = toPosix2(import_node_path36.default.normalize(config.main));
7419
+ const entryRelPath = toPosix2(import_node_path37.default.normalize(config.main));
7087
7420
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
7088
7421
  graph,
7089
7422
  service.pkg.name,
@@ -7110,15 +7443,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7110
7443
  nodesAdded++;
7111
7444
  }
7112
7445
  if (runtimeNode.id !== anchorId) {
7113
- const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id, import_types25.EdgeType.RUNS_ON);
7446
+ const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id, import_types26.EdgeType.RUNS_ON);
7114
7447
  if (!graph.hasEdge(runsOnId)) {
7115
7448
  const edge = {
7116
7449
  id: runsOnId,
7117
7450
  source: anchorId,
7118
7451
  target: runtimeNode.id,
7119
- type: import_types25.EdgeType.RUNS_ON,
7120
- provenance: import_types25.Provenance.EXTRACTED,
7121
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7452
+ type: import_types26.EdgeType.RUNS_ON,
7453
+ provenance: import_types26.Provenance.EXTRACTED,
7454
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7122
7455
  evidence: {
7123
7456
  file: evidenceFile,
7124
7457
  ...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
@@ -7132,7 +7465,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7132
7465
  const result = addResourceEdge(
7133
7466
  graph,
7134
7467
  anchorId,
7135
- import_types25.EdgeType.CONNECTS_TO,
7468
+ import_types26.EdgeType.CONNECTS_TO,
7136
7469
  "cloudflare-route",
7137
7470
  route,
7138
7471
  evidenceFile,
@@ -7156,7 +7489,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7156
7489
  const result = addResourceEdge(
7157
7490
  graph,
7158
7491
  anchorId,
7159
- import_types25.EdgeType.DEPENDS_ON,
7492
+ import_types26.EdgeType.DEPENDS_ON,
7160
7493
  group.kind,
7161
7494
  name,
7162
7495
  evidenceFile,
@@ -7170,7 +7503,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7170
7503
  const result = addResourceEdge(
7171
7504
  graph,
7172
7505
  anchorId,
7173
- import_types25.EdgeType.DEPENDS_ON,
7506
+ import_types26.EdgeType.DEPENDS_ON,
7174
7507
  "cloudflare-cron",
7175
7508
  cron,
7176
7509
  evidenceFile,
@@ -7183,7 +7516,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7183
7516
  const result = addResourceEdge(
7184
7517
  graph,
7185
7518
  anchorId,
7186
- import_types25.EdgeType.DEPENDS_ON,
7519
+ import_types26.EdgeType.DEPENDS_ON,
7187
7520
  "cloudflare-env-var",
7188
7521
  varName,
7189
7522
  evidenceFile,
@@ -7196,15 +7529,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7196
7529
  if (!svc.service) continue;
7197
7530
  const target = workerIndex.get(svc.service);
7198
7531
  if (target && target.anchorId !== anchorId) {
7199
- const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId, import_types25.EdgeType.CALLS);
7532
+ const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId, import_types26.EdgeType.CALLS);
7200
7533
  if (!graph.hasEdge(edgeId)) {
7201
7534
  const edge = {
7202
7535
  id: edgeId,
7203
7536
  source: anchorId,
7204
7537
  target: target.anchorId,
7205
- type: import_types25.EdgeType.CALLS,
7206
- provenance: import_types25.Provenance.EXTRACTED,
7207
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7538
+ type: import_types26.EdgeType.CALLS,
7539
+ provenance: import_types26.Provenance.EXTRACTED,
7540
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7208
7541
  evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
7209
7542
  };
7210
7543
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7215,7 +7548,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7215
7548
  const result = addResourceEdge(
7216
7549
  graph,
7217
7550
  anchorId,
7218
- import_types25.EdgeType.DEPENDS_ON,
7551
+ import_types26.EdgeType.DEPENDS_ON,
7219
7552
  "cloudflare-service-binding",
7220
7553
  svc.service,
7221
7554
  evidenceFile,
@@ -7231,12 +7564,12 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7231
7564
  // src/extract/infra/vercel.ts
7232
7565
  init_cjs_shims();
7233
7566
  var import_node_fs21 = require("fs");
7234
- var import_node_path37 = __toESM(require("path"), 1);
7235
- var import_types26 = require("@neat.is/types");
7567
+ var import_node_path38 = __toESM(require("path"), 1);
7568
+ var import_types27 = require("@neat.is/types");
7236
7569
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
7237
7570
  async function readVercelConfig(dir) {
7238
7571
  for (const filename of VERCEL_CONFIG_FILENAMES) {
7239
- const abs = import_node_path37.default.join(dir, filename);
7572
+ const abs = import_node_path38.default.join(dir, filename);
7240
7573
  if (!await exists(abs)) continue;
7241
7574
  const raw = await import_node_fs21.promises.readFile(abs, "utf8");
7242
7575
  const config = JSON.parse(maskCommentsInSource(raw));
@@ -7245,7 +7578,7 @@ async function readVercelConfig(dir) {
7245
7578
  return null;
7246
7579
  }
7247
7580
  async function readLinkedProjectName(dir) {
7248
- const abs = import_node_path37.default.join(dir, ".vercel", "project.json");
7581
+ const abs = import_node_path38.default.join(dir, ".vercel", "project.json");
7249
7582
  if (!await exists(abs)) return void 0;
7250
7583
  const parsed = JSON.parse(await import_node_fs21.promises.readFile(abs, "utf8"));
7251
7584
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
@@ -7263,7 +7596,7 @@ async function addVercelServices(graph, services, scanPath) {
7263
7596
  read = await readVercelConfig(service.dir);
7264
7597
  projectName = await readLinkedProjectName(service.dir);
7265
7598
  } catch (err) {
7266
- recordExtractionError("infra vercel", import_node_path37.default.relative(scanPath, service.dir), err);
7599
+ recordExtractionError("infra vercel", import_node_path38.default.relative(scanPath, service.dir), err);
7267
7600
  continue;
7268
7601
  }
7269
7602
  if (!read && !projectName) continue;
@@ -7279,7 +7612,7 @@ async function addVercelServices(graph, services, scanPath) {
7279
7612
  const anchorId = service.node.id;
7280
7613
  if (!read) continue;
7281
7614
  const { config, relFile, raw } = read;
7282
- const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, relFile)));
7615
+ const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
7283
7616
  const add = (edgeType, kind, name) => {
7284
7617
  if (!name) return;
7285
7618
  const result = emitPlatformResourceEdge(
@@ -7295,12 +7628,12 @@ async function addVercelServices(graph, services, scanPath) {
7295
7628
  nodesAdded += result.nodesAdded;
7296
7629
  edgesAdded += result.edgesAdded;
7297
7630
  };
7298
- add(import_types26.EdgeType.RUNS_ON, "vercel", "vercel");
7299
- for (const cron of config.crons ?? []) add(import_types26.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
7300
- for (const varName of Object.keys(config.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7301
- for (const varName of Object.keys(config.build?.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7631
+ add(import_types27.EdgeType.RUNS_ON, "vercel", "vercel");
7632
+ for (const cron of config.crons ?? []) add(import_types27.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
7633
+ for (const varName of Object.keys(config.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7634
+ for (const varName of Object.keys(config.build?.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7302
7635
  for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
7303
- add(import_types26.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7636
+ add(import_types27.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7304
7637
  }
7305
7638
  }
7306
7639
  return { nodesAdded, edgesAdded };
@@ -7309,13 +7642,13 @@ async function addVercelServices(graph, services, scanPath) {
7309
7642
  // src/extract/infra/railway.ts
7310
7643
  init_cjs_shims();
7311
7644
  var import_node_fs22 = require("fs");
7312
- var import_node_path38 = __toESM(require("path"), 1);
7645
+ var import_node_path39 = __toESM(require("path"), 1);
7313
7646
  var import_smol_toml3 = require("smol-toml");
7314
- var import_types27 = require("@neat.is/types");
7647
+ var import_types28 = require("@neat.is/types");
7315
7648
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
7316
7649
  async function readRailwayConfig(dir) {
7317
7650
  for (const filename of RAILWAY_FILENAMES) {
7318
- const abs = import_node_path38.default.join(dir, filename);
7651
+ const abs = import_node_path39.default.join(dir, filename);
7319
7652
  if (!await exists(abs)) continue;
7320
7653
  const raw = await import_node_fs22.promises.readFile(abs, "utf8");
7321
7654
  const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -7331,7 +7664,7 @@ async function addRailwayServices(graph, services, scanPath) {
7331
7664
  try {
7332
7665
  read = await readRailwayConfig(service.dir);
7333
7666
  } catch (err) {
7334
- recordExtractionError("infra railway", import_node_path38.default.relative(scanPath, service.dir), err);
7667
+ recordExtractionError("infra railway", import_node_path39.default.relative(scanPath, service.dir), err);
7335
7668
  continue;
7336
7669
  }
7337
7670
  if (!read) continue;
@@ -7341,7 +7674,7 @@ async function addRailwayServices(graph, services, scanPath) {
7341
7674
  }
7342
7675
  const anchorId = service.node.id;
7343
7676
  const { config, relFile, raw } = read;
7344
- const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
7677
+ const evidenceFile = toPosix2(import_node_path39.default.relative(scanPath, import_node_path39.default.join(service.dir, relFile)));
7345
7678
  const add = (edgeType, kind, name) => {
7346
7679
  if (!name) return;
7347
7680
  const result = emitPlatformResourceEdge(
@@ -7357,9 +7690,9 @@ async function addRailwayServices(graph, services, scanPath) {
7357
7690
  nodesAdded += result.nodesAdded;
7358
7691
  edgesAdded += result.edgesAdded;
7359
7692
  };
7360
- add(import_types27.EdgeType.RUNS_ON, "railway", "railway");
7361
- add(import_types27.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
7362
- add(import_types27.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
7693
+ add(import_types28.EdgeType.RUNS_ON, "railway", "railway");
7694
+ add(import_types28.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
7695
+ add(import_types28.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
7363
7696
  }
7364
7697
  return { nodesAdded, edgesAdded };
7365
7698
  }
@@ -7367,12 +7700,12 @@ async function addRailwayServices(graph, services, scanPath) {
7367
7700
  // src/extract/infra/supabase.ts
7368
7701
  init_cjs_shims();
7369
7702
  var import_node_fs23 = require("fs");
7370
- var import_node_path39 = __toESM(require("path"), 1);
7703
+ var import_node_path40 = __toESM(require("path"), 1);
7371
7704
  var import_smol_toml4 = require("smol-toml");
7372
- var import_types28 = require("@neat.is/types");
7705
+ var import_types29 = require("@neat.is/types");
7373
7706
  async function readSupabaseConfig(dir) {
7374
- const relFile = import_node_path39.default.join("supabase", "config.toml");
7375
- const abs = import_node_path39.default.join(dir, relFile);
7707
+ const relFile = import_node_path40.default.join("supabase", "config.toml");
7708
+ const abs = import_node_path40.default.join(dir, relFile);
7376
7709
  if (!await exists(abs)) return null;
7377
7710
  const raw = await import_node_fs23.promises.readFile(abs, "utf8");
7378
7711
  const config = (0, import_smol_toml4.parse)(raw);
@@ -7386,7 +7719,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
7386
7719
  try {
7387
7720
  read = await readSupabaseConfig(service.dir);
7388
7721
  } catch (err) {
7389
- recordExtractionError("infra supabase", import_node_path39.default.relative(scanPath, service.dir), err);
7722
+ recordExtractionError("infra supabase", import_node_path40.default.relative(scanPath, service.dir), err);
7390
7723
  continue;
7391
7724
  }
7392
7725
  if (!read) continue;
@@ -7401,7 +7734,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
7401
7734
  });
7402
7735
  }
7403
7736
  const anchorId = service.node.id;
7404
- const evidenceFile = toPosix2(import_node_path39.default.relative(scanPath, import_node_path39.default.join(service.dir, relFile)));
7737
+ const evidenceFile = toPosix2(import_node_path40.default.relative(scanPath, import_node_path40.default.join(service.dir, relFile)));
7405
7738
  const add = (edgeType, kind, name) => {
7406
7739
  if (!name) return;
7407
7740
  const result = emitPlatformResourceEdge(
@@ -7417,10 +7750,10 @@ async function addSupabaseProjects(graph, services, scanPath) {
7417
7750
  nodesAdded += result.nodesAdded;
7418
7751
  edgesAdded += result.edgesAdded;
7419
7752
  };
7420
- add(import_types28.EdgeType.RUNS_ON, "supabase", "supabase");
7421
- for (const fn of Object.keys(config.functions ?? {})) add(import_types28.EdgeType.DEPENDS_ON, "supabase-function", fn);
7422
- if (config.storage) add(import_types28.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
7423
- if (config.auth) add(import_types28.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
7753
+ add(import_types29.EdgeType.RUNS_ON, "supabase", "supabase");
7754
+ for (const fn of Object.keys(config.functions ?? {})) add(import_types29.EdgeType.DEPENDS_ON, "supabase-function", fn);
7755
+ if (config.storage) add(import_types29.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
7756
+ if (config.auth) add(import_types29.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
7424
7757
  }
7425
7758
  return { nodesAdded, edgesAdded };
7426
7759
  }
@@ -7442,17 +7775,17 @@ async function addInfra(graph, scanPath, services) {
7442
7775
  }
7443
7776
 
7444
7777
  // src/extract/index.ts
7445
- var import_node_path41 = __toESM(require("path"), 1);
7778
+ var import_node_path42 = __toESM(require("path"), 1);
7446
7779
 
7447
7780
  // src/extract/retire.ts
7448
7781
  init_cjs_shims();
7449
7782
  var import_node_fs24 = require("fs");
7450
- var import_node_path40 = __toESM(require("path"), 1);
7451
- var import_types29 = require("@neat.is/types");
7783
+ var import_node_path41 = __toESM(require("path"), 1);
7784
+ var import_types30 = require("@neat.is/types");
7452
7785
  function dropOrphanedFileNodes(graph) {
7453
7786
  const orphans = [];
7454
7787
  graph.forEachNode((id, attrs) => {
7455
- if (attrs.type !== import_types29.NodeType.FileNode) return;
7788
+ if (attrs.type !== import_types30.NodeType.FileNode) return;
7456
7789
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
7457
7790
  orphans.push(id);
7458
7791
  }
@@ -7465,7 +7798,7 @@ function retireEdgesByFile(graph, file) {
7465
7798
  const toDrop = [];
7466
7799
  graph.forEachEdge((id, attrs) => {
7467
7800
  const edge = attrs;
7468
- if (edge.provenance !== import_types29.Provenance.EXTRACTED) return;
7801
+ if (edge.provenance !== import_types30.Provenance.EXTRACTED) return;
7469
7802
  if (!edge.evidence?.file) return;
7470
7803
  if (edge.evidence.file === normalized) toDrop.push(id);
7471
7804
  });
@@ -7478,14 +7811,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
7478
7811
  const bases = [scanPath, ...serviceDirs];
7479
7812
  graph.forEachEdge((id, attrs) => {
7480
7813
  const edge = attrs;
7481
- if (edge.provenance !== import_types29.Provenance.EXTRACTED) return;
7814
+ if (edge.provenance !== import_types30.Provenance.EXTRACTED) return;
7482
7815
  const evidenceFile = edge.evidence?.file;
7483
7816
  if (!evidenceFile) return;
7484
- if (import_node_path40.default.isAbsolute(evidenceFile)) {
7817
+ if (import_node_path41.default.isAbsolute(evidenceFile)) {
7485
7818
  if (!(0, import_node_fs24.existsSync)(evidenceFile)) toDrop.push(id);
7486
7819
  return;
7487
7820
  }
7488
- const found = bases.some((base) => (0, import_node_fs24.existsSync)(import_node_path40.default.join(base, evidenceFile)));
7821
+ const found = bases.some((base) => (0, import_node_fs24.existsSync)(import_node_path41.default.join(base, evidenceFile)));
7489
7822
  if (!found) toDrop.push(id);
7490
7823
  });
7491
7824
  for (const id of toDrop) graph.dropEdge(id);
@@ -7527,7 +7860,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7527
7860
  }
7528
7861
  const droppedEntries = drainDroppedExtracted();
7529
7862
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
7530
- const rejectedPath = import_node_path41.default.join(import_node_path41.default.dirname(opts.errorsPath), "rejected.ndjson");
7863
+ const rejectedPath = import_node_path42.default.join(import_node_path42.default.dirname(opts.errorsPath), "rejected.ndjson");
7531
7864
  try {
7532
7865
  await writeRejectedExtracted(droppedEntries, rejectedPath);
7533
7866
  } catch (err) {
@@ -7561,39 +7894,39 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7561
7894
 
7562
7895
  // src/divergences.ts
7563
7896
  init_cjs_shims();
7564
- var import_types30 = require("@neat.is/types");
7897
+ var import_types31 = require("@neat.is/types");
7565
7898
  function bucketKey(source, target, type) {
7566
7899
  return `${type}|${source}|${target}`;
7567
7900
  }
7568
7901
  function bucketSourceFor(graph, edge) {
7569
- if (edge.type !== import_types30.EdgeType.CONNECTS_TO) return edge.source;
7570
- const parsed = (0, import_types30.parseFileId)(edge.source);
7902
+ if (edge.type !== import_types31.EdgeType.CONNECTS_TO) return edge.source;
7903
+ const parsed = (0, import_types31.parseFileId)(edge.source);
7571
7904
  if (!parsed || !graph.hasNode(edge.target)) return edge.source;
7572
7905
  const target = graph.getNodeAttributes(edge.target);
7573
- if (target.type !== import_types30.NodeType.DatabaseNode) return edge.source;
7574
- return (0, import_types30.serviceId)(parsed.service);
7906
+ if (target.type !== import_types31.NodeType.DatabaseNode) return edge.source;
7907
+ return (0, import_types31.serviceId)(parsed.service);
7575
7908
  }
7576
7909
  function bucketEdges(graph) {
7577
7910
  const buckets2 = /* @__PURE__ */ new Map();
7578
7911
  graph.forEachEdge((id, attrs) => {
7579
7912
  const e = attrs;
7580
- const parsed = (0, import_types30.parseEdgeId)(id);
7913
+ const parsed = (0, import_types31.parseEdgeId)(id);
7581
7914
  const provenance = parsed?.provenance ?? e.provenance;
7582
7915
  const source = bucketSourceFor(graph, e);
7583
7916
  const key = bucketKey(source, e.target, e.type);
7584
7917
  const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
7585
7918
  switch (provenance) {
7586
- case import_types30.Provenance.EXTRACTED:
7919
+ case import_types31.Provenance.EXTRACTED:
7587
7920
  cur.extracted = e;
7588
7921
  break;
7589
- case import_types30.Provenance.OBSERVED:
7922
+ case import_types31.Provenance.OBSERVED:
7590
7923
  cur.observed = e;
7591
7924
  break;
7592
- case import_types30.Provenance.INFERRED:
7925
+ case import_types31.Provenance.INFERRED:
7593
7926
  cur.inferred = e;
7594
7927
  break;
7595
7928
  default:
7596
- if (e.provenance === import_types30.Provenance.STALE) cur.stale = e;
7929
+ if (e.provenance === import_types31.Provenance.STALE) cur.stale = e;
7597
7930
  }
7598
7931
  buckets2.set(key, cur);
7599
7932
  });
@@ -7602,12 +7935,12 @@ function bucketEdges(graph) {
7602
7935
  function nodeIsFrontier(graph, nodeId) {
7603
7936
  if (!graph.hasNode(nodeId)) return false;
7604
7937
  const attrs = graph.getNodeAttributes(nodeId);
7605
- return attrs.type === import_types30.NodeType.FrontierNode;
7938
+ return attrs.type === import_types31.NodeType.FrontierNode;
7606
7939
  }
7607
7940
  function nodeIsWebsocketChannel(graph, nodeId) {
7608
7941
  if (!graph.hasNode(nodeId)) return false;
7609
7942
  const attrs = graph.getNodeAttributes(nodeId);
7610
- return attrs.type === import_types30.NodeType.WebSocketChannelNode;
7943
+ return attrs.type === import_types31.NodeType.WebSocketChannelNode;
7611
7944
  }
7612
7945
  function clampConfidence(n) {
7613
7946
  if (!Number.isFinite(n)) return 0;
@@ -7627,14 +7960,14 @@ function gradedConfidence(edge) {
7627
7960
  return clampConfidence(confidenceForEdge(edge));
7628
7961
  }
7629
7962
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
7630
- import_types30.EdgeType.CALLS,
7631
- import_types30.EdgeType.CONNECTS_TO,
7632
- import_types30.EdgeType.PUBLISHES_TO,
7633
- import_types30.EdgeType.CONSUMES_FROM
7963
+ import_types31.EdgeType.CALLS,
7964
+ import_types31.EdgeType.CONNECTS_TO,
7965
+ import_types31.EdgeType.PUBLISHES_TO,
7966
+ import_types31.EdgeType.CONSUMES_FROM
7634
7967
  ]);
7635
7968
  function detectMissingDivergences(graph, bucket) {
7636
7969
  const out = [];
7637
- if (bucket.type === import_types30.EdgeType.CONTAINS) return out;
7970
+ if (bucket.type === import_types31.EdgeType.CONTAINS) return out;
7638
7971
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
7639
7972
  if (!nodeIsFrontier(graph, bucket.target)) {
7640
7973
  out.push({
@@ -7675,7 +8008,7 @@ function declaredHostFor(svc) {
7675
8008
  function hasExtractedConfiguredBy(graph, svcId) {
7676
8009
  for (const edgeId of graph.outboundEdges(svcId)) {
7677
8010
  const e = graph.getEdgeAttributes(edgeId);
7678
- if (e.type === import_types30.EdgeType.CONFIGURED_BY && e.provenance === import_types30.Provenance.EXTRACTED) {
8011
+ if (e.type === import_types31.EdgeType.CONFIGURED_BY && e.provenance === import_types31.Provenance.EXTRACTED) {
7679
8012
  return true;
7680
8013
  }
7681
8014
  }
@@ -7688,10 +8021,10 @@ function detectHostMismatch(graph, svcId, svc) {
7688
8021
  const out = [];
7689
8022
  for (const edgeId of graph.outboundEdges(svcId)) {
7690
8023
  const edge = graph.getEdgeAttributes(edgeId);
7691
- if (edge.type !== import_types30.EdgeType.CONNECTS_TO) continue;
7692
- if (edge.provenance !== import_types30.Provenance.OBSERVED) continue;
8024
+ if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
8025
+ if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
7693
8026
  const target = graph.getNodeAttributes(edge.target);
7694
- if (target.type !== import_types30.NodeType.DatabaseNode) continue;
8027
+ if (target.type !== import_types31.NodeType.DatabaseNode) continue;
7695
8028
  const observedHost = target.host?.trim();
7696
8029
  if (!observedHost) continue;
7697
8030
  if (observedHost === declaredHost) continue;
@@ -7713,10 +8046,10 @@ function detectCompatDivergences(graph, svcId, svc) {
7713
8046
  const deps = svc.dependencies ?? {};
7714
8047
  for (const edgeId of graph.outboundEdges(svcId)) {
7715
8048
  const edge = graph.getEdgeAttributes(edgeId);
7716
- if (edge.type !== import_types30.EdgeType.CONNECTS_TO) continue;
7717
- if (edge.provenance !== import_types30.Provenance.OBSERVED) continue;
8049
+ if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
8050
+ if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
7718
8051
  const target = graph.getNodeAttributes(edge.target);
7719
- if (target.type !== import_types30.NodeType.DatabaseNode) continue;
8052
+ if (target.type !== import_types31.NodeType.DatabaseNode) continue;
7720
8053
  for (const pair of compatPairs()) {
7721
8054
  if (pair.engine !== target.engine) continue;
7722
8055
  const declared = deps[pair.driver];
@@ -7775,7 +8108,7 @@ function suppressHostMismatchHalves(all) {
7775
8108
  for (const d of all) {
7776
8109
  if (d.type !== "host-mismatch") continue;
7777
8110
  observedHalf.add(`${d.source}->${d.target}`);
7778
- declaredHalf.add((0, import_types30.databaseId)(d.extractedHost));
8111
+ declaredHalf.add((0, import_types31.databaseId)(d.extractedHost));
7779
8112
  }
7780
8113
  if (observedHalf.size === 0) return all;
7781
8114
  return all.filter((d) => {
@@ -7794,7 +8127,7 @@ function computeDivergences(graph, opts = {}) {
7794
8127
  }
7795
8128
  graph.forEachNode((nodeId, attrs) => {
7796
8129
  const n = attrs;
7797
- if (n.type !== import_types30.NodeType.ServiceNode) return;
8130
+ if (n.type !== import_types31.NodeType.ServiceNode) return;
7798
8131
  const svc = n;
7799
8132
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
7800
8133
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -7828,7 +8161,7 @@ function computeDivergences(graph, opts = {}) {
7828
8161
  if (a.source !== b.source) return a.source.localeCompare(b.source);
7829
8162
  return a.target.localeCompare(b.target);
7830
8163
  });
7831
- return import_types30.DivergenceResultSchema.parse({
8164
+ return import_types31.DivergenceResultSchema.parse({
7832
8165
  divergences: filtered,
7833
8166
  totalAffected: filtered.length,
7834
8167
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -7838,8 +8171,8 @@ function computeDivergences(graph, opts = {}) {
7838
8171
  // src/persist.ts
7839
8172
  init_cjs_shims();
7840
8173
  var import_node_fs25 = require("fs");
7841
- var import_node_path42 = __toESM(require("path"), 1);
7842
- var import_types31 = require("@neat.is/types");
8174
+ var import_node_path43 = __toESM(require("path"), 1);
8175
+ var import_types32 = require("@neat.is/types");
7843
8176
  var SCHEMA_VERSION = 4;
7844
8177
  function migrateV1ToV2(payload) {
7845
8178
  const nodes = payload.graph.nodes;
@@ -7861,12 +8194,12 @@ function migrateV2ToV3(payload) {
7861
8194
  for (const edge of edges) {
7862
8195
  const attrs = edge.attributes;
7863
8196
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
7864
- attrs.provenance = import_types31.Provenance.OBSERVED;
8197
+ attrs.provenance = import_types32.Provenance.OBSERVED;
7865
8198
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
7866
8199
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
7867
8200
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
7868
8201
  if (type && source && target) {
7869
- const newId = (0, import_types31.observedEdgeId)(source, target, type);
8202
+ const newId = (0, import_types32.observedEdgeId)(source, target, type);
7870
8203
  attrs.id = newId;
7871
8204
  if (edge.key) edge.key = newId;
7872
8205
  }
@@ -7875,7 +8208,7 @@ function migrateV2ToV3(payload) {
7875
8208
  return { ...payload, schemaVersion: 3 };
7876
8209
  }
7877
8210
  async function ensureDir(filePath) {
7878
- await import_node_fs25.promises.mkdir(import_node_path42.default.dirname(filePath), { recursive: true });
8211
+ await import_node_fs25.promises.mkdir(import_node_path43.default.dirname(filePath), { recursive: true });
7879
8212
  }
7880
8213
  async function saveGraphToDisk(graph, outPath) {
7881
8214
  await ensureDir(outPath);
@@ -7957,7 +8290,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
7957
8290
  // src/gitignore.ts
7958
8291
  init_cjs_shims();
7959
8292
  var import_node_fs26 = require("fs");
7960
- var import_node_path43 = __toESM(require("path"), 1);
8293
+ var import_node_path44 = __toESM(require("path"), 1);
7961
8294
  var NEAT_OUT_LINE = "neat-out/";
7962
8295
  var NEAT_HEADER = "# NEAT \u2014 machine-local snapshots and events";
7963
8296
  function isNeatOutLine(line) {
@@ -7965,7 +8298,7 @@ function isNeatOutLine(line) {
7965
8298
  return trimmed === "neat-out/" || trimmed === "neat-out";
7966
8299
  }
7967
8300
  async function ensureNeatOutIgnored(projectDir) {
7968
- const file = import_node_path43.default.join(projectDir, ".gitignore");
8301
+ const file = import_node_path44.default.join(projectDir, ".gitignore");
7969
8302
  let existing = null;
7970
8303
  try {
7971
8304
  existing = await import_node_fs26.promises.readFile(file, "utf8");
@@ -7992,7 +8325,7 @@ ${NEAT_OUT_LINE}
7992
8325
 
7993
8326
  // src/summary.ts
7994
8327
  init_cjs_shims();
7995
- var import_types32 = require("@neat.is/types");
8328
+ var import_types33 = require("@neat.is/types");
7996
8329
  function renderOtelEnvBlock() {
7997
8330
  return [
7998
8331
  "for prod OTel routing, set these in your deploy platform's env:",
@@ -8002,19 +8335,19 @@ function renderOtelEnvBlock() {
8002
8335
  }
8003
8336
  function findIncompatServices(nodes) {
8004
8337
  return nodes.filter(
8005
- (n) => n.type === import_types32.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
8338
+ (n) => n.type === import_types33.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
8006
8339
  );
8007
8340
  }
8008
8341
  function servicesWithoutObserved(nodes, edges) {
8009
8342
  const seen = /* @__PURE__ */ new Set();
8010
8343
  for (const e of edges) {
8011
- if (e.provenance === import_types32.Provenance.OBSERVED) {
8344
+ if (e.provenance === import_types33.Provenance.OBSERVED) {
8012
8345
  seen.add(e.source);
8013
8346
  seen.add(e.target);
8014
8347
  }
8015
8348
  }
8016
8349
  return nodes.filter(
8017
- (n) => n.type === import_types32.NodeType.ServiceNode && !seen.has(n.id)
8350
+ (n) => n.type === import_types33.NodeType.ServiceNode && !seen.has(n.id)
8018
8351
  );
8019
8352
  }
8020
8353
  function formatDivergence(d) {
@@ -8089,26 +8422,26 @@ function formatIncompat(inc) {
8089
8422
  // src/watch.ts
8090
8423
  init_cjs_shims();
8091
8424
  var import_node_fs35 = __toESM(require("fs"), 1);
8092
- var import_node_path54 = __toESM(require("path"), 1);
8425
+ var import_node_path55 = __toESM(require("path"), 1);
8093
8426
  var import_chokidar = __toESM(require("chokidar"), 1);
8094
8427
 
8095
8428
  // src/api.ts
8096
8429
  init_cjs_shims();
8097
8430
  var import_fastify = __toESM(require("fastify"), 1);
8098
8431
  var import_cors = __toESM(require("@fastify/cors"), 1);
8099
- var import_types34 = require("@neat.is/types");
8432
+ var import_types35 = require("@neat.is/types");
8100
8433
 
8101
8434
  // src/extend/index.ts
8102
8435
  init_cjs_shims();
8103
8436
  var import_node_fs28 = require("fs");
8104
- var import_node_path45 = __toESM(require("path"), 1);
8437
+ var import_node_path46 = __toESM(require("path"), 1);
8105
8438
  var import_node_os2 = __toESM(require("os"), 1);
8106
8439
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
8107
8440
 
8108
8441
  // src/installers/package-manager.ts
8109
8442
  init_cjs_shims();
8110
8443
  var import_node_fs27 = require("fs");
8111
- var import_node_path44 = __toESM(require("path"), 1);
8444
+ var import_node_path45 = __toESM(require("path"), 1);
8112
8445
  var import_node_child_process = require("child_process");
8113
8446
  var LOCKFILE_PRIORITY = [
8114
8447
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -8130,22 +8463,22 @@ async function exists2(p) {
8130
8463
  }
8131
8464
  }
8132
8465
  async function detectPackageManager(serviceDir) {
8133
- let dir = import_node_path44.default.resolve(serviceDir);
8466
+ let dir = import_node_path45.default.resolve(serviceDir);
8134
8467
  const stops = /* @__PURE__ */ new Set();
8135
8468
  for (let i = 0; i < 64; i++) {
8136
8469
  if (stops.has(dir)) break;
8137
8470
  stops.add(dir);
8138
8471
  for (const candidate of LOCKFILE_PRIORITY) {
8139
- const lockPath = import_node_path44.default.join(dir, candidate.lockfile);
8472
+ const lockPath = import_node_path45.default.join(dir, candidate.lockfile);
8140
8473
  if (await exists2(lockPath)) {
8141
8474
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
8142
8475
  }
8143
8476
  }
8144
- const parent = import_node_path44.default.dirname(dir);
8477
+ const parent = import_node_path45.default.dirname(dir);
8145
8478
  if (parent === dir) break;
8146
8479
  dir = parent;
8147
8480
  }
8148
- return { pm: "npm", cwd: import_node_path44.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
8481
+ return { pm: "npm", cwd: import_node_path45.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
8149
8482
  }
8150
8483
  async function runPackageManagerInstall(cmd) {
8151
8484
  return new Promise((resolve) => {
@@ -8194,22 +8527,53 @@ async function fileExists2(p) {
8194
8527
  }
8195
8528
  }
8196
8529
  async function readPackageJson(scanPath) {
8197
- const pkgPath = import_node_path45.default.join(scanPath, "package.json");
8530
+ const pkgPath = import_node_path46.default.join(scanPath, "package.json");
8198
8531
  const raw = await import_node_fs28.promises.readFile(pkgPath, "utf8");
8199
8532
  return JSON.parse(raw);
8200
8533
  }
8534
+ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
8535
+ "node_modules",
8536
+ "dist",
8537
+ "build",
8538
+ "out",
8539
+ "coverage",
8540
+ "neat-out"
8541
+ ]);
8201
8542
  async function findHookFiles(scanPath) {
8202
- const entries = await import_node_fs28.promises.readdir(scanPath);
8203
- return entries.filter(
8204
- (e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
8205
- ).sort();
8543
+ const found = [];
8544
+ const walk3 = async (dir) => {
8545
+ const entries = await import_node_fs28.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
8546
+ for (const entry2 of entries) {
8547
+ if (entry2.isDirectory()) {
8548
+ if (entry2.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry2.name)) continue;
8549
+ await walk3(import_node_path46.default.join(dir, entry2.name));
8550
+ } else if (entry2.isFile()) {
8551
+ if ((entry2.name.startsWith("instrumentation") || entry2.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry2.name)) {
8552
+ const rel = import_node_path46.default.relative(scanPath, import_node_path46.default.join(dir, entry2.name));
8553
+ found.push(rel.split(import_node_path46.default.sep).join("/"));
8554
+ }
8555
+ }
8556
+ }
8557
+ };
8558
+ await walk3(scanPath);
8559
+ return found.sort();
8560
+ }
8561
+ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
8562
+ let fallback = null;
8563
+ for (const file of hookFiles) {
8564
+ const content = await import_node_fs28.promises.readFile(import_node_path46.default.join(scanPath, file), "utf8");
8565
+ const patched = splicedContent(content, snippet2);
8566
+ if (patched !== null) return { file, content, patched };
8567
+ if (fallback === null) fallback = { file, content };
8568
+ }
8569
+ return { file: fallback.file, content: fallback.content, patched: null };
8206
8570
  }
8207
8571
  function extendLogPath() {
8208
- return process.env.NEAT_EXTEND_LOG ?? import_node_path45.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
8572
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path46.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
8209
8573
  }
8210
8574
  async function appendExtendLog(entry2) {
8211
8575
  const logPath = extendLogPath();
8212
- await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(logPath), { recursive: true });
8576
+ await import_node_fs28.promises.mkdir(import_node_path46.default.dirname(logPath), { recursive: true });
8213
8577
  await import_node_fs28.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
8214
8578
  }
8215
8579
  function splicedContent(fileContent, snippet2) {
@@ -8268,7 +8632,7 @@ function lookupInstrumentation(library, installedVersion) {
8268
8632
  }
8269
8633
  async function describeProjectInstrumentation(ctx) {
8270
8634
  const hookFiles = await findHookFiles(ctx.scanPath);
8271
- const envNeat = await fileExists2(import_node_path45.default.join(ctx.scanPath, ".env.neat"));
8635
+ const envNeat = await fileExists2(import_node_path46.default.join(ctx.scanPath, ".env.neat"));
8272
8636
  const registryInstrPackages = new Set(
8273
8637
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
8274
8638
  );
@@ -8290,16 +8654,22 @@ async function applyExtension(ctx, args, options) {
8290
8654
  );
8291
8655
  }
8292
8656
  for (const file of hookFiles) {
8293
- const content = await import_node_fs28.promises.readFile(import_node_path45.default.join(ctx.scanPath, file), "utf8");
8657
+ const content = await import_node_fs28.promises.readFile(import_node_path46.default.join(ctx.scanPath, file), "utf8");
8294
8658
  if (content.includes(args.registration_snippet)) {
8295
8659
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
8296
8660
  }
8297
8661
  }
8298
- const primaryFile = hookFiles[0];
8299
- const primaryPath = import_node_path45.default.join(ctx.scanPath, primaryFile);
8662
+ const primary = await pickPrimaryHookFile(ctx.scanPath, hookFiles, args.registration_snippet);
8663
+ if (primary.patched === null) {
8664
+ throw new Error(
8665
+ `Could not find instrumentation insertion point in ${hookFiles.join(", ")}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
8666
+ );
8667
+ }
8668
+ const primaryFile = primary.file;
8669
+ const primaryPath = import_node_path46.default.join(ctx.scanPath, primaryFile);
8300
8670
  const filesTouched = [];
8301
8671
  const depsAdded = [];
8302
- const pkgPath = import_node_path45.default.join(ctx.scanPath, "package.json");
8672
+ const pkgPath = import_node_path46.default.join(ctx.scanPath, "package.json");
8303
8673
  const pkg = await readPackageJson(ctx.scanPath);
8304
8674
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
8305
8675
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
@@ -8307,14 +8677,7 @@ async function applyExtension(ctx, args, options) {
8307
8677
  filesTouched.push("package.json");
8308
8678
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
8309
8679
  }
8310
- const hookContent = await import_node_fs28.promises.readFile(primaryPath, "utf8");
8311
- const patched = splicedContent(hookContent, args.registration_snippet);
8312
- if (!patched) {
8313
- throw new Error(
8314
- `Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
8315
- );
8316
- }
8317
- await import_node_fs28.promises.writeFile(primaryPath, patched, "utf8");
8680
+ await import_node_fs28.promises.writeFile(primaryPath, primary.patched, "utf8");
8318
8681
  filesTouched.push(primaryFile);
8319
8682
  const cmd = await detectPackageManager(ctx.scanPath);
8320
8683
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -8345,7 +8708,7 @@ async function dryRunExtension(ctx, args) {
8345
8708
  };
8346
8709
  }
8347
8710
  for (const file of hookFiles) {
8348
- const content = await import_node_fs28.promises.readFile(import_node_path45.default.join(ctx.scanPath, file), "utf8");
8711
+ const content = await import_node_fs28.promises.readFile(import_node_path46.default.join(ctx.scanPath, file), "utf8");
8349
8712
  if (content.includes(args.registration_snippet)) {
8350
8713
  return {
8351
8714
  library: args.library,
@@ -8356,7 +8719,7 @@ async function dryRunExtension(ctx, args) {
8356
8719
  };
8357
8720
  }
8358
8721
  }
8359
- const primaryFile = hookFiles[0];
8722
+ const primary = await pickPrimaryHookFile(ctx.scanPath, hookFiles, args.registration_snippet);
8360
8723
  const filesTouched = [];
8361
8724
  const depsToAdd = [];
8362
8725
  let packageJsonPatch = {};
@@ -8367,10 +8730,8 @@ async function dryRunExtension(ctx, args) {
8367
8730
  depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
8368
8731
  filesTouched.push("package.json");
8369
8732
  }
8370
- const hookContent = await import_node_fs28.promises.readFile(import_node_path45.default.join(ctx.scanPath, primaryFile), "utf8");
8371
- const patched = splicedContent(hookContent, args.registration_snippet);
8372
- if (patched) {
8373
- filesTouched.push(primaryFile);
8733
+ if (primary.patched !== null) {
8734
+ filesTouched.push(primary.file);
8374
8735
  templatePatch = `+ ${args.registration_snippet}`;
8375
8736
  } else {
8376
8737
  templatePatch = "Could not find insertion point in hook file.";
@@ -8388,7 +8749,7 @@ async function rollbackExtension(ctx, args) {
8388
8749
  if (!match) {
8389
8750
  return { undone: false, message: "no apply found for library" };
8390
8751
  }
8391
- const pkgPath = import_node_path45.default.join(ctx.scanPath, "package.json");
8752
+ const pkgPath = import_node_path46.default.join(ctx.scanPath, "package.json");
8392
8753
  if (await fileExists2(pkgPath)) {
8393
8754
  const pkg = await readPackageJson(ctx.scanPath);
8394
8755
  if (pkg.dependencies?.[match.instrumentation_package]) {
@@ -8399,7 +8760,7 @@ async function rollbackExtension(ctx, args) {
8399
8760
  }
8400
8761
  const hookFiles = await findHookFiles(ctx.scanPath);
8401
8762
  for (const file of hookFiles) {
8402
- const filePath = import_node_path45.default.join(ctx.scanPath, file);
8763
+ const filePath = import_node_path46.default.join(ctx.scanPath, file);
8403
8764
  const content = await import_node_fs28.promises.readFile(filePath, "utf8");
8404
8765
  if (content.includes(match.registration_snippet)) {
8405
8766
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
@@ -8537,23 +8898,23 @@ function canonicalJson(value) {
8537
8898
 
8538
8899
  // src/projects.ts
8539
8900
  init_cjs_shims();
8540
- var import_node_path46 = __toESM(require("path"), 1);
8901
+ var import_node_path47 = __toESM(require("path"), 1);
8541
8902
  function pathsForProject(project, baseDir) {
8542
8903
  if (project === DEFAULT_PROJECT) {
8543
8904
  return {
8544
- snapshotPath: import_node_path46.default.join(baseDir, "graph.json"),
8545
- errorsPath: import_node_path46.default.join(baseDir, "errors.ndjson"),
8546
- staleEventsPath: import_node_path46.default.join(baseDir, "stale-events.ndjson"),
8547
- embeddingsCachePath: import_node_path46.default.join(baseDir, "embeddings.json"),
8548
- policyViolationsPath: import_node_path46.default.join(baseDir, "policy-violations.ndjson")
8905
+ snapshotPath: import_node_path47.default.join(baseDir, "graph.json"),
8906
+ errorsPath: import_node_path47.default.join(baseDir, "errors.ndjson"),
8907
+ staleEventsPath: import_node_path47.default.join(baseDir, "stale-events.ndjson"),
8908
+ embeddingsCachePath: import_node_path47.default.join(baseDir, "embeddings.json"),
8909
+ policyViolationsPath: import_node_path47.default.join(baseDir, "policy-violations.ndjson")
8549
8910
  };
8550
8911
  }
8551
8912
  return {
8552
- snapshotPath: import_node_path46.default.join(baseDir, `${project}.json`),
8553
- errorsPath: import_node_path46.default.join(baseDir, `errors.${project}.ndjson`),
8554
- staleEventsPath: import_node_path46.default.join(baseDir, `stale-events.${project}.ndjson`),
8555
- embeddingsCachePath: import_node_path46.default.join(baseDir, `embeddings.${project}.json`),
8556
- policyViolationsPath: import_node_path46.default.join(baseDir, `policy-violations.${project}.ndjson`)
8913
+ snapshotPath: import_node_path47.default.join(baseDir, `${project}.json`),
8914
+ errorsPath: import_node_path47.default.join(baseDir, `errors.${project}.ndjson`),
8915
+ staleEventsPath: import_node_path47.default.join(baseDir, `stale-events.${project}.ndjson`),
8916
+ embeddingsCachePath: import_node_path47.default.join(baseDir, `embeddings.${project}.json`),
8917
+ policyViolationsPath: import_node_path47.default.join(baseDir, `policy-violations.${project}.ndjson`)
8557
8918
  };
8558
8919
  }
8559
8920
  var Projects = class {
@@ -8591,26 +8952,26 @@ var Projects = class {
8591
8952
  init_cjs_shims();
8592
8953
  var import_node_fs30 = require("fs");
8593
8954
  var import_node_os3 = __toESM(require("os"), 1);
8594
- var import_node_path47 = __toESM(require("path"), 1);
8595
- var import_types33 = require("@neat.is/types");
8955
+ var import_node_path48 = __toESM(require("path"), 1);
8956
+ var import_types34 = require("@neat.is/types");
8596
8957
  var LOCK_TIMEOUT_MS = 5e3;
8597
8958
  var LOCK_RETRY_MS = 50;
8598
8959
  function neatHome() {
8599
8960
  const override = process.env.NEAT_HOME;
8600
- if (override && override.length > 0) return import_node_path47.default.resolve(override);
8601
- return import_node_path47.default.join(import_node_os3.default.homedir(), ".neat");
8961
+ if (override && override.length > 0) return import_node_path48.default.resolve(override);
8962
+ return import_node_path48.default.join(import_node_os3.default.homedir(), ".neat");
8602
8963
  }
8603
8964
  function registryPath() {
8604
- return import_node_path47.default.join(neatHome(), "projects.json");
8965
+ return import_node_path48.default.join(neatHome(), "projects.json");
8605
8966
  }
8606
8967
  function registryLockPath() {
8607
- return import_node_path47.default.join(neatHome(), "projects.json.lock");
8968
+ return import_node_path48.default.join(neatHome(), "projects.json.lock");
8608
8969
  }
8609
8970
  function daemonPidPath() {
8610
- return import_node_path47.default.join(neatHome(), "neatd.pid");
8971
+ return import_node_path48.default.join(neatHome(), "neatd.pid");
8611
8972
  }
8612
8973
  function daemonsDir() {
8613
- return import_node_path47.default.join(neatHome(), "daemons");
8974
+ return import_node_path48.default.join(neatHome(), "daemons");
8614
8975
  }
8615
8976
  function isFiniteInt(v) {
8616
8977
  return typeof v === "number" && Number.isFinite(v);
@@ -8651,7 +9012,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
8651
9012
  const out = [];
8652
9013
  for (const name of names) {
8653
9014
  if (!name.endsWith(".json")) continue;
8654
- const file = import_node_path47.default.join(dir, name);
9015
+ const file = import_node_path48.default.join(dir, name);
8655
9016
  let raw;
8656
9017
  try {
8657
9018
  raw = await import_node_fs30.promises.readFile(file, "utf8");
@@ -8772,7 +9133,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
8772
9133
  }
8773
9134
  }
8774
9135
  async function normalizeProjectPath(input) {
8775
- const resolved = import_node_path47.default.resolve(input);
9136
+ const resolved = import_node_path48.default.resolve(input);
8776
9137
  try {
8777
9138
  return await import_node_fs30.promises.realpath(resolved);
8778
9139
  } catch {
@@ -8780,7 +9141,7 @@ async function normalizeProjectPath(input) {
8780
9141
  }
8781
9142
  }
8782
9143
  async function writeAtomically(target, contents) {
8783
- await import_node_fs30.promises.mkdir(import_node_path47.default.dirname(target), { recursive: true });
9144
+ await import_node_fs30.promises.mkdir(import_node_path48.default.dirname(target), { recursive: true });
8784
9145
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
8785
9146
  const fd = await import_node_fs30.promises.open(tmp, "w");
8786
9147
  try {
@@ -8793,7 +9154,7 @@ async function writeAtomically(target, contents) {
8793
9154
  }
8794
9155
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
8795
9156
  const deadline = Date.now() + timeoutMs;
8796
- await import_node_fs30.promises.mkdir(import_node_path47.default.dirname(lockPath), { recursive: true });
9157
+ await import_node_fs30.promises.mkdir(import_node_path48.default.dirname(lockPath), { recursive: true });
8797
9158
  let probedHolder = false;
8798
9159
  while (true) {
8799
9160
  try {
@@ -8846,10 +9207,10 @@ async function readRegistry() {
8846
9207
  throw err;
8847
9208
  }
8848
9209
  const parsed = JSON.parse(raw);
8849
- return import_types33.RegistryFileSchema.parse(parsed);
9210
+ return import_types34.RegistryFileSchema.parse(parsed);
8850
9211
  }
8851
9212
  async function writeRegistry(reg) {
8852
- const validated = import_types33.RegistryFileSchema.parse(reg);
9213
+ const validated = import_types34.RegistryFileSchema.parse(reg);
8853
9214
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
8854
9215
  }
8855
9216
  var ProjectNameCollisionError = class extends Error {
@@ -9035,7 +9396,7 @@ init_auth();
9035
9396
  // src/connectors-config.ts
9036
9397
  init_cjs_shims();
9037
9398
  var import_node_os4 = __toESM(require("os"), 1);
9038
- var import_node_path48 = __toESM(require("path"), 1);
9399
+ var import_node_path49 = __toESM(require("path"), 1);
9039
9400
  var import_node_fs31 = require("fs");
9040
9401
  var CONNECTORS_CONFIG_VERSION = 1;
9041
9402
  var EnvRefUnsetError = class extends Error {
@@ -9050,11 +9411,11 @@ var EnvRefUnsetError = class extends Error {
9050
9411
  };
9051
9412
  function neatHome2() {
9052
9413
  const override = process.env.NEAT_HOME;
9053
- if (override && override.length > 0) return import_node_path48.default.resolve(override);
9054
- return import_node_path48.default.join(import_node_os4.default.homedir(), ".neat");
9414
+ if (override && override.length > 0) return import_node_path49.default.resolve(override);
9415
+ return import_node_path49.default.join(import_node_os4.default.homedir(), ".neat");
9055
9416
  }
9056
9417
  function connectorsConfigPath(home = neatHome2()) {
9057
- return import_node_path48.default.join(home, "connectors.json");
9418
+ return import_node_path49.default.join(home, "connectors.json");
9058
9419
  }
9059
9420
  var MODE_MASK_LOOSER_THAN_0600 = 63;
9060
9421
  async function warnIfModeLooserThan0600(file) {
@@ -9185,7 +9546,7 @@ function connectorMatchesProject(entry2, project) {
9185
9546
  var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
9186
9547
  var CONNECTORS_LOCK_RETRY_MS = 50;
9187
9548
  function connectorsConfigLockPath(home = neatHome2()) {
9188
- return import_node_path48.default.join(home, "connectors.json.lock");
9549
+ return import_node_path49.default.join(home, "connectors.json.lock");
9189
9550
  }
9190
9551
  function isEnvRef(value) {
9191
9552
  return value.length > 1 && value.startsWith("$");
@@ -9198,7 +9559,7 @@ function redactCredentialRef(ref) {
9198
9559
  return out;
9199
9560
  }
9200
9561
  async function writeConfigAtomically0600(file, contents) {
9201
- await import_node_fs31.promises.mkdir(import_node_path48.default.dirname(file), { recursive: true });
9562
+ await import_node_fs31.promises.mkdir(import_node_path49.default.dirname(file), { recursive: true });
9202
9563
  const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
9203
9564
  const fd = await import_node_fs31.promises.open(tmp, "w", 384);
9204
9565
  try {
@@ -9212,7 +9573,7 @@ async function writeConfigAtomically0600(file, contents) {
9212
9573
  }
9213
9574
  async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
9214
9575
  const deadline = Date.now() + timeoutMs;
9215
- await import_node_fs31.promises.mkdir(import_node_path48.default.dirname(lockPath), { recursive: true });
9576
+ await import_node_fs31.promises.mkdir(import_node_path49.default.dirname(lockPath), { recursive: true });
9216
9577
  for (; ; ) {
9217
9578
  try {
9218
9579
  const fd = await import_node_fs31.promises.open(lockPath, "wx");
@@ -9495,11 +9856,11 @@ function registerRoutes(scope, ctx) {
9495
9856
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
9496
9857
  const parsed = [];
9497
9858
  for (const c of candidates) {
9498
- const r = import_types34.DivergenceTypeSchema.safeParse(c);
9859
+ const r = import_types35.DivergenceTypeSchema.safeParse(c);
9499
9860
  if (!r.success) {
9500
9861
  return reply.code(400).send({
9501
9862
  error: `unknown divergence type "${c}"`,
9502
- allowed: import_types34.DivergenceTypeSchema.options
9863
+ allowed: import_types35.DivergenceTypeSchema.options
9503
9864
  });
9504
9865
  }
9505
9866
  parsed.push(r.data);
@@ -9770,7 +10131,7 @@ function registerRoutes(scope, ctx) {
9770
10131
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
9771
10132
  let violations = await log.readAll();
9772
10133
  if (req.query.severity) {
9773
- const sev = import_types34.PolicySeveritySchema.safeParse(req.query.severity);
10134
+ const sev = import_types35.PolicySeveritySchema.safeParse(req.query.severity);
9774
10135
  if (!sev.success) {
9775
10136
  return reply.code(400).send({
9776
10137
  error: "invalid severity",
@@ -9809,7 +10170,7 @@ function registerRoutes(scope, ctx) {
9809
10170
  scope.post("/policies/check", async (req, reply) => {
9810
10171
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
9811
10172
  if (!proj) return;
9812
- const parsed = import_types34.PoliciesCheckBodySchema.safeParse(req.body ?? {});
10173
+ const parsed = import_types35.PoliciesCheckBodySchema.safeParse(req.body ?? {});
9813
10174
  if (!parsed.success) {
9814
10175
  return reply.code(400).send({
9815
10176
  error: "invalid /policies/check body",
@@ -9943,7 +10304,7 @@ function registerRoutes(scope, ctx) {
9943
10304
  });
9944
10305
  }
9945
10306
  async function buildApi(opts) {
9946
- const app = (0, import_fastify.default)({ logger: false });
10307
+ const app = (0, import_fastify.default)({ logger: false, routerOptions: { maxParamLength: 1024 } });
9947
10308
  await app.register(import_cors.default, { origin: true });
9948
10309
  const env = readAuthEnv();
9949
10310
  const authToken = opts.authToken ?? env.authToken;
@@ -10075,13 +10436,13 @@ init_otel();
10075
10436
  // src/daemon.ts
10076
10437
  init_cjs_shims();
10077
10438
  var import_node_fs33 = require("fs");
10078
- var import_node_path52 = __toESM(require("path"), 1);
10439
+ var import_node_path53 = __toESM(require("path"), 1);
10079
10440
  var import_node_module = require("module");
10080
10441
  init_otel();
10081
10442
 
10082
10443
  // src/connectors/index.ts
10083
10444
  init_cjs_shims();
10084
- var import_types35 = require("@neat.is/types");
10445
+ var import_types36 = require("@neat.is/types");
10085
10446
 
10086
10447
  // src/connectors/registry.ts
10087
10448
  init_cjs_shims();
@@ -10159,7 +10520,12 @@ var JUNCTION_DEFAULT_RATE_LIMITS = {
10159
10520
  supabase: { capacity: 30, refillMs: 1e4 },
10160
10521
  // Not a documented API limit at all — a self-imposed ceiling on the raw
10161
10522
  // pg_stat_statements connection (see module header above).
10162
- "supabase-postgres": { capacity: 20, refillMs: 3e3 }
10523
+ "supabase-postgres": { capacity: 20, refillMs: 3e3 },
10524
+ // Push provider (ADR-146): the Drains REST API is touched only by `neat
10525
+ // connector add/remove/test` (provision/deprovision/validate), never a poll
10526
+ // loop, so this bucket is exercised a handful of times per command. Kept
10527
+ // conservative pending a documented Drains-API rate limit.
10528
+ vercel: { capacity: 20, refillMs: 5e3 }
10163
10529
  };
10164
10530
  var JUNCTION_GENERIC_RATE_LIMIT = { capacity: 20, refillMs: 5e3 };
10165
10531
  function defaultRateLimitFor(provider) {
@@ -10451,17 +10817,20 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
10451
10817
  // src/connectors/supabase/map.ts
10452
10818
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
10453
10819
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
10454
- function targetFromRestPath(path61) {
10455
- const rpcMatch = REST_RPC_PATH_RE.exec(path61);
10820
+ function targetFromRestPath(path63) {
10821
+ const rpcMatch = REST_RPC_PATH_RE.exec(path63);
10456
10822
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
10457
- const tableMatch = REST_TABLE_PATH_RE.exec(path61);
10823
+ const tableMatch = REST_TABLE_PATH_RE.exec(path63);
10458
10824
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
10459
10825
  return null;
10460
10826
  }
10461
10827
  var ERROR_STATUS_THRESHOLD = 500;
10462
10828
  function mapEdgeLogRowsToSignals(rows) {
10463
10829
  const buckets2 = /* @__PURE__ */ new Map();
10830
+ if (!Array.isArray(rows)) return [];
10464
10831
  for (const row of rows) {
10832
+ if (!row || typeof row !== "object") continue;
10833
+ if (typeof row.path !== "string" || typeof row.timestamp !== "string") continue;
10465
10834
  const target = targetFromRestPath(row.path);
10466
10835
  if (!target) continue;
10467
10836
  const key = `${target.targetKind}:${target.name}`;
@@ -10502,9 +10871,12 @@ function tableNameFromQueryText(query) {
10502
10871
  function diffPgStatStatementsToSignals(rows, previous, nowIso2) {
10503
10872
  const signals = [];
10504
10873
  const seen = /* @__PURE__ */ new Set();
10874
+ if (!Array.isArray(rows)) return signals;
10505
10875
  for (const row of rows) {
10506
- seen.add(row.queryid);
10876
+ if (!row || typeof row !== "object" || typeof row.queryid !== "string") continue;
10507
10877
  const calls = Number(row.calls);
10878
+ if (!Number.isFinite(calls)) continue;
10879
+ seen.add(row.queryid);
10508
10880
  const prior = previous.get(row.queryid);
10509
10881
  previous.set(row.queryid, { calls });
10510
10882
  if (!prior || calls < prior.calls) continue;
@@ -10557,23 +10929,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
10557
10929
 
10558
10930
  // src/connectors/supabase/resolve.ts
10559
10931
  init_cjs_shims();
10560
- var import_types37 = require("@neat.is/types");
10932
+ var import_types38 = require("@neat.is/types");
10561
10933
  function createSupabaseResolveTarget(graph, config) {
10562
10934
  return (signal, _ctx) => {
10563
10935
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
10564
10936
  return null;
10565
10937
  }
10566
- const subResourceId = (0, import_types37.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
10938
+ const subResourceId = (0, import_types38.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
10567
10939
  if (graph.hasNode(subResourceId)) {
10568
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10940
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types38.EdgeType.CALLS };
10569
10941
  }
10570
- const bareResourceId = (0, import_types37.infraId)(signal.targetKind, signal.targetName);
10942
+ const bareResourceId = (0, import_types38.infraId)(signal.targetKind, signal.targetName);
10571
10943
  if (graph.hasNode(bareResourceId)) {
10572
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10944
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types38.EdgeType.CALLS };
10573
10945
  }
10574
- const projectLevelId = (0, import_types37.infraId)("supabase", config.nodeRef);
10946
+ const projectLevelId = (0, import_types38.infraId)("supabase", config.nodeRef);
10575
10947
  if (graph.hasNode(projectLevelId)) {
10576
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types37.EdgeType.CALLS };
10948
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types38.EdgeType.CALLS };
10577
10949
  }
10578
10950
  return null;
10579
10951
  };
@@ -10666,7 +11038,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
10666
11038
 
10667
11039
  // src/connectors/railway/index.ts
10668
11040
  init_cjs_shims();
10669
- var import_types41 = require("@neat.is/types");
11041
+ var import_types42 = require("@neat.is/types");
10670
11042
 
10671
11043
  // src/connectors/railway/client.ts
10672
11044
  init_cjs_shims();
@@ -10792,7 +11164,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
10792
11164
  const out = [];
10793
11165
  graph.forEachNode((_id, attrs) => {
10794
11166
  const node = attrs;
10795
- if (node.type !== import_types41.NodeType.RouteNode) return;
11167
+ if (node.type !== import_types42.NodeType.RouteNode) return;
10796
11168
  const route = attrs;
10797
11169
  if (route.service !== serviceName) return;
10798
11170
  out.push({
@@ -10828,7 +11200,12 @@ function upsertBucket(buckets2, key, isError, timestamp, build) {
10828
11200
  }
10829
11201
  function mapRailwayHttpLogsToSignals(entries, routeIndex) {
10830
11202
  const buckets2 = /* @__PURE__ */ new Map();
11203
+ if (!Array.isArray(entries)) return [];
10831
11204
  for (const entry2 of entries) {
11205
+ if (!entry2 || typeof entry2 !== "object") continue;
11206
+ if (typeof entry2.method !== "string" || typeof entry2.path !== "string" || typeof entry2.timestamp !== "string") {
11207
+ continue;
11208
+ }
10832
11209
  const method = entry2.method.toUpperCase();
10833
11210
  const normalizedPath = normalizePathTemplate(entry2.path);
10834
11211
  const match = findRailwayRoute(routeIndex, method, normalizedPath);
@@ -10867,8 +11244,11 @@ function mapRailwayHttpLogsToSignals(entries, routeIndex) {
10867
11244
  }
10868
11245
  function mapRailwayNetworkFlowLogsToSignals(entries) {
10869
11246
  const buckets2 = /* @__PURE__ */ new Map();
11247
+ if (!Array.isArray(entries)) return [];
10870
11248
  for (const entry2 of entries) {
10871
- if (!entry2.peerServiceId) continue;
11249
+ if (!entry2 || typeof entry2 !== "object") continue;
11250
+ if (typeof entry2.peerServiceId !== "string" || entry2.peerServiceId.length === 0) continue;
11251
+ if (typeof entry2.timestamp !== "string") continue;
10872
11252
  const isError = entry2.dropCause !== null && entry2.dropCause !== "";
10873
11253
  upsertBucket(buckets2, entry2.peerServiceId, isError, entry2.timestamp, () => ({
10874
11254
  targetKind: PEER_SERVICE_TARGET_KIND,
@@ -10888,12 +11268,12 @@ function createRailwayResolveTarget(config) {
10888
11268
  const serviceName = config.serviceNameById[config.serviceId];
10889
11269
  if (!serviceName) return null;
10890
11270
  if (signal.targetKind === ROUTE_TARGET_KIND) {
10891
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types41.EdgeType.CALLS };
11271
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types42.EdgeType.CALLS };
10892
11272
  }
10893
11273
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
10894
11274
  const peerName = config.serviceNameById[signal.targetName];
10895
11275
  if (!peerName) return null;
10896
- return { targetNodeId: (0, import_types41.serviceId)(peerName), serviceName, edgeType: import_types41.EdgeType.CONNECTS_TO };
11276
+ return { targetNodeId: (0, import_types42.serviceId)(peerName), serviceName, edgeType: import_types42.EdgeType.CONNECTS_TO };
10897
11277
  }
10898
11278
  return null;
10899
11279
  };
@@ -10996,7 +11376,7 @@ async function fetchHttpRequestLogEntries(creds, sinceIso) {
10996
11376
  throw new Error(`Cloud Logging entries.list failed: ${res.status} ${res.statusText}`);
10997
11377
  }
10998
11378
  const json = await res.json();
10999
- out.push(...json.entries ?? []);
11379
+ if (Array.isArray(json.entries)) out.push(...json.entries);
11000
11380
  if (!json.nextPageToken) break;
11001
11381
  pageToken = json.nextPageToken;
11002
11382
  }
@@ -11017,9 +11397,9 @@ function parseFirebaseTargetName(targetName) {
11017
11397
  const secondSep = rest.indexOf(FIELD_SEP);
11018
11398
  if (secondSep === -1) return null;
11019
11399
  const method = rest.slice(0, secondSep);
11020
- const path61 = rest.slice(secondSep + 1);
11021
- if (!resourceName || !method || !path61) return null;
11022
- return { resourceName, method, path: path61 };
11400
+ const path63 = rest.slice(secondSep + 1);
11401
+ if (!resourceName || !method || !path63) return null;
11402
+ return { resourceName, method, path: path63 };
11023
11403
  }
11024
11404
  function resourceNameFor(type, labels) {
11025
11405
  if (!labels) return null;
@@ -11033,7 +11413,7 @@ function resourceNameFor(type, labels) {
11033
11413
  }
11034
11414
  }
11035
11415
  function pathFromRequestUrl(requestUrl) {
11036
- if (!requestUrl) return null;
11416
+ if (typeof requestUrl !== "string" || requestUrl.length === 0) return null;
11037
11417
  if (requestUrl.startsWith("/")) {
11038
11418
  const withoutQuery = requestUrl.split("?")[0];
11039
11419
  return withoutQuery && withoutQuery.length > 0 ? withoutQuery : "/";
@@ -11048,22 +11428,23 @@ function pathFromRequestUrl(requestUrl) {
11048
11428
  }
11049
11429
  var ERROR_STATUS_THRESHOLD2 = 500;
11050
11430
  function mapLogEntryToSignal(entry2) {
11431
+ if (!entry2 || typeof entry2 !== "object") return null;
11051
11432
  const resourceType = entry2.resource?.type;
11052
11433
  if (!resourceType || !isFirebaseResourceType(resourceType)) return null;
11053
11434
  const resourceName = resourceNameFor(resourceType, entry2.resource?.labels);
11054
11435
  if (!resourceName) return null;
11055
11436
  const req = entry2.httpRequest;
11056
11437
  if (!req) return null;
11057
- if (!req.requestMethod) return null;
11438
+ if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
11058
11439
  const method = req.requestMethod.toUpperCase();
11059
- const path61 = pathFromRequestUrl(req.requestUrl);
11060
- if (path61 === null) return null;
11440
+ const path63 = pathFromRequestUrl(req.requestUrl);
11441
+ if (path63 === null) return null;
11061
11442
  const timestamp = entry2.timestamp;
11062
- if (!timestamp) return null;
11443
+ if (typeof timestamp !== "string" || timestamp.length === 0) return null;
11063
11444
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
11064
11445
  return {
11065
11446
  targetKind: resourceType,
11066
- targetName: packFirebaseTargetName({ resourceName, method, path: path61 }),
11447
+ targetName: packFirebaseTargetName({ resourceName, method, path: path63 }),
11067
11448
  callCount: 1,
11068
11449
  errorCount: isError ? 1 : 0,
11069
11450
  lastObservedIso: timestamp
@@ -11080,7 +11461,7 @@ function mapLogEntriesToSignals(entries) {
11080
11461
 
11081
11462
  // src/connectors/firebase/resolve.ts
11082
11463
  init_cjs_shims();
11083
- var import_types42 = require("@neat.is/types");
11464
+ var import_types43 = require("@neat.is/types");
11084
11465
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
11085
11466
  switch (resourceType) {
11086
11467
  case "cloud_function":
@@ -11095,7 +11476,7 @@ function routeEntriesFor(graph, serviceName) {
11095
11476
  const entries = [];
11096
11477
  graph.forEachNode((_id, attrs) => {
11097
11478
  const node = attrs;
11098
- if (node.type !== import_types42.NodeType.RouteNode) return;
11479
+ if (node.type !== import_types43.NodeType.RouteNode) return;
11099
11480
  const route = attrs;
11100
11481
  if (route.service !== serviceName) return;
11101
11482
  entries.push({
@@ -11127,7 +11508,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
11127
11508
  return {
11128
11509
  targetNodeId: match.routeNodeId,
11129
11510
  serviceName,
11130
- edgeType: import_types42.EdgeType.CALLS
11511
+ edgeType: import_types43.EdgeType.CALLS
11131
11512
  };
11132
11513
  };
11133
11514
  }
@@ -11154,7 +11535,7 @@ init_cjs_shims();
11154
11535
 
11155
11536
  // src/connectors/cloudflare/connector.ts
11156
11537
  init_cjs_shims();
11157
- var import_types44 = require("@neat.is/types");
11538
+ var import_types45 = require("@neat.is/types");
11158
11539
 
11159
11540
  // src/connectors/cloudflare/client.ts
11160
11541
  init_cjs_shims();
@@ -11211,7 +11592,7 @@ async function queryWorkerInvocations(ctx, config, window, fetchImpl = fetch) {
11211
11592
  throw new Error(`cloudflare connector: telemetry query returned an error (${message})`);
11212
11593
  }
11213
11594
  const events = payload.result?.events?.events;
11214
- if (events === void 0) {
11595
+ if (!Array.isArray(events)) {
11215
11596
  console.warn(
11216
11597
  "[neat connector] cloudflare: telemetry query returned success:true but no result.events.events array \u2014 the response shape may have changed; treating as zero events this tick"
11217
11598
  );
@@ -11241,7 +11622,7 @@ var HTTP_METHODS = /* @__PURE__ */ new Set([
11241
11622
  ]);
11242
11623
  var LEADING_TOKEN_RE = /^(\S+)\s+\S/;
11243
11624
  function parseHttpMethodFromTrigger(trigger) {
11244
- if (!trigger) return null;
11625
+ if (typeof trigger !== "string") return null;
11245
11626
  const match = LEADING_TOKEN_RE.exec(trigger.trim());
11246
11627
  const token = match?.[1];
11247
11628
  if (!token) return null;
@@ -11257,25 +11638,28 @@ function parsePathFromTrigger(trigger) {
11257
11638
  }
11258
11639
  var ERROR_STATUS_THRESHOLD3 = 500;
11259
11640
  function mapEventToSignal(event) {
11641
+ if (!event || typeof event !== "object") return null;
11260
11642
  const metadata = event.$metadata;
11261
11643
  const workers = event.$workers;
11262
11644
  const method = parseHttpMethodFromTrigger(metadata?.trigger);
11263
11645
  if (!method) return null;
11264
11646
  const scriptName = workers?.scriptName ?? metadata?.service;
11265
- if (!scriptName) return null;
11647
+ if (typeof scriptName !== "string" || scriptName.length === 0) return null;
11266
11648
  const timestampMs = event.timestamp ?? metadata?.startTime;
11267
11649
  if (typeof timestampMs !== "number" || !Number.isFinite(timestampMs)) return null;
11650
+ const observedAt = new Date(timestampMs);
11651
+ if (Number.isNaN(observedAt.getTime())) return null;
11268
11652
  const statusCode = metadata?.statusCode;
11269
11653
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
11270
- const path61 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
11654
+ const path63 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
11271
11655
  return {
11272
11656
  targetKind: CLOUDFLARE_TARGET_KIND,
11273
11657
  targetName: scriptName,
11274
11658
  callCount: 1,
11275
11659
  errorCount: isError ? 1 : 0,
11276
- lastObservedIso: new Date(timestampMs).toISOString(),
11660
+ lastObservedIso: observedAt.toISOString(),
11277
11661
  method,
11278
- ...path61 ? { path: path61 } : {},
11662
+ ...path63 ? { path: path63 } : {},
11279
11663
  ...typeof statusCode === "number" ? { statusCode } : {},
11280
11664
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
11281
11665
  };
@@ -11315,19 +11699,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
11315
11699
  graph.forEachNode((id, attrs) => {
11316
11700
  if (found) return;
11317
11701
  const a = attrs;
11318
- if (a.type === import_types44.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
11702
+ if (a.type === import_types45.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
11319
11703
  found = id;
11320
11704
  }
11321
11705
  });
11322
11706
  return found;
11323
11707
  }
11324
- function findMatchingRouteNode(graph, serviceName, method, path61) {
11325
- const normalizedPath = normalizePathTemplate(path61);
11708
+ function findMatchingRouteNode(graph, serviceName, method, path63) {
11709
+ const normalizedPath = normalizePathTemplate(path63);
11326
11710
  let found = null;
11327
11711
  graph.forEachNode((id, attrs) => {
11328
11712
  if (found) return;
11329
11713
  const a = attrs;
11330
- if (a.type !== import_types44.NodeType.RouteNode || a.service !== serviceName) return;
11714
+ if (a.type !== import_types45.NodeType.RouteNode || a.service !== serviceName) return;
11331
11715
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
11332
11716
  const routeMethod = (a.method ?? "").toUpperCase();
11333
11717
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -11339,18 +11723,18 @@ function createCloudflareResolveTarget(config, graph) {
11339
11723
  return (signal) => {
11340
11724
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
11341
11725
  const scriptName = signal.targetName;
11342
- const { method, path: path61 } = signal;
11726
+ const { method, path: path63 } = signal;
11343
11727
  const resolveRouteGrain = (serviceName, wholeFileId) => {
11344
- if (!method || !path61) return wholeFileId;
11345
- return findMatchingRouteNode(graph, serviceName, method, path61) ?? wholeFileId;
11728
+ if (!method || !path63) return wholeFileId;
11729
+ return findMatchingRouteNode(graph, serviceName, method, path63) ?? wholeFileId;
11346
11730
  };
11347
11731
  const mapping = config.workers?.[scriptName];
11348
11732
  if (mapping) {
11349
- const wholeFileId = (0, import_types44.fileId)(mapping.service, mapping.entryFile);
11733
+ const wholeFileId = (0, import_types45.fileId)(mapping.service, mapping.entryFile);
11350
11734
  return {
11351
11735
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
11352
11736
  serviceName: mapping.service,
11353
- edgeType: import_types44.EdgeType.CALLS
11737
+ edgeType: import_types45.EdgeType.CALLS
11354
11738
  };
11355
11739
  }
11356
11740
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -11359,18 +11743,124 @@ function createCloudflareResolveTarget(config, graph) {
11359
11743
  return {
11360
11744
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
11361
11745
  serviceName: fileNode.service,
11362
- edgeType: import_types44.EdgeType.CALLS
11746
+ edgeType: import_types45.EdgeType.CALLS
11363
11747
  };
11364
11748
  }
11365
11749
  return {
11366
- targetNodeId: (0, import_types44.infraId)("cloudflare-worker", scriptName),
11750
+ targetNodeId: (0, import_types45.infraId)("cloudflare-worker", scriptName),
11367
11751
  serviceName: scriptName,
11368
- edgeType: import_types44.EdgeType.CALLS,
11752
+ edgeType: import_types45.EdgeType.CALLS,
11369
11753
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
11370
11754
  };
11371
11755
  };
11372
11756
  }
11373
11757
 
11758
+ // src/connectors/vercel/index.ts
11759
+ init_cjs_shims();
11760
+
11761
+ // src/connectors/vercel/client.ts
11762
+ init_cjs_shims();
11763
+ var DEFAULT_API_BASE_URL = "https://api.vercel.com";
11764
+ var DEFAULT_DRAIN_NAME = "neat-otlp";
11765
+ var TRACE_SCHEMAS = { trace: { version: "v1" } };
11766
+ function apiBase(config) {
11767
+ return config.apiBaseUrl ?? DEFAULT_API_BASE_URL;
11768
+ }
11769
+ function teamQuery(config) {
11770
+ return `?teamId=${encodeURIComponent(config.teamId)}`;
11771
+ }
11772
+ function drainDelivery(config, otelToken) {
11773
+ return {
11774
+ type: "http",
11775
+ endpoint: config.endpoint,
11776
+ encoding: "json",
11777
+ headers: bearerAuthHeader(otelToken),
11778
+ ...config.secret ? { secret: config.secret } : {}
11779
+ };
11780
+ }
11781
+ async function describeError(res) {
11782
+ try {
11783
+ const data = await res.json();
11784
+ const message = data?.error?.message;
11785
+ return typeof message === "string" && message.length > 0 ? ` \u2014 ${message}` : "";
11786
+ } catch {
11787
+ return "";
11788
+ }
11789
+ }
11790
+ async function createVercelDrain(config, credentials, fetchImpl = fetch) {
11791
+ const projectIds = config.projectIds ?? [];
11792
+ const body = {
11793
+ name: config.drainName ?? DEFAULT_DRAIN_NAME,
11794
+ projects: projectIds.length > 0 ? "some" : "all",
11795
+ ...projectIds.length > 0 ? { projectIds } : {},
11796
+ schemas: TRACE_SCHEMAS,
11797
+ delivery: drainDelivery(config, credentials.otelToken),
11798
+ source: { kind: "self-served" }
11799
+ };
11800
+ const res = await junctionFetch(
11801
+ `${apiBase(config)}/v1/drains${teamQuery(config)}`,
11802
+ {
11803
+ method: "POST",
11804
+ headers: { "Content-Type": "application/json", ...bearerAuthHeader(credentials.token) },
11805
+ body: JSON.stringify(body)
11806
+ },
11807
+ { provider: "vercel", accountKey: config.teamId, fetchImpl }
11808
+ );
11809
+ if (!res.ok) {
11810
+ throw new Error(
11811
+ `vercel connector: create drain failed (${res.status} ${res.statusText}${await describeError(res)})`
11812
+ );
11813
+ }
11814
+ const payload = await res.json().catch(() => null);
11815
+ if (!payload || typeof payload.id !== "string" || payload.id.length === 0) {
11816
+ throw new Error(
11817
+ "vercel connector: create drain returned no drain id \u2014 the Drains API response shape may have changed"
11818
+ );
11819
+ }
11820
+ return {
11821
+ id: payload.id,
11822
+ ...payload.status ? { status: payload.status } : {},
11823
+ ...payload.disabledReason ? { disabledReason: payload.disabledReason } : {}
11824
+ };
11825
+ }
11826
+ async function deleteVercelDrain(config, drainId, credentials, fetchImpl = fetch) {
11827
+ const res = await junctionFetch(
11828
+ `${apiBase(config)}/v1/drains/${encodeURIComponent(drainId)}${teamQuery(config)}`,
11829
+ { method: "DELETE", headers: { ...bearerAuthHeader(credentials.token) } },
11830
+ { provider: "vercel", accountKey: config.teamId, fetchImpl }
11831
+ );
11832
+ if (res.ok || res.status === 404) return;
11833
+ throw new Error(
11834
+ `vercel connector: delete drain failed (${res.status} ${res.statusText}${await describeError(res)})`
11835
+ );
11836
+ }
11837
+ async function testVercelDrainDelivery(config, credentials, fetchImpl = fetch) {
11838
+ const res = await junctionFetch(
11839
+ `${apiBase(config)}/v1/drains/test${teamQuery(config)}`,
11840
+ {
11841
+ method: "POST",
11842
+ headers: { "Content-Type": "application/json", ...bearerAuthHeader(credentials.token) },
11843
+ body: JSON.stringify({ schemas: TRACE_SCHEMAS, delivery: drainDelivery(config, credentials.otelToken) })
11844
+ },
11845
+ { provider: "vercel", accountKey: config.teamId, fetchImpl }
11846
+ );
11847
+ if (res.status === 401 || res.status === 403) {
11848
+ return { status: "failure", error: `vercel rejected the API token (HTTP ${res.status})` };
11849
+ }
11850
+ if (!res.ok) {
11851
+ return {
11852
+ status: "failure",
11853
+ error: `vercel drain validation failed (${res.status} ${res.statusText}${await describeError(res)})`
11854
+ };
11855
+ }
11856
+ const payload = await res.json().catch(() => null);
11857
+ return {
11858
+ ...payload?.status ? { status: payload.status } : {},
11859
+ ...payload?.error ? { error: payload.error } : {},
11860
+ ...payload?.endpoint ? { endpoint: payload.endpoint } : {}
11861
+ };
11862
+ }
11863
+
11374
11864
  // src/connectors/registry.ts
11375
11865
  var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
11376
11866
  async function authProbe(input) {
@@ -11522,8 +12012,96 @@ var PROVIDER_DISPATCH = {
11522
12012
  }
11523
12013
  }
11524
12014
  };
11525
- function getProviderDispatch(provider) {
11526
- return PROVIDER_DISPATCH[provider];
12015
+ function vercelCredsFrom(credentials) {
12016
+ return { token: String(credentials.token ?? ""), otelToken: String(credentials.otelToken ?? "") };
12017
+ }
12018
+ function vercelConfigFromOptions(options) {
12019
+ const raw = options.projectIds;
12020
+ let projectIds;
12021
+ if (Array.isArray(raw)) projectIds = raw.filter((p) => typeof p === "string");
12022
+ else if (typeof raw === "string" && raw.trim().length > 0) {
12023
+ projectIds = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
12024
+ }
12025
+ return {
12026
+ teamId: String(options.teamId ?? ""),
12027
+ endpoint: String(options.endpoint ?? ""),
12028
+ ...projectIds && projectIds.length > 0 ? { projectIds } : {},
12029
+ ...typeof options.drainId === "string" ? { drainId: options.drainId } : {},
12030
+ ...typeof options.drainName === "string" ? { drainName: options.drainName } : {},
12031
+ ...typeof options.apiBaseUrl === "string" ? { apiBaseUrl: options.apiBaseUrl } : {},
12032
+ ...typeof options.secret === "string" ? { secret: options.secret } : {}
12033
+ };
12034
+ }
12035
+ var PUSH_PROVIDER_DISPATCH = {
12036
+ vercel: {
12037
+ provider: "vercel",
12038
+ // The Vercel access token is the "primary" secret a single `--token`
12039
+ // populates; `otelToken` (the daemon's OTLP bearer) is the second field.
12040
+ primaryCredentialKey: "token",
12041
+ requiredCredentialFields: ["token", "otelToken"],
12042
+ // teamId scopes every Drains call; endpoint is where the drain delivers.
12043
+ // projectIds is optional (absent → the drain covers the whole team).
12044
+ requiredOptionFields: ["teamId", "endpoint"],
12045
+ // POST /v1/drains/test — authenticates the token and pings the endpoint
12046
+ // with a sample event, so `success` means the credential is live *and* the
12047
+ // daemon's OTLP endpoint is reachable and accepted the drain's bearer.
12048
+ async validate({ credentials, options, fetchImpl }) {
12049
+ const result = await testVercelDrainDelivery(
12050
+ vercelConfigFromOptions(options),
12051
+ vercelCredsFrom(credentials),
12052
+ fetchImpl
12053
+ );
12054
+ if (result.status === "success") return { ok: true };
12055
+ return {
12056
+ ok: false,
12057
+ reason: result.error ?? `vercel drain delivery test returned "${result.status ?? "no status"}"`
12058
+ };
12059
+ },
12060
+ // POST /v1/drains — creates the trace drain, returns its id to store in
12061
+ // `options.drainId`. A created-but-not-enabled drain is surfaced as a note,
12062
+ // not a failure (the entry still points at a real drain).
12063
+ async provision({ credentials, options, fetchImpl }) {
12064
+ try {
12065
+ const created = await createVercelDrain(
12066
+ vercelConfigFromOptions(options),
12067
+ vercelCredsFrom(credentials),
12068
+ fetchImpl
12069
+ );
12070
+ 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;
12071
+ return { ok: true, options: { drainId: created.id }, ...note ? { note } : {} };
12072
+ } catch (err) {
12073
+ return { ok: false, reason: err.message };
12074
+ }
12075
+ },
12076
+ // DELETE /v1/drains/{id} — idempotent (deleteVercelDrain treats 404 as
12077
+ // success). No recorded drainId → nothing to delete, still a success.
12078
+ async deprovision({ credentials, options, fetchImpl }) {
12079
+ const drainId = typeof options.drainId === "string" ? options.drainId : "";
12080
+ if (!drainId) {
12081
+ return { ok: true, note: "no drain id was recorded \u2014 nothing to delete on the Vercel side" };
12082
+ }
12083
+ try {
12084
+ await deleteVercelDrain(
12085
+ vercelConfigFromOptions(options),
12086
+ drainId,
12087
+ vercelCredsFrom(credentials),
12088
+ fetchImpl
12089
+ );
12090
+ return { ok: true };
12091
+ } catch (err) {
12092
+ return { ok: false, reason: err.message };
12093
+ }
12094
+ }
12095
+ }
12096
+ };
12097
+ function isPushProvider(provider) {
12098
+ return provider in PUSH_PROVIDER_DISPATCH;
12099
+ }
12100
+ function getProviderFieldSchema(provider) {
12101
+ return PROVIDER_DISPATCH[provider] ?? PUSH_PROVIDER_DISPATCH[provider];
12102
+ }
12103
+ function knownProviderNames() {
12104
+ return [...Object.keys(PROVIDER_DISPATCH), ...Object.keys(PUSH_PROVIDER_DISPATCH)].sort();
11527
12105
  }
11528
12106
  function resolveEntryCredentials(dispatch, entry2, env) {
11529
12107
  let credentials;
@@ -11545,7 +12123,7 @@ function resolveEntryCredentials(dispatch, entry2, env) {
11545
12123
  return { ok: true, credentials };
11546
12124
  }
11547
12125
  async function validateConnectorEntry(entry2, env = process.env, fetchImpl) {
11548
- const dispatch = PROVIDER_DISPATCH[entry2.provider];
12126
+ const dispatch = PROVIDER_DISPATCH[entry2.provider] ?? PUSH_PROVIDER_DISPATCH[entry2.provider];
11549
12127
  if (!dispatch) {
11550
12128
  return { status: "unknown-provider", reason: `unknown provider "${entry2.provider}"` };
11551
12129
  }
@@ -11569,6 +12147,54 @@ async function validateConnectorEntry(entry2, env = process.env, fetchImpl) {
11569
12147
  });
11570
12148
  return result.ok ? { status: "ok" } : { status: "rejected", reason: result.reason };
11571
12149
  }
12150
+ function resolvePushEntry(entry2, env) {
12151
+ const dispatch = PUSH_PROVIDER_DISPATCH[entry2.provider];
12152
+ if (!dispatch) {
12153
+ return PROVIDER_DISPATCH[entry2.provider] ? {
12154
+ ok: false,
12155
+ outcome: {
12156
+ status: "not-push",
12157
+ reason: `provider "${entry2.provider}" is polled, not provisioned \u2014 there is no drain to manage`
12158
+ }
12159
+ } : { ok: false, outcome: { status: "unknown-provider", reason: `unknown provider "${entry2.provider}"` } };
12160
+ }
12161
+ const creds = resolveEntryCredentials(dispatch, entry2, env);
12162
+ if (!creds.ok) {
12163
+ const status2 = creds.kind === "unset-env" ? "unset-env" : creds.kind === "missing-field" ? "missing-field" : "failed";
12164
+ return { ok: false, outcome: { status: status2, reason: creds.reason } };
12165
+ }
12166
+ const options = entry2.options ?? {};
12167
+ const missingOpts = dispatch.requiredOptionFields.filter((k) => !(k in options));
12168
+ if (missingOpts.length > 0) {
12169
+ return {
12170
+ ok: false,
12171
+ outcome: { status: "missing-field", reason: `options missing required field(s): ${missingOpts.join(", ")}` }
12172
+ };
12173
+ }
12174
+ return { ok: true, dispatch, credentials: creds.credentials, options };
12175
+ }
12176
+ async function provisionConnector(entry2, env = process.env, fetchImpl) {
12177
+ const resolved = resolvePushEntry(entry2, env);
12178
+ if (!resolved.ok) return resolved.outcome;
12179
+ const result = await resolved.dispatch.provision({
12180
+ credentials: resolved.credentials,
12181
+ options: resolved.options,
12182
+ ...fetchImpl ? { fetchImpl } : {}
12183
+ });
12184
+ if (!result.ok) return { status: "failed", reason: result.reason };
12185
+ return { status: "ok", ...result.options ? { options: result.options } : {}, ...result.note ? { note: result.note } : {} };
12186
+ }
12187
+ async function deprovisionConnector(entry2, env = process.env, fetchImpl) {
12188
+ const resolved = resolvePushEntry(entry2, env);
12189
+ if (!resolved.ok) return resolved.outcome;
12190
+ const result = await resolved.dispatch.deprovision({
12191
+ credentials: resolved.credentials,
12192
+ options: resolved.options,
12193
+ ...fetchImpl ? { fetchImpl } : {}
12194
+ });
12195
+ if (!result.ok) return { status: "failed", reason: result.reason };
12196
+ return { status: "ok", ...result.note ? { note: result.note } : {} };
12197
+ }
11572
12198
 
11573
12199
  // src/daemon.ts
11574
12200
  init_auth();
@@ -11576,28 +12202,28 @@ init_auth();
11576
12202
  // src/unrouted.ts
11577
12203
  init_cjs_shims();
11578
12204
  var import_node_fs32 = require("fs");
11579
- var import_node_path51 = __toESM(require("path"), 1);
12205
+ var import_node_path52 = __toESM(require("path"), 1);
11580
12206
 
11581
12207
  // src/daemon.ts
11582
- var import_types47 = require("@neat.is/types");
12208
+ var import_types48 = require("@neat.is/types");
11583
12209
  function daemonJsonPath(scanPath) {
11584
- return import_node_path52.default.join(scanPath, "neat-out", "daemon.json");
12210
+ return import_node_path53.default.join(scanPath, "neat-out", "daemon.json");
11585
12211
  }
11586
12212
  function daemonsDiscoveryDir(home) {
11587
12213
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
11588
- return import_node_path52.default.join(base, "daemons");
12214
+ return import_node_path53.default.join(base, "daemons");
11589
12215
  }
11590
12216
  function daemonDiscoveryPath(project, home) {
11591
- return import_node_path52.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
12217
+ return import_node_path53.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
11592
12218
  }
11593
12219
  function sanitizeDiscoveryName(project) {
11594
12220
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
11595
12221
  }
11596
12222
  function neatHomeFromEnv() {
11597
12223
  const env = process.env.NEAT_HOME;
11598
- if (env && env.length > 0) return import_node_path52.default.resolve(env);
12224
+ if (env && env.length > 0) return import_node_path53.default.resolve(env);
11599
12225
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
11600
- return import_node_path52.default.join(home, ".neat");
12226
+ return import_node_path53.default.join(home, ".neat");
11601
12227
  }
11602
12228
  async function readDaemonRecord(scanPath) {
11603
12229
  try {
@@ -11668,7 +12294,7 @@ init_otel_grpc();
11668
12294
  // src/search.ts
11669
12295
  init_cjs_shims();
11670
12296
  var import_node_fs34 = require("fs");
11671
- var import_node_path53 = __toESM(require("path"), 1);
12297
+ var import_node_path54 = __toESM(require("path"), 1);
11672
12298
  var import_node_crypto4 = require("crypto");
11673
12299
  var DEFAULT_LIMIT = 10;
11674
12300
  var NOMIC_DIM = 768;
@@ -11831,7 +12457,7 @@ async function readCache(cachePath) {
11831
12457
  }
11832
12458
  }
11833
12459
  async function writeCache(cachePath, cache) {
11834
- await import_node_fs34.promises.mkdir(import_node_path53.default.dirname(cachePath), { recursive: true });
12460
+ await import_node_fs34.promises.mkdir(import_node_path54.default.dirname(cachePath), { recursive: true });
11835
12461
  await import_node_fs34.promises.writeFile(cachePath, JSON.stringify(cache));
11836
12462
  }
11837
12463
  var VectorIndex = class {
@@ -11989,8 +12615,8 @@ var ALL_PHASES = [
11989
12615
  ];
11990
12616
  function classifyChange(relPath) {
11991
12617
  const phases = /* @__PURE__ */ new Set();
11992
- const base = import_node_path54.default.basename(relPath).toLowerCase();
11993
- const segments = relPath.split(import_node_path54.default.sep).map((s) => s.toLowerCase());
12618
+ const base = import_node_path55.default.basename(relPath).toLowerCase();
12619
+ const segments = relPath.split(import_node_path55.default.sep).map((s) => s.toLowerCase());
11994
12620
  if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
11995
12621
  phases.add("services");
11996
12622
  phases.add("aliases");
@@ -12118,9 +12744,9 @@ function countWatchableDirs(scanPath, limit) {
12118
12744
  for (const e of entries) {
12119
12745
  if (count >= limit) return;
12120
12746
  if (!e.isDirectory()) continue;
12121
- if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path54.default.join(dir, e.name) + import_node_path54.default.sep))) continue;
12747
+ if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path55.default.join(dir, e.name) + import_node_path55.default.sep))) continue;
12122
12748
  count++;
12123
- if (depth < 2) visit(import_node_path54.default.join(dir, e.name), depth + 1);
12749
+ if (depth < 2) visit(import_node_path55.default.join(dir, e.name), depth + 1);
12124
12750
  }
12125
12751
  };
12126
12752
  visit(scanPath, 0);
@@ -12138,8 +12764,8 @@ async function startWatch(graph, opts) {
12138
12764
  const projectName = opts.project ?? DEFAULT_PROJECT;
12139
12765
  await loadGraphFromDisk(graph, opts.outPath);
12140
12766
  const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
12141
- const policyFilePath = import_node_path54.default.join(opts.scanPath, "policy.json");
12142
- const policyViolationsPath = import_node_path54.default.join(import_node_path54.default.dirname(opts.outPath), "policy-violations.ndjson");
12767
+ const policyFilePath = import_node_path55.default.join(opts.scanPath, "policy.json");
12768
+ const policyViolationsPath = import_node_path55.default.join(import_node_path55.default.dirname(opts.outPath), "policy-violations.ndjson");
12143
12769
  let policies = [];
12144
12770
  try {
12145
12771
  policies = await loadPolicyFile(policyFilePath);
@@ -12190,7 +12816,7 @@ async function startWatch(graph, opts) {
12190
12816
  assertBindAuthority(host, auth.authToken);
12191
12817
  const port = opts.port ?? 8080;
12192
12818
  const otelPort = opts.otelPort ?? 4318;
12193
- const cachePath = opts.embeddingsCachePath ?? import_node_path54.default.join(import_node_path54.default.dirname(opts.outPath), "embeddings.json");
12819
+ const cachePath = opts.embeddingsCachePath ?? import_node_path55.default.join(import_node_path55.default.dirname(opts.outPath), "embeddings.json");
12194
12820
  let searchIndex;
12195
12821
  try {
12196
12822
  searchIndex = await buildSearchIndex(graph, { cachePath });
@@ -12208,7 +12834,7 @@ async function startWatch(graph, opts) {
12208
12834
  // Paths are derived from the explicit options the watch caller passes
12209
12835
  // — pathsForProject is only used to fill in the embeddings/snapshot
12210
12836
  // fields so the registry shape is complete.
12211
- ...pathsForProject(projectName, import_node_path54.default.dirname(opts.outPath)),
12837
+ ...pathsForProject(projectName, import_node_path55.default.dirname(opts.outPath)),
12212
12838
  snapshotPath: opts.outPath,
12213
12839
  errorsPath: opts.errorsPath,
12214
12840
  staleEventsPath: opts.staleEventsPath
@@ -12326,9 +12952,9 @@ async function startWatch(graph, opts) {
12326
12952
  };
12327
12953
  const onPath = (absPath) => {
12328
12954
  if (shouldIgnore(absPath)) return;
12329
- const rel = import_node_path54.default.relative(opts.scanPath, absPath);
12955
+ const rel = import_node_path55.default.relative(opts.scanPath, absPath);
12330
12956
  if (!rel || rel.startsWith("..")) return;
12331
- pendingPaths.add(rel.split(import_node_path54.default.sep).join("/"));
12957
+ pendingPaths.add(rel.split(import_node_path55.default.sep).join("/"));
12332
12958
  const phases = classifyChange(rel);
12333
12959
  if (phases.size === 0) {
12334
12960
  for (const p of ALL_PHASES) pending.add(p);
@@ -12385,7 +13011,7 @@ async function startWatch(graph, opts) {
12385
13011
  // src/deploy/detect.ts
12386
13012
  init_cjs_shims();
12387
13013
  var import_node_fs36 = require("fs");
12388
- var import_node_path55 = __toESM(require("path"), 1);
13014
+ var import_node_path56 = __toESM(require("path"), 1);
12389
13015
  var import_node_child_process2 = require("child_process");
12390
13016
  var import_node_crypto5 = require("crypto");
12391
13017
  function generateToken() {
@@ -12485,7 +13111,7 @@ async function runDeploy(opts = {}) {
12485
13111
  const token = generateToken();
12486
13112
  switch (substrate) {
12487
13113
  case "docker-compose": {
12488
- const artifactPath = import_node_path55.default.join(cwd, "docker-compose.neat.yml");
13114
+ const artifactPath = import_node_path56.default.join(cwd, "docker-compose.neat.yml");
12489
13115
  const contents = emitDockerCompose(cwd);
12490
13116
  await import_node_fs36.promises.writeFile(artifactPath, contents, "utf8");
12491
13117
  return {
@@ -12493,11 +13119,11 @@ async function runDeploy(opts = {}) {
12493
13119
  artifactPath,
12494
13120
  token,
12495
13121
  contents,
12496
- startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path55.default.basename(artifactPath)} up -d`
13122
+ startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path56.default.basename(artifactPath)} up -d`
12497
13123
  };
12498
13124
  }
12499
13125
  case "systemd": {
12500
- const artifactPath = import_node_path55.default.join(cwd, "neat.service");
13126
+ const artifactPath = import_node_path56.default.join(cwd, "neat.service");
12501
13127
  const contents = emitSystemdUnit(cwd);
12502
13128
  await import_node_fs36.promises.writeFile(artifactPath, contents, "utf8");
12503
13129
  return {
@@ -12533,7 +13159,7 @@ init_cjs_shims();
12533
13159
  // src/installers/javascript.ts
12534
13160
  init_cjs_shims();
12535
13161
  var import_node_fs37 = require("fs");
12536
- var import_node_path56 = __toESM(require("path"), 1);
13162
+ var import_node_path57 = __toESM(require("path"), 1);
12537
13163
  var import_semver2 = __toESM(require("semver"), 1);
12538
13164
 
12539
13165
  // src/installers/templates.ts
@@ -13164,11 +13790,11 @@ var OTEL_ENV = {
13164
13790
  value: "http://localhost:4318/projects/<project>/v1/traces"
13165
13791
  };
13166
13792
  function serviceNodeName(pkg, serviceDir) {
13167
- return pkg.name ?? import_node_path56.default.basename(serviceDir);
13793
+ return pkg.name ?? import_node_path57.default.basename(serviceDir);
13168
13794
  }
13169
13795
  function projectToken(pkg, serviceDir, project) {
13170
13796
  if (project && project.length > 0) return project;
13171
- return pkg.name ?? import_node_path56.default.basename(serviceDir);
13797
+ return pkg.name ?? import_node_path57.default.basename(serviceDir);
13172
13798
  }
13173
13799
  async function readJsonFile(p) {
13174
13800
  try {
@@ -13181,16 +13807,16 @@ async function readJsonFile(p) {
13181
13807
  async function detectRuntimeKind(pkgRoot, pkg) {
13182
13808
  const deps = allDeps(pkg);
13183
13809
  if ("react-native" in deps || "expo" in deps) return "react-native";
13184
- const appJson = await readJsonFile(import_node_path56.default.join(pkgRoot, "app.json"));
13810
+ const appJson = await readJsonFile(import_node_path57.default.join(pkgRoot, "app.json"));
13185
13811
  if (appJson && typeof appJson === "object" && "expo" in appJson) {
13186
13812
  return "react-native";
13187
13813
  }
13188
- 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) {
13814
+ 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) {
13189
13815
  return "browser-bundle";
13190
13816
  }
13191
- if (await exists3(import_node_path56.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
13192
- if (await exists3(import_node_path56.default.join(pkgRoot, "bun.lockb"))) return "bun";
13193
- if (await exists3(import_node_path56.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path56.default.join(pkgRoot, "deno.lock"))) {
13817
+ if (await exists3(import_node_path57.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
13818
+ if (await exists3(import_node_path57.default.join(pkgRoot, "bun.lockb"))) return "bun";
13819
+ if (await exists3(import_node_path57.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path57.default.join(pkgRoot, "deno.lock"))) {
13194
13820
  return "deno";
13195
13821
  }
13196
13822
  const engines = pkg.engines ?? {};
@@ -13199,7 +13825,7 @@ async function detectRuntimeKind(pkgRoot, pkg) {
13199
13825
  }
13200
13826
  async function readPackageJson2(serviceDir) {
13201
13827
  try {
13202
- const raw = await import_node_fs37.promises.readFile(import_node_path56.default.join(serviceDir, "package.json"), "utf8");
13828
+ const raw = await import_node_fs37.promises.readFile(import_node_path57.default.join(serviceDir, "package.json"), "utf8");
13203
13829
  return JSON.parse(raw);
13204
13830
  } catch {
13205
13831
  return null;
@@ -13243,7 +13869,7 @@ function needsVersionUpgrade(installed, expected) {
13243
13869
  var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
13244
13870
  async function findNextConfig(serviceDir) {
13245
13871
  for (const name of NEXT_CONFIG_CANDIDATES) {
13246
- const candidate = import_node_path56.default.join(serviceDir, name);
13872
+ const candidate = import_node_path57.default.join(serviceDir, name);
13247
13873
  if (await exists3(candidate)) return candidate;
13248
13874
  }
13249
13875
  return null;
@@ -13312,7 +13938,7 @@ function hasRemixDependency(pkg) {
13312
13938
  }
13313
13939
  async function findRemixEntry(serviceDir) {
13314
13940
  for (const rel of REMIX_ENTRY_CANDIDATES) {
13315
- const candidate = import_node_path56.default.join(serviceDir, rel);
13941
+ const candidate = import_node_path57.default.join(serviceDir, rel);
13316
13942
  if (await exists3(candidate)) return candidate;
13317
13943
  }
13318
13944
  return null;
@@ -13324,14 +13950,14 @@ function hasSvelteKitDependency(pkg) {
13324
13950
  }
13325
13951
  async function findSvelteKitHooks(serviceDir) {
13326
13952
  for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
13327
- const candidate = import_node_path56.default.join(serviceDir, rel);
13953
+ const candidate = import_node_path57.default.join(serviceDir, rel);
13328
13954
  if (await exists3(candidate)) return candidate;
13329
13955
  }
13330
13956
  return null;
13331
13957
  }
13332
13958
  async function findSvelteKitConfig(serviceDir) {
13333
13959
  for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
13334
- const candidate = import_node_path56.default.join(serviceDir, rel);
13960
+ const candidate = import_node_path57.default.join(serviceDir, rel);
13335
13961
  if (await exists3(candidate)) return candidate;
13336
13962
  }
13337
13963
  return null;
@@ -13342,7 +13968,7 @@ function hasNuxtDependency(pkg) {
13342
13968
  }
13343
13969
  async function findNuxtConfig(serviceDir) {
13344
13970
  for (const name of NUXT_CONFIG_CANDIDATES) {
13345
- const candidate = import_node_path56.default.join(serviceDir, name);
13971
+ const candidate = import_node_path57.default.join(serviceDir, name);
13346
13972
  if (await exists3(candidate)) return candidate;
13347
13973
  }
13348
13974
  return null;
@@ -13353,7 +13979,7 @@ function hasAstroDependency(pkg) {
13353
13979
  }
13354
13980
  async function findAstroConfig(serviceDir) {
13355
13981
  for (const name of ASTRO_CONFIG_CANDIDATES) {
13356
- const candidate = import_node_path56.default.join(serviceDir, name);
13982
+ const candidate = import_node_path57.default.join(serviceDir, name);
13357
13983
  if (await exists3(candidate)) return candidate;
13358
13984
  }
13359
13985
  return null;
@@ -13367,7 +13993,7 @@ function parseNextMajor(range) {
13367
13993
  return Number.isFinite(n) ? n : null;
13368
13994
  }
13369
13995
  async function isTypeScriptProject(serviceDir) {
13370
- return exists3(import_node_path56.default.join(serviceDir, "tsconfig.json"));
13996
+ return exists3(import_node_path57.default.join(serviceDir, "tsconfig.json"));
13371
13997
  }
13372
13998
  var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
13373
13999
  var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
@@ -13416,7 +14042,7 @@ function entryFromScript(script) {
13416
14042
  }
13417
14043
  async function resolveEntry(serviceDir, pkg) {
13418
14044
  if (typeof pkg.main === "string" && pkg.main.length > 0) {
13419
- const candidate = import_node_path56.default.resolve(serviceDir, pkg.main);
14045
+ const candidate = import_node_path57.default.resolve(serviceDir, pkg.main);
13420
14046
  if (await exists3(candidate)) return candidate;
13421
14047
  }
13422
14048
  if (pkg.bin) {
@@ -13430,40 +14056,40 @@ async function resolveEntry(serviceDir, pkg) {
13430
14056
  if (typeof first === "string") binEntry = first;
13431
14057
  }
13432
14058
  if (binEntry) {
13433
- const candidate = import_node_path56.default.resolve(serviceDir, binEntry);
14059
+ const candidate = import_node_path57.default.resolve(serviceDir, binEntry);
13434
14060
  if (await exists3(candidate)) return candidate;
13435
14061
  }
13436
14062
  }
13437
14063
  const startEntry = entryFromScript(pkg.scripts?.start);
13438
14064
  if (startEntry) {
13439
- const candidate = import_node_path56.default.resolve(serviceDir, startEntry);
14065
+ const candidate = import_node_path57.default.resolve(serviceDir, startEntry);
13440
14066
  if (await exists3(candidate)) return candidate;
13441
14067
  }
13442
14068
  const devEntry = entryFromScript(pkg.scripts?.dev);
13443
14069
  if (devEntry) {
13444
- const candidate = import_node_path56.default.resolve(serviceDir, devEntry);
14070
+ const candidate = import_node_path57.default.resolve(serviceDir, devEntry);
13445
14071
  if (await exists3(candidate)) return candidate;
13446
14072
  }
13447
14073
  for (const rel of SRC_INDEX_CANDIDATES) {
13448
- const candidate = import_node_path56.default.join(serviceDir, rel);
14074
+ const candidate = import_node_path57.default.join(serviceDir, rel);
13449
14075
  if (await exists3(candidate)) return candidate;
13450
14076
  }
13451
14077
  for (const rel of SRC_NAMED_CANDIDATES) {
13452
- const candidate = import_node_path56.default.join(serviceDir, rel);
14078
+ const candidate = import_node_path57.default.join(serviceDir, rel);
13453
14079
  if (await exists3(candidate)) return candidate;
13454
14080
  }
13455
14081
  for (const rel of ROOT_NAMED_CANDIDATES) {
13456
- const candidate = import_node_path56.default.join(serviceDir, rel);
14082
+ const candidate = import_node_path57.default.join(serviceDir, rel);
13457
14083
  if (await exists3(candidate)) return candidate;
13458
14084
  }
13459
14085
  for (const name of INDEX_CANDIDATES) {
13460
- const candidate = import_node_path56.default.join(serviceDir, name);
14086
+ const candidate = import_node_path57.default.join(serviceDir, name);
13461
14087
  if (await exists3(candidate)) return candidate;
13462
14088
  }
13463
14089
  return null;
13464
14090
  }
13465
14091
  function dispatchEntry(entryFile, pkg) {
13466
- const ext = import_node_path56.default.extname(entryFile).toLowerCase();
14092
+ const ext = import_node_path57.default.extname(entryFile).toLowerCase();
13467
14093
  if (ext === ".ts" || ext === ".tsx") return "ts";
13468
14094
  if (ext === ".mjs") return "esm";
13469
14095
  if (ext === ".cjs") return "cjs";
@@ -13480,9 +14106,9 @@ function otelInitContents(flavor) {
13480
14106
  return OTEL_INIT_CJS;
13481
14107
  }
13482
14108
  function injectionLine(flavor, entryFile, otelInitFile) {
13483
- let rel = import_node_path56.default.relative(import_node_path56.default.dirname(entryFile), otelInitFile);
14109
+ let rel = import_node_path57.default.relative(import_node_path57.default.dirname(entryFile), otelInitFile);
13484
14110
  if (!rel.startsWith(".")) rel = `./${rel}`;
13485
- rel = rel.split(import_node_path56.default.sep).join("/");
14111
+ rel = rel.split(import_node_path57.default.sep).join("/");
13486
14112
  if (flavor === "cjs") return `require('${rel}')`;
13487
14113
  if (flavor === "esm") return `import '${rel}'`;
13488
14114
  const tsRel = rel.replace(/\.ts$/, "");
@@ -13495,27 +14121,27 @@ function lineIsOtelInjection(line) {
13495
14121
  }
13496
14122
  async function detectsSrcLayout(serviceDir) {
13497
14123
  const [hasSrcApp, hasSrcPages, hasRootApp, hasRootPages] = await Promise.all([
13498
- exists3(import_node_path56.default.join(serviceDir, "src", "app")),
13499
- exists3(import_node_path56.default.join(serviceDir, "src", "pages")),
13500
- exists3(import_node_path56.default.join(serviceDir, "app")),
13501
- exists3(import_node_path56.default.join(serviceDir, "pages"))
14124
+ exists3(import_node_path57.default.join(serviceDir, "src", "app")),
14125
+ exists3(import_node_path57.default.join(serviceDir, "src", "pages")),
14126
+ exists3(import_node_path57.default.join(serviceDir, "app")),
14127
+ exists3(import_node_path57.default.join(serviceDir, "pages"))
13502
14128
  ]);
13503
14129
  return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
13504
14130
  }
13505
14131
  async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
13506
14132
  const useTs = await isTypeScriptProject(serviceDir);
13507
14133
  const srcLayout = await detectsSrcLayout(serviceDir);
13508
- const baseDir = srcLayout ? import_node_path56.default.join(serviceDir, "src") : serviceDir;
13509
- const instrumentationFile = import_node_path56.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
13510
- const instrumentationNodeFile = import_node_path56.default.join(
14134
+ const baseDir = srcLayout ? import_node_path57.default.join(serviceDir, "src") : serviceDir;
14135
+ const instrumentationFile = import_node_path57.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
14136
+ const instrumentationNodeFile = import_node_path57.default.join(
13511
14137
  baseDir,
13512
14138
  useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
13513
14139
  );
13514
- const instrumentationEdgeFile = import_node_path56.default.join(
14140
+ const instrumentationEdgeFile = import_node_path57.default.join(
13515
14141
  baseDir,
13516
14142
  useTs ? "instrumentation.edge.ts" : "instrumentation.edge.js"
13517
14143
  );
13518
- const envNeatFile = import_node_path56.default.join(baseDir, ".env.neat");
14144
+ const envNeatFile = import_node_path57.default.join(baseDir, ".env.neat");
13519
14145
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
13520
14146
  const dependencyEdits = [];
13521
14147
  for (const sdk of SDK_PACKAGES) {
@@ -13640,7 +14266,7 @@ function buildDependencyEdits(pkg, manifestPath) {
13640
14266
  return edits;
13641
14267
  }
13642
14268
  async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
13643
- const envNeatFile = import_node_path56.default.join(serviceDir, ".env.neat");
14269
+ const envNeatFile = import_node_path57.default.join(serviceDir, ".env.neat");
13644
14270
  if (!await exists3(envNeatFile)) {
13645
14271
  generatedFiles.push({
13646
14272
  file: envNeatFile,
@@ -13675,7 +14301,7 @@ function fileImportsOtelHook(raw, specifiers) {
13675
14301
  }
13676
14302
  async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
13677
14303
  const useTs = await isTypeScriptProject(serviceDir);
13678
- const otelServerFile = import_node_path56.default.join(
14304
+ const otelServerFile = import_node_path57.default.join(
13679
14305
  serviceDir,
13680
14306
  useTs ? "app/otel.server.ts" : "app/otel.server.js"
13681
14307
  );
@@ -13732,11 +14358,11 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
13732
14358
  }
13733
14359
  async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
13734
14360
  const useTs = await isTypeScriptProject(serviceDir);
13735
- const otelInitFile = import_node_path56.default.join(
14361
+ const otelInitFile = import_node_path57.default.join(
13736
14362
  serviceDir,
13737
14363
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
13738
14364
  );
13739
- const resolvedHooksFile = hooksFile ?? import_node_path56.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
14365
+ const resolvedHooksFile = hooksFile ?? import_node_path57.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
13740
14366
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
13741
14367
  const generatedFiles = [];
13742
14368
  const entrypointEdits = [];
@@ -13798,11 +14424,11 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
13798
14424
  }
13799
14425
  async function planNuxt(serviceDir, pkg, manifestPath, project) {
13800
14426
  const useTs = await isTypeScriptProject(serviceDir);
13801
- const otelPluginFile = import_node_path56.default.join(
14427
+ const otelPluginFile = import_node_path57.default.join(
13802
14428
  serviceDir,
13803
14429
  useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
13804
14430
  );
13805
- const otelInitFile = import_node_path56.default.join(
14431
+ const otelInitFile = import_node_path57.default.join(
13806
14432
  serviceDir,
13807
14433
  useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
13808
14434
  );
@@ -13853,19 +14479,19 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
13853
14479
  var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
13854
14480
  async function findAstroMiddleware(serviceDir) {
13855
14481
  for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
13856
- const candidate = import_node_path56.default.join(serviceDir, rel);
14482
+ const candidate = import_node_path57.default.join(serviceDir, rel);
13857
14483
  if (await exists3(candidate)) return candidate;
13858
14484
  }
13859
14485
  return null;
13860
14486
  }
13861
14487
  async function planAstro(serviceDir, pkg, manifestPath, project) {
13862
14488
  const useTs = await isTypeScriptProject(serviceDir);
13863
- const otelInitFile = import_node_path56.default.join(
14489
+ const otelInitFile = import_node_path57.default.join(
13864
14490
  serviceDir,
13865
14491
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
13866
14492
  );
13867
14493
  const existingMiddleware = await findAstroMiddleware(serviceDir);
13868
- const middlewareFile = existingMiddleware ?? import_node_path56.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
14494
+ const middlewareFile = existingMiddleware ?? import_node_path57.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
13869
14495
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
13870
14496
  const generatedFiles = [];
13871
14497
  const entrypointEdits = [];
@@ -13961,7 +14587,7 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
13961
14587
  }
13962
14588
  async function plan(serviceDir, opts) {
13963
14589
  const pkg = await readPackageJson2(serviceDir);
13964
- const manifestPath = import_node_path56.default.join(serviceDir, "package.json");
14590
+ const manifestPath = import_node_path57.default.join(serviceDir, "package.json");
13965
14591
  const project = opts?.project;
13966
14592
  const empty = {
13967
14593
  language: "javascript",
@@ -13996,8 +14622,8 @@ async function plan(serviceDir, opts) {
13996
14622
  return { ...empty, libOnly: true };
13997
14623
  }
13998
14624
  const flavor = dispatchEntry(entryFile, pkg);
13999
- const otelInitFile = import_node_path56.default.join(import_node_path56.default.dirname(entryFile), otelInitFilename(flavor));
14000
- const envNeatFile = import_node_path56.default.join(serviceDir, ".env.neat");
14625
+ const otelInitFile = import_node_path57.default.join(import_node_path57.default.dirname(entryFile), otelInitFilename(flavor));
14626
+ const envNeatFile = import_node_path57.default.join(serviceDir, ".env.neat");
14001
14627
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
14002
14628
  const dependencyEdits = [];
14003
14629
  for (const sdk of SDK_PACKAGES) {
@@ -14066,13 +14692,13 @@ async function plan(serviceDir, opts) {
14066
14692
  };
14067
14693
  }
14068
14694
  function isAllowedWritePath(serviceDir, target) {
14069
- const rel = import_node_path56.default.relative(serviceDir, target);
14695
+ const rel = import_node_path57.default.relative(serviceDir, target);
14070
14696
  if (rel.startsWith("..")) return false;
14071
- const base = import_node_path56.default.basename(target);
14697
+ const base = import_node_path57.default.basename(target);
14072
14698
  if (base === "package.json") return true;
14073
14699
  if (base === ".env.neat") return true;
14074
14700
  if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
14075
- const relPosix = rel.split(import_node_path56.default.sep).join("/");
14701
+ const relPosix = rel.split(import_node_path57.default.sep).join("/");
14076
14702
  if (/^instrumentation(?:\.(?:node|edge))?\.(?:js|cjs|mjs|ts)$/.test(base)) {
14077
14703
  if (relPosix === base) return true;
14078
14704
  if (relPosix === `src/${base}`) return true;
@@ -14089,7 +14715,7 @@ function isAllowedWritePath(serviceDir, target) {
14089
14715
  return false;
14090
14716
  }
14091
14717
  async function writeAtomic(file, contents) {
14092
- await import_node_fs37.promises.mkdir(import_node_path56.default.dirname(file), { recursive: true });
14718
+ await import_node_fs37.promises.mkdir(import_node_path57.default.dirname(file), { recursive: true });
14093
14719
  const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
14094
14720
  await import_node_fs37.promises.writeFile(tmp, contents, "utf8");
14095
14721
  await import_node_fs37.promises.rename(tmp, file);
@@ -14273,7 +14899,7 @@ async function rollback(installPlan, originals, createdFiles) {
14273
14899
  ...removed.map((f) => `removed: ${f}`),
14274
14900
  ""
14275
14901
  ];
14276
- const rollbackPath = import_node_path56.default.join(installPlan.serviceDir, "neat-rollback.patch");
14902
+ const rollbackPath = import_node_path57.default.join(installPlan.serviceDir, "neat-rollback.patch");
14277
14903
  await import_node_fs37.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
14278
14904
  }
14279
14905
  function injectInstrumentationHook(raw) {
@@ -14304,7 +14930,7 @@ var javascriptInstaller = {
14304
14930
  // src/installers/python.ts
14305
14931
  init_cjs_shims();
14306
14932
  var import_node_fs38 = require("fs");
14307
- var import_node_path57 = __toESM(require("path"), 1);
14933
+ var import_node_path58 = __toESM(require("path"), 1);
14308
14934
  var SDK_PACKAGES2 = [
14309
14935
  { name: "opentelemetry-distro", version: ">=0.49b0" },
14310
14936
  { name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
@@ -14325,7 +14951,7 @@ async function exists4(p) {
14325
14951
  async function detect2(serviceDir) {
14326
14952
  const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
14327
14953
  for (const m of markers) {
14328
- if (await exists4(import_node_path57.default.join(serviceDir, m))) return true;
14954
+ if (await exists4(import_node_path58.default.join(serviceDir, m))) return true;
14329
14955
  }
14330
14956
  return false;
14331
14957
  }
@@ -14335,7 +14961,7 @@ function reqPackageName(line) {
14335
14961
  return head.replace(/[<>=!~].*$/, "").toLowerCase();
14336
14962
  }
14337
14963
  async function planRequirementsTxtEdits(serviceDir) {
14338
- const file = import_node_path57.default.join(serviceDir, "requirements.txt");
14964
+ const file = import_node_path58.default.join(serviceDir, "requirements.txt");
14339
14965
  if (!await exists4(file)) return null;
14340
14966
  const raw = await import_node_fs38.promises.readFile(file, "utf8");
14341
14967
  const presentNames = new Set(
@@ -14345,7 +14971,7 @@ async function planRequirementsTxtEdits(serviceDir) {
14345
14971
  return { manifest: file, missing: [...missing] };
14346
14972
  }
14347
14973
  async function planProcfileEdits(serviceDir) {
14348
- const procfile = import_node_path57.default.join(serviceDir, "Procfile");
14974
+ const procfile = import_node_path58.default.join(serviceDir, "Procfile");
14349
14975
  if (!await exists4(procfile)) return [];
14350
14976
  const raw = await import_node_fs38.promises.readFile(procfile, "utf8");
14351
14977
  const edits = [];
@@ -14434,7 +15060,7 @@ async function apply2(installPlan) {
14434
15060
  if (raw === void 0) {
14435
15061
  throw new Error(`python installer: cannot read ${file} during apply`);
14436
15062
  }
14437
- const base = import_node_path57.default.basename(file);
15063
+ const base = import_node_path58.default.basename(file);
14438
15064
  if (base === "requirements.txt") {
14439
15065
  const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
14440
15066
  if (edits.length > 0) {
@@ -14473,7 +15099,7 @@ async function rollback2(installPlan, originals) {
14473
15099
  ...restored.map((f) => `restored: ${f}`),
14474
15100
  ""
14475
15101
  ];
14476
- const rollbackPath = import_node_path57.default.join(installPlan.serviceDir, "neat-rollback.patch");
15102
+ const rollbackPath = import_node_path58.default.join(installPlan.serviceDir, "neat-rollback.patch");
14477
15103
  await import_node_fs38.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
14478
15104
  }
14479
15105
  var pythonInstaller = {
@@ -14600,7 +15226,7 @@ init_cjs_shims();
14600
15226
  var import_node_fs39 = require("fs");
14601
15227
  var import_node_http = __toESM(require("http"), 1);
14602
15228
  var import_node_net = __toESM(require("net"), 1);
14603
- var import_node_path58 = __toESM(require("path"), 1);
15229
+ var import_node_path59 = __toESM(require("path"), 1);
14604
15230
  var import_node_url4 = require("url");
14605
15231
  var import_node_child_process3 = require("child_process");
14606
15232
  var import_node_readline = __toESM(require("readline"), 1);
@@ -14610,7 +15236,7 @@ async function extractAndPersist(opts) {
14610
15236
  const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
14611
15237
  resetGraph(graphKey);
14612
15238
  const graph = getGraph(graphKey);
14613
- const projectPaths = pathsForProject(graphKey, import_node_path58.default.join(opts.scanPath, "neat-out"));
15239
+ const projectPaths = pathsForProject(graphKey, import_node_path59.default.join(opts.scanPath, "neat-out"));
14614
15240
  const extraction = await extractFromDirectory(graph, opts.scanPath, {
14615
15241
  errorsPath: projectPaths.errorsPath
14616
15242
  });
@@ -14662,7 +15288,7 @@ async function applyInstallersOver(services, project, options = {}) {
14662
15288
  libOnly++;
14663
15289
  const appDeps = svc.pkg ? appFrameworkDependencies(svc.pkg) : [];
14664
15290
  if (appDeps.length > 0) {
14665
- const svcName = import_node_path58.default.basename(svc.dir);
15291
+ const svcName = import_node_path59.default.basename(svc.dir);
14666
15292
  const list = appDeps.join(", ");
14667
15293
  console.warn(
14668
15294
  `neat: runtime layer won't engage for ${svcName}: no entry point found.
@@ -14675,7 +15301,7 @@ async function applyInstallersOver(services, project, options = {}) {
14675
15301
  console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
14676
15302
  } else if (outcome.outcome === "react-native") {
14677
15303
  reactNative++;
14678
- const svcName = import_node_path58.default.basename(svc.dir);
15304
+ const svcName = import_node_path59.default.basename(svc.dir);
14679
15305
  console.log(
14680
15306
  `neat: ${svc.dir} detected as React Native / Expo
14681
15307
  The installer doesn't cover this runtime deterministically.
@@ -14686,7 +15312,7 @@ async function applyInstallersOver(services, project, options = {}) {
14686
15312
  );
14687
15313
  } else if (outcome.outcome === "bun") {
14688
15314
  bun++;
14689
- const svcName = import_node_path58.default.basename(svc.dir);
15315
+ const svcName = import_node_path59.default.basename(svc.dir);
14690
15316
  console.log(
14691
15317
  `neat: ${svc.dir} detected as Bun
14692
15318
  The installer doesn't cover this runtime deterministically.
@@ -14697,7 +15323,7 @@ async function applyInstallersOver(services, project, options = {}) {
14697
15323
  );
14698
15324
  } else if (outcome.outcome === "deno") {
14699
15325
  deno++;
14700
- const svcName = import_node_path58.default.basename(svc.dir);
15326
+ const svcName = import_node_path59.default.basename(svc.dir);
14701
15327
  console.log(
14702
15328
  `neat: ${svc.dir} detected as Deno
14703
15329
  The installer doesn't cover this runtime deterministically.
@@ -14708,7 +15334,7 @@ async function applyInstallersOver(services, project, options = {}) {
14708
15334
  );
14709
15335
  } else if (outcome.outcome === "cloudflare-workers") {
14710
15336
  cloudflareWorkers++;
14711
- const svcName = import_node_path58.default.basename(svc.dir);
15337
+ const svcName = import_node_path59.default.basename(svc.dir);
14712
15338
  console.log(
14713
15339
  `neat: ${svc.dir} detected as Cloudflare Workers
14714
15340
  The installer doesn't cover this runtime deterministically.
@@ -14719,7 +15345,7 @@ async function applyInstallersOver(services, project, options = {}) {
14719
15345
  );
14720
15346
  } else if (outcome.outcome === "electron") {
14721
15347
  electron++;
14722
- const svcName = import_node_path58.default.basename(svc.dir);
15348
+ const svcName = import_node_path59.default.basename(svc.dir);
14723
15349
  console.log(
14724
15350
  `neat: ${svc.dir} detected as Electron
14725
15351
  The installer doesn't cover this runtime deterministically.
@@ -14732,7 +15358,7 @@ async function applyInstallersOver(services, project, options = {}) {
14732
15358
  if (svc.pkg && (outcome.outcome === "instrumented" || outcome.outcome === "already-instrumented")) {
14733
15359
  const gaps = uninstrumentedLibraries(svc.pkg);
14734
15360
  if (gaps.length > 0) {
14735
- const svcName = import_node_path58.default.basename(svc.dir);
15361
+ const svcName = import_node_path59.default.basename(svc.dir);
14736
15362
  const list = gaps.join(", ");
14737
15363
  const subject = gaps.length === 1 ? "this library" : "these libraries";
14738
15364
  const aux = gaps.length === 1 ? "isn't" : "aren't";
@@ -14938,8 +15564,8 @@ async function persistedPortsFor(scanPath) {
14938
15564
  return { rest: record.ports.rest, otlp: record.ports.otlp, web: record.ports.web };
14939
15565
  }
14940
15566
  async function acquireSpawnLock(scanPath) {
14941
- const lockPath = import_node_path58.default.join(scanPath, "neat-out", "daemon.spawn.lock");
14942
- await import_node_fs39.promises.mkdir(import_node_path58.default.dirname(lockPath), { recursive: true });
15567
+ const lockPath = import_node_path59.default.join(scanPath, "neat-out", "daemon.spawn.lock");
15568
+ await import_node_fs39.promises.mkdir(import_node_path59.default.dirname(lockPath), { recursive: true });
14943
15569
  const STALE_LOCK_MS = 6e4;
14944
15570
  try {
14945
15571
  const fd = await import_node_fs39.promises.open(lockPath, "wx");
@@ -14984,13 +15610,13 @@ async function healthIsForProject(restPort, project) {
14984
15610
  return false;
14985
15611
  }
14986
15612
  function daemonLogPath(projectPath2) {
14987
- return import_node_path58.default.join(projectPath2, "neat-out", "daemon.log");
15613
+ return import_node_path59.default.join(projectPath2, "neat-out", "daemon.log");
14988
15614
  }
14989
15615
  function spawnDaemonDetached(spec) {
14990
- const here = import_node_path58.default.dirname((0, import_node_url4.fileURLToPath)(importMetaUrl));
15616
+ const here = import_node_path59.default.dirname((0, import_node_url4.fileURLToPath)(importMetaUrl));
14991
15617
  const candidates = [
14992
- import_node_path58.default.join(here, "neatd.cjs"),
14993
- import_node_path58.default.join(here, "neatd.js")
15618
+ import_node_path59.default.join(here, "neatd.cjs"),
15619
+ import_node_path59.default.join(here, "neatd.js")
14994
15620
  ];
14995
15621
  let entry2 = null;
14996
15622
  const fsSync = require("fs");
@@ -15020,7 +15646,7 @@ function spawnDaemonDetached(spec) {
15020
15646
  let logFd = null;
15021
15647
  if (spec) {
15022
15648
  const logPath = daemonLogPath(spec.projectPath);
15023
- fsSync.mkdirSync(import_node_path58.default.dirname(logPath), { recursive: true });
15649
+ fsSync.mkdirSync(import_node_path59.default.dirname(logPath), { recursive: true });
15024
15650
  logFd = fsSync.openSync(logPath, "a");
15025
15651
  }
15026
15652
  const child = (0, import_node_child_process3.spawn)(process.execPath, [entry2, "start"], {
@@ -15219,7 +15845,7 @@ async function runOrchestrator(opts) {
15219
15845
  result.steps.browser = openBrowser(dashboardUrl);
15220
15846
  }
15221
15847
  const daemonRunning = result.steps.daemon === "spawned" || result.steps.daemon === "already-running";
15222
- const daemonLog = daemonRunning ? import_node_path58.default.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
15848
+ const daemonLog = daemonRunning ? import_node_path59.default.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
15223
15849
  printSummary(result, graph, dashboardUrl, daemonLog);
15224
15850
  return result;
15225
15851
  }
@@ -15299,6 +15925,8 @@ function parseConnectorArgs(args) {
15299
15925
  positional: [],
15300
15926
  skipValidate: false,
15301
15927
  plaintext: false,
15928
+ help: false,
15929
+ json: false,
15302
15930
  fields: {}
15303
15931
  };
15304
15932
  const positional = [];
@@ -15312,6 +15940,14 @@ function parseConnectorArgs(args) {
15312
15940
  out.plaintext = true;
15313
15941
  continue;
15314
15942
  }
15943
+ if (arg === "--help" || arg === "-h") {
15944
+ out.help = true;
15945
+ continue;
15946
+ }
15947
+ if (arg === "--json") {
15948
+ out.json = true;
15949
+ continue;
15950
+ }
15315
15951
  if (arg.startsWith("--")) {
15316
15952
  let flag = arg;
15317
15953
  let value;
@@ -15388,19 +16024,20 @@ function plaintextFields(ref) {
15388
16024
  async function connectorAdd(args, deps) {
15389
16025
  let provider = args.positional[0];
15390
16026
  if (!provider && deps.interactive) {
15391
- provider = (await deps.prompt(`Provider (${Object.keys(PROVIDER_DISPATCH).sort().join(", ")}):`)).trim();
16027
+ provider = (await deps.prompt(`Provider (${knownProviderNames().join(", ")}):`)).trim();
15392
16028
  }
15393
16029
  if (!provider) {
15394
16030
  deps.err("neat connector add: a provider is required (e.g. `neat connector add supabase`)");
15395
16031
  return 2;
15396
16032
  }
15397
- const dispatch = getProviderDispatch(provider);
16033
+ const dispatch = getProviderFieldSchema(provider);
15398
16034
  if (!dispatch) {
15399
16035
  deps.err(
15400
- `neat connector add: unknown provider "${provider}". known providers: ${Object.keys(PROVIDER_DISPATCH).sort().join(", ")}`
16036
+ `neat connector add: unknown provider "${provider}". known providers: ${knownProviderNames().join(", ")}`
15401
16037
  );
15402
16038
  return 2;
15403
16039
  }
16040
+ const push = isPushProvider(provider);
15404
16041
  let project = args.project;
15405
16042
  if (project === void 0 && deps.interactive) {
15406
16043
  const answer = (await deps.prompt("Project (blank = the project the daemon is bootstrapping):")).trim();
@@ -15439,7 +16076,7 @@ async function connectorAdd(args, deps) {
15439
16076
  credential,
15440
16077
  ...Object.keys(options).length > 0 ? { options } : {}
15441
16078
  };
15442
- if (!args.skipValidate) {
16079
+ if (!push && !args.skipValidate) {
15443
16080
  const outcome = await validateConnectorEntry(entry2, deps.env, deps.fetchImpl);
15444
16081
  const code = reportPreWriteValidation(outcome, deps);
15445
16082
  if (code !== 0) return code;
@@ -15450,14 +16087,32 @@ async function connectorAdd(args, deps) {
15450
16087
  `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).`
15451
16088
  );
15452
16089
  }
16090
+ if (push) {
16091
+ if (args.skipValidate) {
16092
+ deps.out("note: --skip-validate has no effect for a push provider \u2014 provisioning the drain is the add.");
16093
+ }
16094
+ const outcome = await provisionConnector(entry2, deps.env, deps.fetchImpl);
16095
+ if (outcome.status !== "ok") {
16096
+ deps.err(
16097
+ `neat connector add: could not provision the ${provider} connector \u2014 ${outcome.reason}. Nothing was written.`
16098
+ );
16099
+ return 1;
16100
+ }
16101
+ if (outcome.options) entry2.options = { ...entry2.options ?? {}, ...outcome.options };
16102
+ if (outcome.note) deps.out(`note: ${outcome.note}`);
16103
+ }
15453
16104
  const { replaced } = await upsertConnectorEntry(entry2, deps.home);
15454
16105
  const verb = replaced ? "updated" : "added";
15455
16106
  const where = project ? `project "${project}"` : "the bootstrapping project";
15456
16107
  deps.out(`${verb} connector "${id}" (${provider}) for ${where}.`);
15457
- if (args.skipValidate) {
15458
- deps.out("skipped validation (--skip-validate) \u2014 the credential is checked at the next daemon poll.");
16108
+ if (push) {
16109
+ deps.out("The drain is live \u2014 traces arrive at the daemon's OTLP receiver. No poll or restart needed.");
16110
+ } else {
16111
+ if (args.skipValidate) {
16112
+ deps.out("skipped validation (--skip-validate) \u2014 the credential is checked at the next daemon poll.");
16113
+ }
16114
+ deps.out("Restart the project's daemon (or start it) to begin polling this connector.");
15459
16115
  }
15460
- deps.out("Restart the project's daemon (or start it) to begin polling this connector.");
15461
16116
  return 0;
15462
16117
  }
15463
16118
  function reportPreWriteValidation(outcome, deps) {
@@ -15483,10 +16138,26 @@ function reportPreWriteValidation(outcome, deps) {
15483
16138
  return 2;
15484
16139
  }
15485
16140
  }
15486
- async function connectorList(deps, filterProject) {
16141
+ async function connectorList(deps, filterProject, json = false) {
15487
16142
  const config = await readConnectorsConfig(deps.home);
15488
16143
  let entries = config.connectors;
15489
16144
  if (filterProject) entries = entries.filter((e) => e.project === filterProject);
16145
+ if (json) {
16146
+ const rows = entries.map((e) => ({
16147
+ id: e.id,
16148
+ provider: e.provider,
16149
+ project: e.project ?? null,
16150
+ kind: isPushProvider(e.provider) ? "push" : "pull",
16151
+ credential: describeCredential(e.credential, deps.env).map((c) => ({
16152
+ ...c.field ? { field: c.field } : {},
16153
+ ref: c.display,
16154
+ ...c.status ? { status: c.status } : { kind: c.kind }
16155
+ })),
16156
+ ...e.options ? { options: e.options } : {}
16157
+ }));
16158
+ deps.out(JSON.stringify(rows, null, 2));
16159
+ return 0;
16160
+ }
15490
16161
  if (entries.length === 0) {
15491
16162
  deps.out(
15492
16163
  filterProject ? `no connectors configured for project "${filterProject}".` : "no connectors configured. run `neat connector add <provider>` to add one."
@@ -15509,6 +16180,22 @@ async function connectorRemove(deps, id) {
15509
16180
  deps.err("neat connector remove: missing <id>. run `neat connector list` to see configured ids.");
15510
16181
  return 2;
15511
16182
  }
16183
+ const config = await readConnectorsConfig(deps.home);
16184
+ const entry2 = config.connectors.find((c) => c.id === id);
16185
+ if (!entry2) {
16186
+ deps.err(`neat connector remove: no connector with id "${id}". run \`neat connector list\` to see configured ids.`);
16187
+ return 1;
16188
+ }
16189
+ if (isPushProvider(entry2.provider)) {
16190
+ const outcome = await deprovisionConnector(entry2, deps.env, deps.fetchImpl);
16191
+ if (outcome.status !== "ok") {
16192
+ deps.err(
16193
+ `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).`
16194
+ );
16195
+ return 1;
16196
+ }
16197
+ if (outcome.note) deps.out(`note: ${outcome.note}`);
16198
+ }
15512
16199
  const removed = await removeConnectorEntry(id, deps.home);
15513
16200
  if (!removed) {
15514
16201
  deps.err(`neat connector remove: no connector with id "${id}". run \`neat connector list\` to see configured ids.`);
@@ -15549,13 +16236,20 @@ async function connectorTest(deps, id) {
15549
16236
  }
15550
16237
  function printConnectorUsage(write) {
15551
16238
  write("usage: neat connector <add|list|remove|test> [args]");
16239
+ write(` providers: ${knownProviderNames().join(", ")}`);
16240
+ write(" pull (polled): supabase, railway, firebase, cloudflare");
16241
+ write(" push (drains): vercel \u2014 provisions a Vercel trace Drain that forwards");
16242
+ write(" traces to the daemon's OTLP receiver; no app instrumentation");
15552
16243
  write(" add <provider> add a connector; validates the credential first (--skip-validate to skip)");
15553
16244
  write(" flags: --project <name> --credential/--token <$VAR|value> --id <id>");
15554
16245
  write(" --<option> <value> (provider-specific) --plaintext --skip-validate");
16246
+ write(" vercel: neat connector add vercel --token $VERCEL_TOKEN \\");
16247
+ write(" --otel-token $NEAT_OTEL_TOKEN --team-id <teamId> \\");
16248
+ write(" --endpoint https://<public-host>/v1/traces [--project-ids <id,id>]");
15555
16249
  write(" list list configured connectors (credentials shown redacted)");
15556
16250
  write(" flags: --project <name>");
15557
- write(" remove <id> remove a connector by id");
15558
- write(" test <id> re-run the credential validation for an existing connector");
16251
+ write(" remove <id> remove a connector by id (a push provider also has its drain deleted)");
16252
+ write(" test <id> re-run validation for an existing connector (a push provider re-tests its drain)");
15559
16253
  }
15560
16254
  async function runConnectorCommand(rawArgs, deps = {}) {
15561
16255
  const resolved = resolveDeps(deps);
@@ -15565,11 +16259,15 @@ async function runConnectorCommand(rawArgs, deps = {}) {
15565
16259
  return 2;
15566
16260
  }
15567
16261
  const a = parsed.value;
16262
+ if (a.help) {
16263
+ printConnectorUsage(resolved.out);
16264
+ return 0;
16265
+ }
15568
16266
  switch (a.subcommand) {
15569
16267
  case "add":
15570
16268
  return connectorAdd(a, resolved);
15571
16269
  case "list":
15572
- return connectorList(resolved, a.project);
16270
+ return connectorList(resolved, a.project, a.json);
15573
16271
  case "remove":
15574
16272
  return connectorRemove(resolved, a.positional[0]);
15575
16273
  case "test":
@@ -15585,13 +16283,187 @@ async function runConnectorCommand(rawArgs, deps = {}) {
15585
16283
  }
15586
16284
  }
15587
16285
 
16286
+ // src/hooks-cli.ts
16287
+ init_cjs_shims();
16288
+ var import_node_path60 = __toESM(require("path"), 1);
16289
+ var import_node_os5 = __toESM(require("os"), 1);
16290
+ var import_node_fs40 = require("fs");
16291
+ var import_node_url5 = require("url");
16292
+ var HOOK_FILENAME = "neat-search-nudge.mjs";
16293
+ var GUIDE_FILENAME = "GRAPH_FIRST.md";
16294
+ var GUIDE_INSTALL_NAME = "neat-graph-first.md";
16295
+ var HOOK_MATCHER = "Grep|Glob|Bash";
16296
+ function moduleDir() {
16297
+ return typeof __dirname !== "undefined" ? __dirname : import_node_path60.default.dirname((0, import_node_url5.fileURLToPath)(importMetaUrl));
16298
+ }
16299
+ async function readSkillAsset(rel) {
16300
+ const here = moduleDir();
16301
+ const candidates = [
16302
+ import_node_path60.default.resolve(here, "../../claude-skill", rel),
16303
+ import_node_path60.default.resolve(here, "../../../claude-skill", rel),
16304
+ import_node_path60.default.resolve(here, "../claude-skill", rel)
16305
+ ];
16306
+ for (const candidate of candidates) {
16307
+ try {
16308
+ return await import_node_fs40.promises.readFile(candidate, "utf8");
16309
+ } catch {
16310
+ }
16311
+ }
16312
+ throw new Error(
16313
+ `neat hooks: could not find @neat.is/claude-skill/${rel} \u2014 is the package installed?`
16314
+ );
16315
+ }
16316
+ function neatHome3() {
16317
+ const override = process.env.NEAT_HOME;
16318
+ if (override && override.length > 0) return import_node_path60.default.resolve(override);
16319
+ return import_node_path60.default.join(import_node_os5.default.homedir(), ".neat");
16320
+ }
16321
+ function claudeSettingsPath() {
16322
+ const override = process.env.NEAT_CLAUDE_SETTINGS;
16323
+ if (override && override.length > 0) return import_node_path60.default.resolve(override);
16324
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os5.default.homedir();
16325
+ return import_node_path60.default.join(home, ".claude", "settings.json");
16326
+ }
16327
+ function installedHookPath() {
16328
+ return import_node_path60.default.join(neatHome3(), "hooks", HOOK_FILENAME);
16329
+ }
16330
+ function isNeatSearchEntry(entry2) {
16331
+ return (entry2.hooks ?? []).some(
16332
+ (h) => typeof h.command === "string" && h.command.includes(HOOK_FILENAME)
16333
+ );
16334
+ }
16335
+ function neatHookEntry(command) {
16336
+ return { matcher: HOOK_MATCHER, hooks: [{ type: "command", command }] };
16337
+ }
16338
+ function hookCommand(scriptPath) {
16339
+ return `node "${scriptPath}"`;
16340
+ }
16341
+ async function runHooks(opts) {
16342
+ if (opts.printHook) {
16343
+ process.stdout.write(await readSkillAsset(`hooks/${HOOK_FILENAME}`));
16344
+ return { exitCode: 0 };
16345
+ }
16346
+ if (opts.printGuide) {
16347
+ process.stdout.write(await readSkillAsset(GUIDE_FILENAME));
16348
+ return { exitCode: 0 };
16349
+ }
16350
+ if (opts.printSettings) {
16351
+ const block = {
16352
+ hooks: { PreToolUse: [neatHookEntry(hookCommand(installedHookPath()))] }
16353
+ };
16354
+ process.stdout.write(JSON.stringify(block, null, 2) + "\n");
16355
+ return { exitCode: 0 };
16356
+ }
16357
+ if (opts.apply) {
16358
+ const hookScript = await readSkillAsset(`hooks/${HOOK_FILENAME}`);
16359
+ const guide = await readSkillAsset(GUIDE_FILENAME);
16360
+ const scriptPath = installedHookPath();
16361
+ await import_node_fs40.promises.mkdir(import_node_path60.default.dirname(scriptPath), { recursive: true });
16362
+ await import_node_fs40.promises.writeFile(scriptPath, hookScript, { mode: 493 });
16363
+ const guidePath = import_node_path60.default.join(neatHome3(), GUIDE_INSTALL_NAME);
16364
+ await import_node_fs40.promises.writeFile(guidePath, guide, "utf8");
16365
+ const settingsFile = claudeSettingsPath();
16366
+ let settings = {};
16367
+ try {
16368
+ settings = JSON.parse(await import_node_fs40.promises.readFile(settingsFile, "utf8"));
16369
+ } catch (err) {
16370
+ if (err.code !== "ENOENT") {
16371
+ console.error(
16372
+ `neat hooks: failed to read ${settingsFile} \u2014 ${err.message}`
16373
+ );
16374
+ return { exitCode: 1 };
16375
+ }
16376
+ }
16377
+ const hooks = settings.hooks ?? {};
16378
+ const preToolUse = Array.isArray(hooks.PreToolUse) ? [...hooks.PreToolUse] : [];
16379
+ const command = hookCommand(scriptPath);
16380
+ const existingIdx = preToolUse.findIndex(isNeatSearchEntry);
16381
+ if (existingIdx >= 0) {
16382
+ preToolUse[existingIdx] = neatHookEntry(command);
16383
+ } else {
16384
+ preToolUse.push(neatHookEntry(command));
16385
+ }
16386
+ const merged = {
16387
+ ...settings,
16388
+ hooks: { ...hooks, PreToolUse: preToolUse }
16389
+ };
16390
+ await import_node_fs40.promises.mkdir(import_node_path60.default.dirname(settingsFile), { recursive: true });
16391
+ await import_node_fs40.promises.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
16392
+ console.log(`neat hooks: installed the search-nudge hook`);
16393
+ console.log(` script: ${scriptPath}`);
16394
+ console.log(` settings: ${settingsFile} (PreToolUse \u2192 ${HOOK_MATCHER})`);
16395
+ console.log(` guidance: ${guidePath}`);
16396
+ console.log("");
16397
+ console.log("restart Claude Code to load the hook. On a Grep/Glob or a Bash grep,");
16398
+ console.log("your agent will now be nudged to query NEAT first.");
16399
+ console.log("");
16400
+ console.log("The hook is Claude-Code-specific. For agents on other harnesses, paste");
16401
+ console.log(`the guidance above into your project instructions (CLAUDE.md / AGENTS.md).`);
16402
+ return { exitCode: 0 };
16403
+ }
16404
+ usage();
16405
+ return { exitCode: 0 };
16406
+ }
16407
+ function usage() {
16408
+ console.log("neat hooks \u2014 wire NEAT into your agent so it queries the graph before grepping");
16409
+ console.log("");
16410
+ console.log(" --apply install the Claude Code search-nudge hook and write the");
16411
+ console.log(" graph-first guidance to ~/.neat/, merging into");
16412
+ console.log(" ~/.claude/settings.json without touching your other hooks");
16413
+ console.log(" --print-hook print the hook script to stdout");
16414
+ console.log(" --print-guide print the agent-agnostic graph-first guidance to stdout");
16415
+ console.log(" --print-settings print the settings.json PreToolUse block --apply would add");
16416
+ console.log("");
16417
+ console.log("The hook is a gentle, non-blocking nudge \u2014 searches still run. It is");
16418
+ console.log("Claude-Code-specific; other harnesses get the same steer from the guidance.");
16419
+ }
16420
+ async function runHooksCommand(args) {
16421
+ const opts = {
16422
+ apply: false,
16423
+ printHook: false,
16424
+ printGuide: false,
16425
+ printSettings: false
16426
+ };
16427
+ for (const arg of args) {
16428
+ switch (arg) {
16429
+ case "--apply":
16430
+ opts.apply = true;
16431
+ break;
16432
+ case "--print-hook":
16433
+ opts.printHook = true;
16434
+ break;
16435
+ case "--print-guide":
16436
+ opts.printGuide = true;
16437
+ break;
16438
+ case "--print-settings":
16439
+ opts.printSettings = true;
16440
+ break;
16441
+ case "-h":
16442
+ case "--help":
16443
+ usage();
16444
+ return 0;
16445
+ default:
16446
+ console.error(`neat hooks: unknown flag "${arg}"`);
16447
+ usage();
16448
+ return 2;
16449
+ }
16450
+ }
16451
+ try {
16452
+ const { exitCode } = await runHooks(opts);
16453
+ return exitCode;
16454
+ } catch (err) {
16455
+ console.error(err.message);
16456
+ return 1;
16457
+ }
16458
+ }
16459
+
15588
16460
  // src/cli-verbs.ts
15589
16461
  init_cjs_shims();
15590
- var import_node_path59 = __toESM(require("path"), 1);
16462
+ var import_node_path61 = __toESM(require("path"), 1);
15591
16463
 
15592
16464
  // src/cli-client.ts
15593
16465
  init_cjs_shims();
15594
- var import_types48 = require("@neat.is/types");
16466
+ var import_types49 = require("@neat.is/types");
15595
16467
  var HttpError = class extends Error {
15596
16468
  constructor(status2, message, responseBody = "") {
15597
16469
  super(message);
@@ -15616,10 +16488,10 @@ function createHttpClient(baseUrl, bearerToken) {
15616
16488
  const root = baseUrl.replace(/\/$/, "");
15617
16489
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
15618
16490
  return {
15619
- async get(path61) {
16491
+ async get(path63) {
15620
16492
  let res;
15621
16493
  try {
15622
- res = await fetch(`${root}${path61}`, {
16494
+ res = await fetch(`${root}${path63}`, {
15623
16495
  headers: { ...authHeader }
15624
16496
  });
15625
16497
  } catch (err) {
@@ -15631,16 +16503,16 @@ function createHttpClient(baseUrl, bearerToken) {
15631
16503
  const body = await res.text().catch(() => "");
15632
16504
  throw new HttpError(
15633
16505
  res.status,
15634
- `${res.status} ${res.statusText} on GET ${path61}: ${body}`,
16506
+ `${res.status} ${res.statusText} on GET ${path63}: ${body}`,
15635
16507
  body
15636
16508
  );
15637
16509
  }
15638
16510
  return await res.json();
15639
16511
  },
15640
- async post(path61, body) {
16512
+ async post(path63, body) {
15641
16513
  let res;
15642
16514
  try {
15643
- res = await fetch(`${root}${path61}`, {
16515
+ res = await fetch(`${root}${path63}`, {
15644
16516
  method: "POST",
15645
16517
  headers: { "content-type": "application/json", ...authHeader },
15646
16518
  body: JSON.stringify(body)
@@ -15654,7 +16526,7 @@ function createHttpClient(baseUrl, bearerToken) {
15654
16526
  const text = await res.text().catch(() => "");
15655
16527
  throw new HttpError(
15656
16528
  res.status,
15657
- `${res.status} ${res.statusText} on POST ${path61}: ${text}`,
16529
+ `${res.status} ${res.statusText} on POST ${path63}: ${text}`,
15658
16530
  text
15659
16531
  );
15660
16532
  }
@@ -15668,12 +16540,12 @@ function projectPath(project, suffix) {
15668
16540
  }
15669
16541
  async function runRootCause(client, input) {
15670
16542
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
15671
- const path61 = projectPath(
16543
+ const path63 = projectPath(
15672
16544
  input.project,
15673
16545
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
15674
16546
  );
15675
16547
  try {
15676
- const result = await client.get(path61);
16548
+ const result = await client.get(path63);
15677
16549
  const arrowPath = result.traversalPath.join(" \u2190 ");
15678
16550
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
15679
16551
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -15699,12 +16571,12 @@ async function runRootCause(client, input) {
15699
16571
  }
15700
16572
  async function runBlastRadius(client, input) {
15701
16573
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
15702
- const path61 = projectPath(
16574
+ const path63 = projectPath(
15703
16575
  input.project,
15704
16576
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
15705
16577
  );
15706
16578
  try {
15707
- const result = await client.get(path61);
16579
+ const result = await client.get(path63);
15708
16580
  if (result.totalAffected === 0) {
15709
16581
  return {
15710
16582
  summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
@@ -15733,17 +16605,17 @@ async function runBlastRadius(client, input) {
15733
16605
  }
15734
16606
  }
15735
16607
  function formatBlastEntry(n) {
15736
- const tag = n.edgeProvenance === import_types48.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
16608
+ const tag = n.edgeProvenance === import_types49.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
15737
16609
  return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
15738
16610
  }
15739
16611
  async function runDependencies(client, input) {
15740
16612
  const depth = input.depth ?? 3;
15741
- const path61 = projectPath(
16613
+ const path63 = projectPath(
15742
16614
  input.project,
15743
16615
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
15744
16616
  );
15745
16617
  try {
15746
- const result = await client.get(path61);
16618
+ const result = await client.get(path63);
15747
16619
  if (result.total === 0) {
15748
16620
  return {
15749
16621
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -15790,7 +16662,7 @@ async function runObservedDependencies(client, input) {
15790
16662
  if (result.observed) {
15791
16663
  return {
15792
16664
  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.`,
15793
- provenance: import_types48.Provenance.OBSERVED
16665
+ provenance: import_types49.Provenance.OBSERVED
15794
16666
  };
15795
16667
  }
15796
16668
  const note = result.hasExtractedOutbound ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
@@ -15800,7 +16672,7 @@ async function runObservedDependencies(client, input) {
15800
16672
  return {
15801
16673
  summary: `${input.nodeId} has ${result.dependencies.length} runtime dependenc${result.dependencies.length === 1 ? "y" : "ies"} confirmed by OTel.`,
15802
16674
  block: blockLines.join("\n"),
15803
- provenance: import_types48.Provenance.OBSERVED
16675
+ provenance: import_types49.Provenance.OBSERVED
15804
16676
  };
15805
16677
  } catch (err) {
15806
16678
  if (err instanceof HttpError && err.status === 404) {
@@ -15835,9 +16707,9 @@ function formatDuration(ms) {
15835
16707
  return `${Math.round(h / 24)}d`;
15836
16708
  }
15837
16709
  async function runIncidents(client, input) {
15838
- const path61 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
16710
+ const path63 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
15839
16711
  try {
15840
- const body = await client.get(path61);
16712
+ const body = await client.get(path63);
15841
16713
  const events = body.events;
15842
16714
  if (events.length === 0) {
15843
16715
  return {
@@ -15854,7 +16726,7 @@ async function runIncidents(client, input) {
15854
16726
  return {
15855
16727
  summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
15856
16728
  block: blockLines.join("\n"),
15857
- provenance: import_types48.Provenance.OBSERVED
16729
+ provenance: import_types49.Provenance.OBSERVED
15858
16730
  };
15859
16731
  } catch (err) {
15860
16732
  if (err instanceof HttpError && err.status === 404) {
@@ -15963,7 +16835,7 @@ async function runStaleEdges(client, input) {
15963
16835
  return {
15964
16836
  summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
15965
16837
  block: blockLines.join("\n"),
15966
- provenance: import_types48.Provenance.STALE
16838
+ provenance: import_types49.Provenance.STALE
15967
16839
  };
15968
16840
  }
15969
16841
  async function runPolicies(client, input) {
@@ -16127,7 +16999,7 @@ async function resolveProjectEntry(opts) {
16127
16999
  const cwd = opts.cwd ?? process.cwd();
16128
17000
  const resolvedCwd = await normalizeProjectPath(cwd);
16129
17001
  for (const entry2 of entries) {
16130
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path59.default.sep}`)) {
17002
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path61.default.sep}`)) {
16131
17003
  return entry2;
16132
17004
  }
16133
17005
  }
@@ -16280,7 +17152,7 @@ async function runSync(opts) {
16280
17152
  }
16281
17153
 
16282
17154
  // src/cli.ts
16283
- var import_types49 = require("@neat.is/types");
17155
+ var import_types50 = require("@neat.is/types");
16284
17156
  function isNpxInvocation() {
16285
17157
  if (process.env.npm_command === "exec") return true;
16286
17158
  const execpath = process.env.npm_execpath ?? "";
@@ -16292,7 +17164,7 @@ function isNpxInvocation() {
16292
17164
  function commandPrefix() {
16293
17165
  return isNpxInvocation() ? "npx neat.is" : "neat";
16294
17166
  }
16295
- function usage() {
17167
+ function usage2() {
16296
17168
  const neat = commandPrefix();
16297
17169
  console.log("Installed via npx? Prefix commands with `npx neat.is`, or install once: `npm i -g neat.is`.");
16298
17170
  console.log("");
@@ -16329,6 +17201,14 @@ function usage() {
16329
17201
  console.log(" Flags:");
16330
17202
  console.log(" --print-config print the JSON snippet to stdout");
16331
17203
  console.log(" --apply merge mcpServers.neat into ~/.claude.json");
17204
+ console.log(" hooks Wire NEAT into your agent so it queries the graph before");
17205
+ console.log(" grepping. Installs a gentle Claude Code search-nudge hook and");
17206
+ console.log(" writes agent-agnostic graph-first guidance for other harnesses.");
17207
+ console.log(" Flags:");
17208
+ console.log(" --apply install the hook + guidance");
17209
+ console.log(" --print-hook print the hook script");
17210
+ console.log(" --print-guide print the graph-first guidance");
17211
+ console.log(" --print-settings print the settings.json block --apply adds");
16332
17212
  console.log(" deploy Detect the deploy substrate, generate NEAT_AUTH_TOKEN,");
16333
17213
  console.log(" emit a docker-compose / systemd / docker run artifact, and");
16334
17214
  console.log(" print the OTel env-vars block to paste into your platform.");
@@ -16341,8 +17221,9 @@ function usage() {
16341
17221
  console.log(" --dry-run run extraction in-memory; do not write");
16342
17222
  console.log(" --no-instrument skip the SDK install apply step");
16343
17223
  console.log(" --json emit the delta summary as JSON");
16344
- console.log(" connector Configure pull-based OBSERVED connectors (supabase, railway,");
16345
- console.log(" firebase, cloudflare). Subcommands:");
17224
+ console.log(" connector Configure OBSERVED connectors \u2014 pull (supabase, railway,");
17225
+ console.log(" firebase, cloudflare) and push (vercel, via a trace Drain).");
17226
+ console.log(" Subcommands:");
16346
17227
  console.log(" add <provider> add a connector; validates the credential");
16347
17228
  console.log(" against the provider first (--skip-validate to skip)");
16348
17229
  console.log(" flags: --project <name>, --credential/--token <$VAR|value>,");
@@ -16573,7 +17454,7 @@ async function buildPatchSections(services, project) {
16573
17454
  }
16574
17455
  async function runInit(opts) {
16575
17456
  const written = [];
16576
- const stat = await import_node_fs40.promises.stat(opts.scanPath).catch(() => null);
17457
+ const stat = await import_node_fs41.promises.stat(opts.scanPath).catch(() => null);
16577
17458
  if (!stat || !stat.isDirectory()) {
16578
17459
  console.error(`neat init: ${opts.scanPath} is not a directory`);
16579
17460
  return { exitCode: 2, writtenFiles: written };
@@ -16582,13 +17463,13 @@ async function runInit(opts) {
16582
17463
  printDiscoveryReport(opts, services);
16583
17464
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
16584
17465
  const patch = renderPatch(sections);
16585
- const patchPath = import_node_path60.default.join(opts.scanPath, "neat.patch");
17466
+ const patchPath = import_node_path62.default.join(opts.scanPath, "neat.patch");
16586
17467
  if (opts.dryRun) {
16587
- await import_node_fs40.promises.writeFile(patchPath, patch, "utf8");
17468
+ await import_node_fs41.promises.writeFile(patchPath, patch, "utf8");
16588
17469
  written.push(patchPath);
16589
17470
  console.log(`dry-run: patch written to ${patchPath}`);
16590
- const gitignorePath = import_node_path60.default.join(opts.scanPath, ".gitignore");
16591
- const gitignoreExists = await import_node_fs40.promises.stat(gitignorePath).then(() => true).catch(() => false);
17471
+ const gitignorePath = import_node_path62.default.join(opts.scanPath, ".gitignore");
17472
+ const gitignoreExists = await import_node_fs41.promises.stat(gitignorePath).then(() => true).catch(() => false);
16592
17473
  const verb = gitignoreExists ? "append" : "create";
16593
17474
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
16594
17475
  console.log("rerun without --dry-run to register and snapshot.");
@@ -16599,9 +17480,9 @@ async function runInit(opts) {
16599
17480
  const graph = getGraph(graphKey);
16600
17481
  const projectPaths = pathsForProject(
16601
17482
  graphKey,
16602
- import_node_path60.default.join(opts.scanPath, "neat-out")
17483
+ import_node_path62.default.join(opts.scanPath, "neat-out")
16603
17484
  );
16604
- const errorsPath = import_node_path60.default.join(import_node_path60.default.dirname(opts.outPath), import_node_path60.default.basename(projectPaths.errorsPath));
17485
+ const errorsPath = import_node_path62.default.join(import_node_path62.default.dirname(opts.outPath), import_node_path62.default.basename(projectPaths.errorsPath));
16605
17486
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
16606
17487
  await saveGraphToDisk(graph, opts.outPath);
16607
17488
  written.push(opts.outPath);
@@ -16680,7 +17561,7 @@ async function runInit(opts) {
16680
17561
  console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
16681
17562
  }
16682
17563
  } else {
16683
- await import_node_fs40.promises.writeFile(patchPath, patch, "utf8");
17564
+ await import_node_fs41.promises.writeFile(patchPath, patch, "utf8");
16684
17565
  written.push(patchPath);
16685
17566
  }
16686
17567
  }
@@ -16720,9 +17601,9 @@ var CLAUDE_SKILL_CONFIG = {
16720
17601
  };
16721
17602
  function claudeConfigPath() {
16722
17603
  const override = process.env.NEAT_CLAUDE_CONFIG;
16723
- if (override && override.length > 0) return import_node_path60.default.resolve(override);
17604
+ if (override && override.length > 0) return import_node_path62.default.resolve(override);
16724
17605
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
16725
- return import_node_path60.default.join(home, ".claude.json");
17606
+ return import_node_path62.default.join(home, ".claude.json");
16726
17607
  }
16727
17608
  async function runSkill(opts) {
16728
17609
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -16734,7 +17615,7 @@ async function runSkill(opts) {
16734
17615
  const target = claudeConfigPath();
16735
17616
  let existing = {};
16736
17617
  try {
16737
- existing = JSON.parse(await import_node_fs40.promises.readFile(target, "utf8"));
17618
+ existing = JSON.parse(await import_node_fs41.promises.readFile(target, "utf8"));
16738
17619
  } catch (err) {
16739
17620
  if (err.code !== "ENOENT") {
16740
17621
  console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
@@ -16746,10 +17627,13 @@ async function runSkill(opts) {
16746
17627
  ...existing,
16747
17628
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
16748
17629
  };
16749
- await import_node_fs40.promises.mkdir(import_node_path60.default.dirname(target), { recursive: true });
16750
- await import_node_fs40.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
17630
+ await import_node_fs41.promises.mkdir(import_node_path62.default.dirname(target), { recursive: true });
17631
+ await import_node_fs41.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
16751
17632
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
16752
17633
  console.log("restart Claude Code to pick up the new MCP server.");
17634
+ console.log("");
17635
+ console.log("Tip: run `neat hooks --apply` to also install the search-nudge hook, so");
17636
+ console.log("your agent reaches for the graph before it grep-scans the repo.");
16753
17637
  return { exitCode: 0 };
16754
17638
  }
16755
17639
  console.log("neat skill \u2014 Claude Code MCP drop-in for NEAT");
@@ -16762,13 +17646,16 @@ async function runSkill(opts) {
16762
17646
  console.log("");
16763
17647
  console.log("The MCP server reads NEAT_CORE_URL for the daemon URL \u2014 point it at a");
16764
17648
  console.log("non-default daemon by editing that value in the generated config.");
17649
+ console.log("");
17650
+ console.log("See also `neat hooks --apply` \u2014 the search-nudge hook + graph-first guidance");
17651
+ console.log("that steer an agent to query the graph before falling back to text search.");
16765
17652
  return { exitCode: 0 };
16766
17653
  }
16767
17654
  async function main() {
16768
17655
  const argv = process.argv.slice(2);
16769
17656
  const cmd0 = argv[0];
16770
17657
  if (cmd0 === "-h" || cmd0 === "--help") {
16771
- usage();
17658
+ usage2();
16772
17659
  process.exit(0);
16773
17660
  }
16774
17661
  if (cmd0 === "--version" || cmd0 === "-v" || cmd0 === "version") {
@@ -16780,6 +17667,11 @@ async function main() {
16780
17667
  if (code !== 0) process.exit(code);
16781
17668
  return;
16782
17669
  }
17670
+ if (cmd0 === "hooks") {
17671
+ const code = await runHooksCommand(argv.slice(1));
17672
+ if (code !== 0) process.exit(code);
17673
+ return;
17674
+ }
16783
17675
  const argvParsed = parseArgs(argv);
16784
17676
  if (argvParsed.positional.length === 0) {
16785
17677
  const orchestratorCode2 = await tryOrchestrator(process.cwd(), argvParsed);
@@ -16794,19 +17686,19 @@ async function main() {
16794
17686
  const target = positional[0];
16795
17687
  if (!target) {
16796
17688
  console.error("neat init: missing <path>");
16797
- usage();
17689
+ usage2();
16798
17690
  process.exit(2);
16799
17691
  }
16800
17692
  if (apply3 && dryRun) {
16801
17693
  console.error("neat init: --apply and --dry-run are mutually exclusive");
16802
17694
  process.exit(2);
16803
17695
  }
16804
- const scanPath = import_node_path60.default.resolve(target);
17696
+ const scanPath = import_node_path62.default.resolve(target);
16805
17697
  const projectExplicit = parsed.project !== null;
16806
- const projectName = projectExplicit ? project : import_node_path60.default.basename(scanPath);
17698
+ const projectName = projectExplicit ? project : import_node_path62.default.basename(scanPath);
16807
17699
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
16808
- const fallback = pathsForProject(projectKey, import_node_path60.default.join(scanPath, "neat-out")).snapshotPath;
16809
- const outPath = import_node_path60.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
17700
+ const fallback = pathsForProject(projectKey, import_node_path62.default.join(scanPath, "neat-out")).snapshotPath;
17701
+ const outPath = import_node_path62.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
16810
17702
  const result = await runInit({
16811
17703
  scanPath,
16812
17704
  outPath,
@@ -16824,24 +17716,24 @@ async function main() {
16824
17716
  const target = positional[0];
16825
17717
  if (!target) {
16826
17718
  console.error("neat watch: missing <path>");
16827
- usage();
17719
+ usage2();
16828
17720
  process.exit(2);
16829
17721
  }
16830
- const scanPath = import_node_path60.default.resolve(target);
16831
- const stat = await import_node_fs40.promises.stat(scanPath).catch(() => null);
17722
+ const scanPath = import_node_path62.default.resolve(target);
17723
+ const stat = await import_node_fs41.promises.stat(scanPath).catch(() => null);
16832
17724
  if (!stat || !stat.isDirectory()) {
16833
17725
  console.error(`neat watch: ${scanPath} is not a directory`);
16834
17726
  process.exit(2);
16835
17727
  }
16836
- const projectPaths = pathsForProject(project, import_node_path60.default.join(scanPath, "neat-out"));
16837
- const outPath = import_node_path60.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
16838
- const errorsPath = import_node_path60.default.resolve(
16839
- process.env.NEAT_ERRORS_PATH ?? import_node_path60.default.join(import_node_path60.default.dirname(outPath), import_node_path60.default.basename(projectPaths.errorsPath))
17728
+ const projectPaths = pathsForProject(project, import_node_path62.default.join(scanPath, "neat-out"));
17729
+ const outPath = import_node_path62.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
17730
+ const errorsPath = import_node_path62.default.resolve(
17731
+ process.env.NEAT_ERRORS_PATH ?? import_node_path62.default.join(import_node_path62.default.dirname(outPath), import_node_path62.default.basename(projectPaths.errorsPath))
16840
17732
  );
16841
- const staleEventsPath = import_node_path60.default.resolve(
16842
- process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path60.default.join(import_node_path60.default.dirname(outPath), import_node_path60.default.basename(projectPaths.staleEventsPath))
17733
+ const staleEventsPath = import_node_path62.default.resolve(
17734
+ process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path62.default.join(import_node_path62.default.dirname(outPath), import_node_path62.default.basename(projectPaths.staleEventsPath))
16843
17735
  );
16844
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path60.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
17736
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path62.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
16845
17737
  const handle = await startWatch(getGraph(project), {
16846
17738
  scanPath,
16847
17739
  outPath,
@@ -16883,7 +17775,7 @@ async function main() {
16883
17775
  const name = positional[0];
16884
17776
  if (!name) {
16885
17777
  console.error("neat pause: missing <name>");
16886
- usage();
17778
+ usage2();
16887
17779
  process.exit(2);
16888
17780
  }
16889
17781
  const daemon = await findDaemonByProject(name);
@@ -16908,7 +17800,7 @@ async function main() {
16908
17800
  const name = positional[0];
16909
17801
  if (!name) {
16910
17802
  console.error("neat resume: missing <name>");
16911
- usage();
17803
+ usage2();
16912
17804
  process.exit(2);
16913
17805
  }
16914
17806
  const daemon = await findDaemonByProject(name);
@@ -16938,7 +17830,7 @@ async function main() {
16938
17830
  const name = positional[0];
16939
17831
  if (!name) {
16940
17832
  console.error("neat uninstall: missing <name>");
16941
- usage();
17833
+ usage2();
16942
17834
  process.exit(2);
16943
17835
  }
16944
17836
  const daemon = await findDaemonByProject(name);
@@ -17020,15 +17912,15 @@ async function main() {
17020
17912
  return;
17021
17913
  }
17022
17914
  console.error(`neat: unknown command "${cmd}"`);
17023
- usage();
17915
+ usage2();
17024
17916
  process.exit(1);
17025
17917
  }
17026
17918
  async function tryOrchestrator(cmd, parsed) {
17027
- const scanPath = import_node_path60.default.resolve(cmd);
17028
- const stat = await import_node_fs40.promises.stat(scanPath).catch(() => null);
17919
+ const scanPath = import_node_path62.default.resolve(cmd);
17920
+ const stat = await import_node_fs41.promises.stat(scanPath).catch(() => null);
17029
17921
  if (!stat || !stat.isDirectory()) return null;
17030
17922
  const projectExplicit = parsed.project !== null;
17031
- const projectName = projectExplicit ? parsed.project : import_node_path60.default.basename(scanPath);
17923
+ const projectName = projectExplicit ? parsed.project : import_node_path62.default.basename(scanPath);
17032
17924
  const result = await runOrchestrator({
17033
17925
  scanPath,
17034
17926
  project: projectName,
@@ -17217,10 +18109,10 @@ async function runQueryVerb(cmd, parsed) {
17217
18109
  const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
17218
18110
  const out = [];
17219
18111
  for (const p of parts) {
17220
- const r = import_types49.DivergenceTypeSchema.safeParse(p);
18112
+ const r = import_types50.DivergenceTypeSchema.safeParse(p);
17221
18113
  if (!r.success) {
17222
18114
  console.error(
17223
- `neat divergences: unknown --type "${p}". allowed: ${import_types49.DivergenceTypeSchema.options.join(", ")}`
18115
+ `neat divergences: unknown --type "${p}". allowed: ${import_types50.DivergenceTypeSchema.options.join(", ")}`
17224
18116
  );
17225
18117
  return 2;
17226
18118
  }