@neat.is/core 0.5.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.cjs CHANGED
@@ -59,8 +59,8 @@ function mountBearerAuth(app, opts) {
59
59
  ]);
60
60
  const publicRead = opts.publicRead === true;
61
61
  app.addHook("preHandler", (req, reply, done) => {
62
- const path51 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
63
- if (exactUnauthPaths.has(path51) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path51)) {
62
+ const path52 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
63
+ if (exactUnauthPaths.has(path52) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path52)) {
64
64
  done();
65
65
  return;
66
66
  }
@@ -192,8 +192,8 @@ function reshapeGrpcRequest(req) {
192
192
  };
193
193
  }
194
194
  function resolveProtoRoot() {
195
- const here = import_node_path47.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
196
- return import_node_path47.default.resolve(here, "..", "proto");
195
+ const here = import_node_path48.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
196
+ return import_node_path48.default.resolve(here, "..", "proto");
197
197
  }
198
198
  function loadTraceService() {
199
199
  const protoRoot = resolveProtoRoot();
@@ -261,13 +261,13 @@ async function startOtelGrpcReceiver(opts) {
261
261
  })
262
262
  };
263
263
  }
264
- var import_node_url, import_node_path47, import_node_crypto2, grpc, protoLoader;
264
+ var import_node_url, import_node_path48, import_node_crypto2, grpc, protoLoader;
265
265
  var init_otel_grpc = __esm({
266
266
  "src/otel-grpc.ts"() {
267
267
  "use strict";
268
268
  init_cjs_shims();
269
269
  import_node_url = require("url");
270
- import_node_path47 = __toESM(require("path"), 1);
270
+ import_node_path48 = __toESM(require("path"), 1);
271
271
  import_node_crypto2 = require("crypto");
272
272
  grpc = __toESM(require("@grpc/grpc-js"), 1);
273
273
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
@@ -340,6 +340,11 @@ function pickEnv(spanAttrs, resourceAttrs) {
340
340
  }
341
341
  return ENV_FALLBACK;
342
342
  }
343
+ function normalizeDbSystem(attrs) {
344
+ const raw = attrs["db.system"];
345
+ if (typeof raw !== "string") return void 0;
346
+ return raw === "mongoose" ? "mongodb" : raw;
347
+ }
343
348
  function messagingDestinationOf(attrs) {
344
349
  for (const key of ["messaging.destination.name", "messaging.destination"]) {
345
350
  const v = attrs[key];
@@ -360,8 +365,8 @@ function websocketChannelPathOf(attrs) {
360
365
  const v = attrs[key];
361
366
  if (typeof v === "string" && v.length > 0) {
362
367
  const q = v.indexOf("?");
363
- const path51 = q === -1 ? v : v.slice(0, q);
364
- if (path51.length > 0) return path51;
368
+ const path52 = q === -1 ? v : v.slice(0, q);
369
+ if (path52.length > 0) return path52;
365
370
  }
366
371
  }
367
372
  return void 0;
@@ -394,8 +399,9 @@ function parseOtlpRequest(body) {
394
399
  durationNanos: durationNanos(span.startTimeUnixNano, span.endTimeUnixNano),
395
400
  env: pickEnv(attrs, resourceAttrs),
396
401
  attributes: attrs,
397
- dbSystem: typeof attrs["db.system"] === "string" ? attrs["db.system"] : void 0,
402
+ dbSystem: normalizeDbSystem(attrs),
398
403
  dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
404
+ dbCollection: typeof attrs["db.collection.name"] === "string" ? attrs["db.collection.name"] : typeof attrs["db.mongodb.collection"] === "string" ? attrs["db.mongodb.collection"] : void 0,
399
405
  messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
400
406
  messagingDestination: messagingDestinationOf(attrs),
401
407
  graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
@@ -415,10 +421,10 @@ function parseOtlpRequest(body) {
415
421
  return out;
416
422
  }
417
423
  function loadProtoRoot() {
418
- const here = import_node_path48.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
419
- const protoRoot = import_node_path48.default.resolve(here, "..", "proto");
424
+ const here = import_node_path49.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
425
+ const protoRoot = import_node_path49.default.resolve(here, "..", "proto");
420
426
  const root = new import_protobufjs.default.Root();
421
- root.resolvePath = (_origin, target) => import_node_path48.default.resolve(protoRoot, target);
427
+ root.resolvePath = (_origin, target) => import_node_path49.default.resolve(protoRoot, target);
422
428
  root.loadSync(
423
429
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
424
430
  { keepCase: true }
@@ -630,12 +636,12 @@ async function buildOtelReceiver(opts) {
630
636
  };
631
637
  return decorated;
632
638
  }
633
- var import_node_path48, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
639
+ var import_node_path49, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
634
640
  var init_otel = __esm({
635
641
  "src/otel.ts"() {
636
642
  "use strict";
637
643
  init_cjs_shims();
638
- import_node_path48 = __toESM(require("path"), 1);
644
+ import_node_path49 = __toESM(require("path"), 1);
639
645
  import_node_url2 = require("url");
640
646
  import_fastify2 = __toESM(require("fastify"), 1);
641
647
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -651,7 +657,7 @@ var init_otel = __esm({
651
657
 
652
658
  // src/server.ts
653
659
  init_cjs_shims();
654
- var import_node_path50 = __toESM(require("path"), 1);
660
+ var import_node_path51 = __toESM(require("path"), 1);
655
661
 
656
662
  // src/graph.ts
657
663
  init_cjs_shims();
@@ -675,7 +681,7 @@ function getGraph(project = DEFAULT_PROJECT) {
675
681
  init_cjs_shims();
676
682
  var import_fastify = __toESM(require("fastify"), 1);
677
683
  var import_cors = __toESM(require("@fastify/cors"), 1);
678
- var import_types33 = require("@neat.is/types");
684
+ var import_types34 = require("@neat.is/types");
679
685
 
680
686
  // src/extend/index.ts
681
687
  init_cjs_shims();
@@ -1446,19 +1452,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1446
1452
  function longestIncomingWalk(graph, start, maxDepth) {
1447
1453
  let best = { path: [start], edges: [] };
1448
1454
  const visited = /* @__PURE__ */ new Set([start]);
1449
- function step(node, path51, edges) {
1450
- if (path51.length > best.path.length) {
1451
- best = { path: [...path51], edges: [...edges] };
1455
+ function step(node, path52, edges) {
1456
+ if (path52.length > best.path.length) {
1457
+ best = { path: [...path52], edges: [...edges] };
1452
1458
  }
1453
- if (path51.length - 1 >= maxDepth) return;
1459
+ if (path52.length - 1 >= maxDepth) return;
1454
1460
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1455
1461
  for (const [srcId, edge] of incoming) {
1456
1462
  if (visited.has(srcId)) continue;
1457
1463
  visited.add(srcId);
1458
- path51.push(srcId);
1464
+ path52.push(srcId);
1459
1465
  edges.push(edge);
1460
- step(srcId, path51, edges);
1461
- path51.pop();
1466
+ step(srcId, path52, edges);
1467
+ path52.pop();
1462
1468
  edges.pop();
1463
1469
  visited.delete(srcId);
1464
1470
  }
@@ -1652,26 +1658,26 @@ function dominantFailingCall(graph, serviceId5, visited) {
1652
1658
  return best;
1653
1659
  }
1654
1660
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1655
- const path51 = [originServiceId];
1661
+ const path52 = [originServiceId];
1656
1662
  const edges = [];
1657
1663
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1658
1664
  let current = originServiceId;
1659
1665
  for (let depth = 0; depth < maxDepth; depth++) {
1660
1666
  const hop = dominantFailingCall(graph, current, visited);
1661
1667
  if (!hop) break;
1662
- path51.push(hop.nextService);
1668
+ path52.push(hop.nextService);
1663
1669
  edges.push(hop.edge);
1664
1670
  visited.add(hop.nextService);
1665
1671
  current = hop.nextService;
1666
1672
  }
1667
1673
  if (edges.length === 0) return null;
1668
- return { path: path51, edges, culprit: current };
1674
+ return { path: path52, edges, culprit: current };
1669
1675
  }
1670
1676
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1671
1677
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1672
1678
  if (!chain) return null;
1673
1679
  const culprit = chain.culprit;
1674
- const path51 = [...chain.path];
1680
+ const path52 = [...chain.path];
1675
1681
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1676
1682
  const baseConfidence = confidenceFromMix(chain.edges);
1677
1683
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1679,14 +1685,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1679
1685
  if (loc) {
1680
1686
  let rootCauseNode = culprit;
1681
1687
  if (loc.fileNode) {
1682
- path51.push(loc.fileNode);
1688
+ path52.push(loc.fileNode);
1683
1689
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1684
1690
  rootCauseNode = loc.fileNode;
1685
1691
  }
1686
1692
  return import_types.RootCauseResultSchema.parse({
1687
1693
  rootCauseNode,
1688
1694
  rootCauseReason: loc.rootCauseReason,
1689
- traversalPath: path51,
1695
+ traversalPath: path52,
1690
1696
  edgeProvenances,
1691
1697
  confidence,
1692
1698
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1698,7 +1704,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1698
1704
  return import_types.RootCauseResultSchema.parse({
1699
1705
  rootCauseNode: culprit,
1700
1706
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1701
- traversalPath: path51,
1707
+ traversalPath: path52,
1702
1708
  edgeProvenances,
1703
1709
  confidence,
1704
1710
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -3122,6 +3128,19 @@ function ensureServiceNode(graph, serviceName, env) {
3122
3128
  graph.addNode(id, node);
3123
3129
  return id;
3124
3130
  }
3131
+ function ensureInfraNode(graph, kind, name, provider) {
3132
+ const id = (0, import_types4.infraId)(kind, name);
3133
+ if (graph.hasNode(id)) return id;
3134
+ const node = {
3135
+ id,
3136
+ type: import_types4.NodeType.InfraNode,
3137
+ name,
3138
+ provider,
3139
+ kind
3140
+ };
3141
+ graph.addNode(id, node);
3142
+ return id;
3143
+ }
3125
3144
  function ensureDatabaseNode(graph, host, engine) {
3126
3145
  const id = (0, import_types4.databaseId)(host);
3127
3146
  if (graph.hasNode(id)) return id;
@@ -3434,6 +3453,18 @@ async function handleSpan(ctx, span) {
3434
3453
  callSiteEvidence
3435
3454
  );
3436
3455
  if (result) affectedNode = targetId;
3456
+ if (span.dbSystem === "mongodb" && span.dbCollection) {
3457
+ const collectionId = ensureInfraNode(ctx.graph, "mongodb-collection", span.dbCollection, "self");
3458
+ upsertObservedEdge(
3459
+ ctx.graph,
3460
+ import_types4.EdgeType.CALLS,
3461
+ observedSource(),
3462
+ collectionId,
3463
+ ts,
3464
+ isError,
3465
+ callSiteEvidence
3466
+ );
3467
+ }
3437
3468
  }
3438
3469
  } else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
3439
3470
  const targetId = ensureMessagingDestinationNode(
@@ -6281,7 +6312,7 @@ async function addGrpcMethods(graph, services) {
6281
6312
 
6282
6313
  // src/extract/calls/index.ts
6283
6314
  init_cjs_shims();
6284
- var import_types21 = require("@neat.is/types");
6315
+ var import_types22 = require("@neat.is/types");
6285
6316
 
6286
6317
  // src/extract/calls/http.ts
6287
6318
  init_cjs_shims();
@@ -6994,15 +7025,311 @@ function supabaseEndpointsFromFile(file, serviceDir) {
6994
7025
  return out;
6995
7026
  }
6996
7027
 
7028
+ // src/extract/calls/mongoose.ts
7029
+ init_cjs_shims();
7030
+ var import_node_path33 = __toESM(require("path"), 1);
7031
+ var import_types21 = require("@neat.is/types");
7032
+ var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
7033
+ var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
7034
+ var PLURALIZE_DISABLED_RE = /\bpluralize\s*\(\s*(?:null|false)\s*\)/;
7035
+ var UNCOUNTABLES = /* @__PURE__ */ new Set([
7036
+ "advice",
7037
+ "energy",
7038
+ "excretion",
7039
+ "digestion",
7040
+ "cooperation",
7041
+ "health",
7042
+ "justice",
7043
+ "labour",
7044
+ "machinery",
7045
+ "equipment",
7046
+ "information",
7047
+ "pollution",
7048
+ "sewage",
7049
+ "paper",
7050
+ "money",
7051
+ "species",
7052
+ "series",
7053
+ "rain",
7054
+ "rice",
7055
+ "fish",
7056
+ "sheep",
7057
+ "moose",
7058
+ "deer",
7059
+ "news",
7060
+ "expertise",
7061
+ "status",
7062
+ "media"
7063
+ ]);
7064
+ var PLURALIZE_RULES = [
7065
+ [/human$/gi, "humans"],
7066
+ [/(m)an$/gi, "$1en"],
7067
+ [/(pe)rson$/gi, "$1ople"],
7068
+ [/(child)$/gi, "$1ren"],
7069
+ [/^(ox)$/gi, "$1en"],
7070
+ [/(ax|test)is$/gi, "$1es"],
7071
+ [/(octop|vir)us$/gi, "$1i"],
7072
+ [/(alias|status)$/gi, "$1es"],
7073
+ [/(bu)s$/gi, "$1ses"],
7074
+ [/(buffal|tomat|potat)o$/gi, "$1oes"],
7075
+ [/([ti])um$/gi, "$1a"],
7076
+ [/sis$/gi, "ses"],
7077
+ [/(?:([^f])fe|([lr])f)$/gi, "$1$2ves"],
7078
+ [/(hive)$/gi, "$1s"],
7079
+ [/([^aeiouy]|qu)y$/gi, "$1ies"],
7080
+ [/(x|ch|ss|sh)$/gi, "$1es"],
7081
+ [/(matr|vert|ind)ix|ex$/gi, "$1ices"],
7082
+ [/([m|l])ouse$/gi, "$1ice"],
7083
+ [/(kn|w|l)ife$/gi, "$1ives"],
7084
+ [/(quiz)$/gi, "$1zes"],
7085
+ [/s$/gi, "s"],
7086
+ [/([^a-z])$/, "$1"],
7087
+ [/$/gi, "s"]
7088
+ ];
7089
+ function pluralizeCollection(name) {
7090
+ const str = name.toLowerCase();
7091
+ if (UNCOUNTABLES.has(str)) return str;
7092
+ for (const [re, repl] of PLURALIZE_RULES) {
7093
+ if (str.match(re)) return str.replace(re, repl);
7094
+ }
7095
+ return str;
7096
+ }
7097
+ var MODEL_METHODS = [
7098
+ "find",
7099
+ "findOne",
7100
+ "findById",
7101
+ "findByIdAndUpdate",
7102
+ "findByIdAndDelete",
7103
+ "findOneAndUpdate",
7104
+ "findOneAndDelete",
7105
+ "findOneAndReplace",
7106
+ "countDocuments",
7107
+ "estimatedDocumentCount",
7108
+ "distinct",
7109
+ "aggregate",
7110
+ "exists",
7111
+ "where",
7112
+ "populate",
7113
+ "watch",
7114
+ "hydrate",
7115
+ "create",
7116
+ "insertMany",
7117
+ "insertOne",
7118
+ "updateOne",
7119
+ "updateMany",
7120
+ "replaceOne",
7121
+ "deleteOne",
7122
+ "deleteMany",
7123
+ "bulkWrite",
7124
+ "bulkSave",
7125
+ "save",
7126
+ // pre-v7, removed in current mongoose but present in older codebases:
7127
+ "count",
7128
+ "update",
7129
+ "remove",
7130
+ "findOneAndRemove",
7131
+ "findByIdAndRemove"
7132
+ ];
7133
+ var MODEL_METHODS_ALT = MODEL_METHODS.join("|");
7134
+ function schemaCollectionVars(content) {
7135
+ const out = /* @__PURE__ */ new Map();
7136
+ const re = /(?:const|let|var)\s+(\w+)\s*=\s*new\s+(?:mongoose\s*\.\s*)?Schema\s*\(/g;
7137
+ let m;
7138
+ while ((m = re.exec(content)) !== null) {
7139
+ const windowText = content.slice(m.index, m.index + 2e3);
7140
+ const opt = /\bcollection\s*:\s*['"`]([\w.-]+)['"`]/.exec(windowText);
7141
+ if (opt) out.set(m[1], opt[1]);
7142
+ }
7143
+ return out;
7144
+ }
7145
+ function resolveModel(modelName, rest, schemaColl, pluralizeOn) {
7146
+ const trimmed = rest.trim();
7147
+ const literalThird = /,\s*['"`]([\w.-]+)['"`]\s*$/.exec(trimmed);
7148
+ if (literalThird) return { name: literalThird[1], kind: "mongodb-collection" };
7149
+ const firstIdent = /^([A-Za-z_$][\w$]*)/.exec(trimmed)?.[1];
7150
+ if (firstIdent && schemaColl.has(firstIdent)) {
7151
+ return { name: schemaColl.get(firstIdent), kind: "mongodb-collection" };
7152
+ }
7153
+ if (/,\s*[A-Za-z_$][\w$]*\s*$/.test(trimmed)) {
7154
+ return { name: modelName, kind: "mongodb-model" };
7155
+ }
7156
+ return { name: pluralizeOn ? pluralizeCollection(modelName) : modelName, kind: "mongodb-collection" };
7157
+ }
7158
+ function collectModelDefs(content, pluralizeOn) {
7159
+ const schemaColl = schemaCollectionVars(content);
7160
+ const out = [];
7161
+ 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;
7162
+ let m;
7163
+ while ((m = re.exec(content)) !== null) {
7164
+ out.push({
7165
+ modelName: m[2],
7166
+ varName: m[1] ?? null,
7167
+ resolved: resolveModel(m[2], m[3] ?? "", schemaColl, pluralizeOn),
7168
+ matchText: m[0]
7169
+ });
7170
+ }
7171
+ return out;
7172
+ }
7173
+ function endpoint(r, file, serviceDir, matchText) {
7174
+ const line = lineOf(file.content, matchText);
7175
+ return {
7176
+ infraId: (0, import_types21.infraId)(r.kind, r.name),
7177
+ name: r.name,
7178
+ kind: r.kind,
7179
+ edgeType: "CALLS",
7180
+ confidenceKind: "verified-call-site",
7181
+ evidence: { file: import_node_path33.default.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
7182
+ };
7183
+ }
7184
+ function mongooseEndpointsFromFile(file, serviceDir) {
7185
+ const hasMongoose = MONGOOSE_IMPORT_RE.test(file.content);
7186
+ const hasMongodb = MONGODB_IMPORT_RE.test(file.content);
7187
+ if (!hasMongoose && !hasMongodb) return [];
7188
+ const content = file.content;
7189
+ const out = [];
7190
+ const seen = /* @__PURE__ */ new Set();
7191
+ const push = (r, matchText) => {
7192
+ const key = `${r.kind}/${r.name}`;
7193
+ if (seen.has(key)) return;
7194
+ seen.add(key);
7195
+ out.push(endpoint(r, file, serviceDir, matchText));
7196
+ };
7197
+ if (hasMongoose) {
7198
+ const pluralizeOn = !PLURALIZE_DISABLED_RE.test(content);
7199
+ for (const def of collectModelDefs(content, pluralizeOn)) push(def.resolved, def.matchText);
7200
+ }
7201
+ const collRe = /\.\s*collection\s*\(\s*['"`]([\w.$-]+)['"`]\s*\)/g;
7202
+ let c;
7203
+ while ((c = collRe.exec(content)) !== null) {
7204
+ push({ name: c[1], kind: "mongodb-collection" }, c[0]);
7205
+ }
7206
+ return out;
7207
+ }
7208
+ function parseDestructure(inner, specifier, out) {
7209
+ for (const raw of inner.split(",")) {
7210
+ const part = raw.trim();
7211
+ if (!part) continue;
7212
+ const asM = /^(\w+)\s+as\s+(\w+)$/.exec(part);
7213
+ const colonM = /^(\w+)\s*:\s*(\w+)$/.exec(part);
7214
+ if (asM) out.push({ local: asM[2], kind: "named", exportName: asM[1], specifier });
7215
+ else if (colonM) out.push({ local: colonM[2], kind: "named", exportName: colonM[1], specifier });
7216
+ else {
7217
+ const id = /^(\w+)$/.exec(part);
7218
+ if (id) out.push({ local: id[1], kind: "named", exportName: id[1], specifier });
7219
+ }
7220
+ }
7221
+ }
7222
+ function parseImportBindings(content) {
7223
+ const out = [];
7224
+ const esm = /import\s+([^;'"`\n]+?)\s+from\s*['"`]([^'"`]+)['"`]/g;
7225
+ let m;
7226
+ while ((m = esm.exec(content)) !== null) {
7227
+ const clause = m[1].trim();
7228
+ const spec = m[2];
7229
+ const ns = /^\*\s+as\s+(\w+)$/.exec(clause);
7230
+ if (ns) {
7231
+ out.push({ local: ns[1], kind: "namespace", specifier: spec });
7232
+ continue;
7233
+ }
7234
+ const named = /\{([^}]*)\}/.exec(clause);
7235
+ if (named) parseDestructure(named[1], spec, out);
7236
+ const def = /^(\w+)\s*(?:,|$)/.exec(clause);
7237
+ if (def && !clause.startsWith("{")) out.push({ local: def[1], kind: "default", specifier: spec });
7238
+ }
7239
+ const cjs = /(?:const|let|var)\s+(\{[^}]*\}|\w+)\s*=\s*require\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
7240
+ while ((m = cjs.exec(content)) !== null) {
7241
+ const lhs = m[1];
7242
+ const spec = m[2];
7243
+ if (lhs.startsWith("{")) parseDestructure(lhs.slice(1, -1), spec, out);
7244
+ else out.push({ local: lhs, kind: "whole", specifier: spec });
7245
+ }
7246
+ return out;
7247
+ }
7248
+ function fileExportsOf(content, pluralizeOn) {
7249
+ const defs = collectModelDefs(content, pluralizeOn).filter((d) => d.resolved.kind === "mongodb-collection");
7250
+ if (defs.length === 0) return null;
7251
+ const byVar = /* @__PURE__ */ new Map();
7252
+ for (const d of defs) if (d.varName) byVar.set(d.varName, d.resolved.name);
7253
+ const byName = new Map(byVar);
7254
+ let def;
7255
+ const named = /(?:module\.exports|export\s+default)\s*=\s*(\w+)\b/.exec(content);
7256
+ if (named && byVar.has(named[1])) def = byVar.get(named[1]);
7257
+ if (!def) {
7258
+ const inline = /(?:module\.exports|export\s+default)\s*=\s*(?:await\s+)?(?:\w+\s*\.\s*)?model\s*\(\s*['"`]([\w$]+)['"`]/.exec(content);
7259
+ if (inline) def = pluralizeOn ? pluralizeCollection(inline[1]) : inline[1];
7260
+ }
7261
+ if (!def && byVar.size === 1) def = [...byVar.values()][0];
7262
+ return { byName, ...def ? { default: def } : {} };
7263
+ }
7264
+ async function mongooseCrossFileEndpoints(files, serviceDir) {
7265
+ const mongooseFiles = files.filter((f) => MONGOOSE_IMPORT_RE.test(f.content));
7266
+ if (mongooseFiles.length === 0) return [];
7267
+ const pluralizeOn = !files.some((f) => PLURALIZE_DISABLED_RE.test(f.content));
7268
+ const registry = /* @__PURE__ */ new Map();
7269
+ for (const f of mongooseFiles) {
7270
+ const fx = fileExportsOf(f.content, pluralizeOn);
7271
+ if (fx) registry.set(toPosix2(import_node_path33.default.relative(serviceDir, f.path)), fx);
7272
+ }
7273
+ if (registry.size === 0) return [];
7274
+ const out = [];
7275
+ const seen = /* @__PURE__ */ new Set();
7276
+ for (const f of files) {
7277
+ const bindings = parseImportBindings(f.content);
7278
+ if (bindings.length === 0) continue;
7279
+ const directColl = /* @__PURE__ */ new Map();
7280
+ const nsExports = /* @__PURE__ */ new Map();
7281
+ for (const b of bindings) {
7282
+ const resolvedRel = await resolveJsImport(b.specifier, import_node_path33.default.dirname(f.path), serviceDir, null);
7283
+ if (!resolvedRel) continue;
7284
+ const fx = registry.get(resolvedRel);
7285
+ if (!fx) continue;
7286
+ if (b.kind === "named" && b.exportName) {
7287
+ const coll = fx.byName.get(b.exportName);
7288
+ if (coll) directColl.set(b.local, coll);
7289
+ } else if (b.kind === "default") {
7290
+ if (fx.default) directColl.set(b.local, fx.default);
7291
+ } else {
7292
+ if (fx.default) directColl.set(b.local, fx.default);
7293
+ nsExports.set(b.local, fx);
7294
+ }
7295
+ }
7296
+ if (directColl.size === 0 && nsExports.size === 0) continue;
7297
+ const localDefs = new Set(
7298
+ collectModelDefs(f.content, pluralizeOn).map((d) => d.varName).filter((v) => v !== null)
7299
+ );
7300
+ const emit = (collection, matchText) => {
7301
+ const key = `${f.path}::${collection}`;
7302
+ if (seen.has(key)) return;
7303
+ seen.add(key);
7304
+ out.push(endpoint({ name: collection, kind: "mongodb-collection" }, f, serviceDir, matchText));
7305
+ };
7306
+ for (const [local, collection] of directColl) {
7307
+ if (localDefs.has(local)) continue;
7308
+ const qre = new RegExp(`\\b${local}\\s*\\.\\s*(?:${MODEL_METHODS_ALT})\\s*\\(`, "g");
7309
+ let qm;
7310
+ while ((qm = qre.exec(f.content)) !== null) emit(collection, qm[0]);
7311
+ }
7312
+ for (const [local, fx] of nsExports) {
7313
+ const qre = new RegExp(`\\b${local}\\s*\\.\\s*(\\w+)\\s*\\.\\s*(?:${MODEL_METHODS_ALT})\\s*\\(`, "g");
7314
+ let qm;
7315
+ while ((qm = qre.exec(f.content)) !== null) {
7316
+ const coll = fx.byName.get(qm[1]);
7317
+ if (coll) emit(coll, qm[0]);
7318
+ }
7319
+ }
7320
+ }
7321
+ return out;
7322
+ }
7323
+
6997
7324
  // src/extract/calls/index.ts
6998
7325
  function edgeTypeFromEndpoint(ep) {
6999
7326
  switch (ep.edgeType) {
7000
7327
  case "PUBLISHES_TO":
7001
- return import_types21.EdgeType.PUBLISHES_TO;
7328
+ return import_types22.EdgeType.PUBLISHES_TO;
7002
7329
  case "CONSUMES_FROM":
7003
- return import_types21.EdgeType.CONSUMES_FROM;
7330
+ return import_types22.EdgeType.CONSUMES_FROM;
7004
7331
  default:
7005
- return import_types21.EdgeType.CALLS;
7332
+ return import_types22.EdgeType.CALLS;
7006
7333
  }
7007
7334
  }
7008
7335
  function isAwsKind(kind) {
@@ -7014,23 +7341,27 @@ async function addExternalEndpointEdges(graph, services) {
7014
7341
  for (const service of services) {
7015
7342
  const files = await loadSourceFiles(service.dir);
7016
7343
  const endpoints = [];
7344
+ const maskedFiles = [];
7017
7345
  for (const file of files) {
7018
7346
  if (isTestPath(file.path)) continue;
7019
7347
  const masked = maskCommentsInSource(file.content);
7020
7348
  const maskedFile = { path: file.path, content: masked };
7349
+ maskedFiles.push(maskedFile);
7021
7350
  endpoints.push(...kafkaEndpointsFromFile(maskedFile, service.dir));
7022
7351
  endpoints.push(...redisEndpointsFromFile(maskedFile, service.dir));
7023
7352
  endpoints.push(...awsEndpointsFromFile(maskedFile, service.dir));
7024
7353
  endpoints.push(...grpcEndpointsFromFile(maskedFile, service.dir));
7025
7354
  endpoints.push(...supabaseEndpointsFromFile(maskedFile, service.dir));
7355
+ endpoints.push(...mongooseEndpointsFromFile(maskedFile, service.dir));
7026
7356
  }
7357
+ endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
7027
7358
  if (endpoints.length === 0) continue;
7028
7359
  const seenEdges = /* @__PURE__ */ new Set();
7029
7360
  for (const ep of endpoints) {
7030
7361
  if (!graph.hasNode(ep.infraId)) {
7031
7362
  const node = {
7032
7363
  id: ep.infraId,
7033
- type: import_types21.NodeType.InfraNode,
7364
+ type: import_types22.NodeType.InfraNode,
7034
7365
  name: ep.name,
7035
7366
  // #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
7036
7367
  // aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
@@ -7042,7 +7373,7 @@ async function addExternalEndpointEdges(graph, services) {
7042
7373
  nodesAdded++;
7043
7374
  }
7044
7375
  const edgeType = edgeTypeFromEndpoint(ep);
7045
- const confidence = (0, import_types21.confidenceForExtracted)(ep.confidenceKind);
7376
+ const confidence = (0, import_types22.confidenceForExtracted)(ep.confidenceKind);
7046
7377
  const relFile = toPosix2(ep.evidence.file);
7047
7378
  const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
7048
7379
  graph,
@@ -7052,7 +7383,7 @@ async function addExternalEndpointEdges(graph, services) {
7052
7383
  );
7053
7384
  nodesAdded += n;
7054
7385
  edgesAdded += e;
7055
- if (!(0, import_types21.passesExtractedFloor)(confidence)) {
7386
+ if (!(0, import_types22.passesExtractedFloor)(confidence)) {
7056
7387
  noteExtractedDropped({
7057
7388
  source: fileNodeId,
7058
7389
  target: ep.infraId,
@@ -7072,7 +7403,7 @@ async function addExternalEndpointEdges(graph, services) {
7072
7403
  source: fileNodeId,
7073
7404
  target: ep.infraId,
7074
7405
  type: edgeType,
7075
- provenance: import_types21.Provenance.EXTRACTED,
7406
+ provenance: import_types22.Provenance.EXTRACTED,
7076
7407
  confidence,
7077
7408
  evidence: ep.evidence
7078
7409
  };
@@ -7098,16 +7429,16 @@ init_cjs_shims();
7098
7429
 
7099
7430
  // src/extract/infra/docker-compose.ts
7100
7431
  init_cjs_shims();
7101
- var import_node_path33 = __toESM(require("path"), 1);
7102
- var import_types23 = require("@neat.is/types");
7432
+ var import_node_path34 = __toESM(require("path"), 1);
7433
+ var import_types24 = require("@neat.is/types");
7103
7434
 
7104
7435
  // src/extract/infra/shared.ts
7105
7436
  init_cjs_shims();
7106
- var import_types22 = require("@neat.is/types");
7437
+ var import_types23 = require("@neat.is/types");
7107
7438
  function makeInfraNode(kind, name, provider = "self", extras) {
7108
7439
  return {
7109
- id: (0, import_types22.infraId)(kind, name),
7110
- type: import_types22.NodeType.InfraNode,
7440
+ id: (0, import_types23.infraId)(kind, name),
7441
+ type: import_types23.NodeType.InfraNode,
7111
7442
  name,
7112
7443
  provider,
7113
7444
  kind,
@@ -7151,8 +7482,8 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
7151
7482
  source: anchorId,
7152
7483
  target: node.id,
7153
7484
  type: edgeType,
7154
- provenance: import_types22.Provenance.EXTRACTED,
7155
- confidence: (0, import_types22.confidenceForExtracted)("structural"),
7485
+ provenance: import_types23.Provenance.EXTRACTED,
7486
+ confidence: (0, import_types23.confidenceForExtracted)("structural"),
7156
7487
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
7157
7488
  };
7158
7489
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7169,7 +7500,7 @@ function dependsOnList(value) {
7169
7500
  }
7170
7501
  function serviceNameToServiceNode(name, services) {
7171
7502
  for (const s of services) {
7172
- if (s.node.name === name || import_node_path33.default.basename(s.dir) === name) return s.node.id;
7503
+ if (s.node.name === name || import_node_path34.default.basename(s.dir) === name) return s.node.id;
7173
7504
  }
7174
7505
  return null;
7175
7506
  }
@@ -7178,7 +7509,7 @@ async function addComposeInfra(graph, scanPath, services) {
7178
7509
  let edgesAdded = 0;
7179
7510
  let composePath = null;
7180
7511
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
7181
- const abs = import_node_path33.default.join(scanPath, name);
7512
+ const abs = import_node_path34.default.join(scanPath, name);
7182
7513
  if (await exists2(abs)) {
7183
7514
  composePath = abs;
7184
7515
  break;
@@ -7191,13 +7522,13 @@ async function addComposeInfra(graph, scanPath, services) {
7191
7522
  } catch (err) {
7192
7523
  recordExtractionError(
7193
7524
  "infra docker-compose",
7194
- import_node_path33.default.relative(scanPath, composePath),
7525
+ import_node_path34.default.relative(scanPath, composePath),
7195
7526
  err
7196
7527
  );
7197
7528
  return { nodesAdded, edgesAdded };
7198
7529
  }
7199
7530
  if (!compose?.services) return { nodesAdded, edgesAdded };
7200
- const evidenceFile = import_node_path33.default.relative(scanPath, composePath).split(import_node_path33.default.sep).join("/");
7531
+ const evidenceFile = import_node_path34.default.relative(scanPath, composePath).split(import_node_path34.default.sep).join("/");
7201
7532
  const composeNameToNodeId = /* @__PURE__ */ new Map();
7202
7533
  for (const [composeName, svc] of Object.entries(compose.services)) {
7203
7534
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -7219,15 +7550,15 @@ async function addComposeInfra(graph, scanPath, services) {
7219
7550
  for (const dep of dependsOnList(svc.depends_on)) {
7220
7551
  const targetId = composeNameToNodeId.get(dep);
7221
7552
  if (!targetId) continue;
7222
- const edgeId = (0, import_types5.extractedEdgeId)(sourceId, targetId, import_types23.EdgeType.DEPENDS_ON);
7553
+ const edgeId = (0, import_types5.extractedEdgeId)(sourceId, targetId, import_types24.EdgeType.DEPENDS_ON);
7223
7554
  if (graph.hasEdge(edgeId)) continue;
7224
7555
  const edge = {
7225
7556
  id: edgeId,
7226
7557
  source: sourceId,
7227
7558
  target: targetId,
7228
- type: import_types23.EdgeType.DEPENDS_ON,
7229
- provenance: import_types23.Provenance.EXTRACTED,
7230
- confidence: (0, import_types23.confidenceForExtracted)("structural"),
7559
+ type: import_types24.EdgeType.DEPENDS_ON,
7560
+ provenance: import_types24.Provenance.EXTRACTED,
7561
+ confidence: (0, import_types24.confidenceForExtracted)("structural"),
7231
7562
  evidence: { file: evidenceFile }
7232
7563
  };
7233
7564
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7239,9 +7570,9 @@ async function addComposeInfra(graph, scanPath, services) {
7239
7570
 
7240
7571
  // src/extract/infra/dockerfile.ts
7241
7572
  init_cjs_shims();
7242
- var import_node_path34 = __toESM(require("path"), 1);
7573
+ var import_node_path35 = __toESM(require("path"), 1);
7243
7574
  var import_node_fs18 = require("fs");
7244
- var import_types24 = require("@neat.is/types");
7575
+ var import_types25 = require("@neat.is/types");
7245
7576
  function readDockerfile(content) {
7246
7577
  let image = null;
7247
7578
  const ports = [];
@@ -7270,7 +7601,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7270
7601
  let nodesAdded = 0;
7271
7602
  let edgesAdded = 0;
7272
7603
  for (const service of services) {
7273
- const dockerfilePath = import_node_path34.default.join(service.dir, "Dockerfile");
7604
+ const dockerfilePath = import_node_path35.default.join(service.dir, "Dockerfile");
7274
7605
  if (!await exists2(dockerfilePath)) continue;
7275
7606
  let content;
7276
7607
  try {
@@ -7278,7 +7609,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7278
7609
  } catch (err) {
7279
7610
  recordExtractionError(
7280
7611
  "infra dockerfile",
7281
- import_node_path34.default.relative(scanPath, dockerfilePath),
7612
+ import_node_path35.default.relative(scanPath, dockerfilePath),
7282
7613
  err
7283
7614
  );
7284
7615
  continue;
@@ -7290,8 +7621,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7290
7621
  graph.addNode(node.id, node);
7291
7622
  nodesAdded++;
7292
7623
  }
7293
- const relDockerfile = toPosix2(import_node_path34.default.relative(service.dir, dockerfilePath));
7294
- const evidenceFile = toPosix2(import_node_path34.default.relative(scanPath, dockerfilePath));
7624
+ const relDockerfile = toPosix2(import_node_path35.default.relative(service.dir, dockerfilePath));
7625
+ const evidenceFile = toPosix2(import_node_path35.default.relative(scanPath, dockerfilePath));
7295
7626
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
7296
7627
  graph,
7297
7628
  service.pkg.name,
@@ -7300,15 +7631,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7300
7631
  );
7301
7632
  nodesAdded += fn;
7302
7633
  edgesAdded += fe;
7303
- const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, node.id, import_types24.EdgeType.RUNS_ON);
7634
+ const edgeId = (0, import_types5.extractedEdgeId)(fileNodeId, node.id, import_types25.EdgeType.RUNS_ON);
7304
7635
  if (!graph.hasEdge(edgeId)) {
7305
7636
  const edge = {
7306
7637
  id: edgeId,
7307
7638
  source: fileNodeId,
7308
7639
  target: node.id,
7309
- type: import_types24.EdgeType.RUNS_ON,
7310
- provenance: import_types24.Provenance.EXTRACTED,
7311
- confidence: (0, import_types24.confidenceForExtracted)("structural"),
7640
+ type: import_types25.EdgeType.RUNS_ON,
7641
+ provenance: import_types25.Provenance.EXTRACTED,
7642
+ confidence: (0, import_types25.confidenceForExtracted)("structural"),
7312
7643
  evidence: {
7313
7644
  file: evidenceFile,
7314
7645
  ...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
@@ -7323,15 +7654,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7323
7654
  graph.addNode(portNode.id, portNode);
7324
7655
  nodesAdded++;
7325
7656
  }
7326
- const portEdgeId = (0, import_types5.extractedEdgeId)(fileNodeId, portNode.id, import_types24.EdgeType.CONNECTS_TO);
7657
+ const portEdgeId = (0, import_types5.extractedEdgeId)(fileNodeId, portNode.id, import_types25.EdgeType.CONNECTS_TO);
7327
7658
  if (graph.hasEdge(portEdgeId)) continue;
7328
7659
  const portEdge = {
7329
7660
  id: portEdgeId,
7330
7661
  source: fileNodeId,
7331
7662
  target: portNode.id,
7332
- type: import_types24.EdgeType.CONNECTS_TO,
7333
- provenance: import_types24.Provenance.EXTRACTED,
7334
- confidence: (0, import_types24.confidenceForExtracted)("structural"),
7663
+ type: import_types25.EdgeType.CONNECTS_TO,
7664
+ provenance: import_types25.Provenance.EXTRACTED,
7665
+ confidence: (0, import_types25.confidenceForExtracted)("structural"),
7335
7666
  evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
7336
7667
  };
7337
7668
  graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
@@ -7344,8 +7675,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
7344
7675
  // src/extract/infra/terraform.ts
7345
7676
  init_cjs_shims();
7346
7677
  var import_node_fs19 = require("fs");
7347
- var import_node_path35 = __toESM(require("path"), 1);
7348
- var import_types25 = require("@neat.is/types");
7678
+ var import_node_path36 = __toESM(require("path"), 1);
7679
+ var import_types26 = require("@neat.is/types");
7349
7680
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
7350
7681
  var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
7351
7682
  async function walkTfFiles(start, depth = 0, max = 5) {
@@ -7355,11 +7686,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
7355
7686
  for (const entry of entries) {
7356
7687
  if (entry.isDirectory()) {
7357
7688
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
7358
- const child = import_node_path35.default.join(start, entry.name);
7689
+ const child = import_node_path36.default.join(start, entry.name);
7359
7690
  if (await isPythonVenvDir(child)) continue;
7360
7691
  out.push(...await walkTfFiles(child, depth + 1, max));
7361
7692
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
7362
- out.push(import_node_path35.default.join(start, entry.name));
7693
+ out.push(import_node_path36.default.join(start, entry.name));
7363
7694
  }
7364
7695
  }
7365
7696
  return out;
@@ -7391,7 +7722,7 @@ async function addTerraformResources(graph, scanPath) {
7391
7722
  const files = await walkTfFiles(scanPath);
7392
7723
  for (const file of files) {
7393
7724
  const content = await import_node_fs19.promises.readFile(file, "utf8");
7394
- const evidenceFile = toPosix2(import_node_path35.default.relative(scanPath, file));
7725
+ const evidenceFile = toPosix2(import_node_path36.default.relative(scanPath, file));
7395
7726
  const resources = [];
7396
7727
  const byKey = /* @__PURE__ */ new Map();
7397
7728
  RESOURCE_RE.lastIndex = 0;
@@ -7426,16 +7757,16 @@ async function addTerraformResources(graph, scanPath) {
7426
7757
  if (!target) continue;
7427
7758
  if (seen.has(target.nodeId)) continue;
7428
7759
  seen.add(target.nodeId);
7429
- const edgeId = (0, import_types5.extractedEdgeId)(resource.nodeId, target.nodeId, import_types25.EdgeType.DEPENDS_ON);
7760
+ const edgeId = (0, import_types5.extractedEdgeId)(resource.nodeId, target.nodeId, import_types26.EdgeType.DEPENDS_ON);
7430
7761
  if (graph.hasEdge(edgeId)) continue;
7431
7762
  const line = lineAt2(content, resource.bodyOffset + ref.index);
7432
7763
  const edge = {
7433
7764
  id: edgeId,
7434
7765
  source: resource.nodeId,
7435
7766
  target: target.nodeId,
7436
- type: import_types25.EdgeType.DEPENDS_ON,
7437
- provenance: import_types25.Provenance.EXTRACTED,
7438
- confidence: (0, import_types25.confidenceForExtracted)("structural"),
7767
+ type: import_types26.EdgeType.DEPENDS_ON,
7768
+ provenance: import_types26.Provenance.EXTRACTED,
7769
+ confidence: (0, import_types26.confidenceForExtracted)("structural"),
7439
7770
  evidence: { file: evidenceFile, line, snippet: key }
7440
7771
  };
7441
7772
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7449,7 +7780,7 @@ async function addTerraformResources(graph, scanPath) {
7449
7780
  // src/extract/infra/k8s.ts
7450
7781
  init_cjs_shims();
7451
7782
  var import_node_fs20 = require("fs");
7452
- var import_node_path36 = __toESM(require("path"), 1);
7783
+ var import_node_path37 = __toESM(require("path"), 1);
7453
7784
  var import_yaml3 = require("yaml");
7454
7785
  var K8S_KIND_TO_INFRA_KIND = {
7455
7786
  Service: "k8s-service",
@@ -7467,11 +7798,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
7467
7798
  for (const entry of entries) {
7468
7799
  if (entry.isDirectory()) {
7469
7800
  if (IGNORED_DIRS.has(entry.name)) continue;
7470
- const child = import_node_path36.default.join(start, entry.name);
7801
+ const child = import_node_path37.default.join(start, entry.name);
7471
7802
  if (await isPythonVenvDir(child)) continue;
7472
7803
  out.push(...await walkYamlFiles2(child, depth + 1, max));
7473
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path36.default.extname(entry.name))) {
7474
- out.push(import_node_path36.default.join(start, entry.name));
7804
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path37.default.extname(entry.name))) {
7805
+ out.push(import_node_path37.default.join(start, entry.name));
7475
7806
  }
7476
7807
  }
7477
7808
  return out;
@@ -7505,13 +7836,13 @@ async function addK8sResources(graph, scanPath) {
7505
7836
  // src/extract/infra/cloudflare.ts
7506
7837
  init_cjs_shims();
7507
7838
  var import_node_fs21 = require("fs");
7508
- var import_node_path37 = __toESM(require("path"), 1);
7839
+ var import_node_path38 = __toESM(require("path"), 1);
7509
7840
  var import_smol_toml2 = require("smol-toml");
7510
- var import_types26 = require("@neat.is/types");
7841
+ var import_types27 = require("@neat.is/types");
7511
7842
  var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
7512
7843
  async function readWranglerConfig(dir) {
7513
7844
  for (const filename of WRANGLER_FILENAMES) {
7514
- const abs = import_node_path37.default.join(dir, filename);
7845
+ const abs = import_node_path38.default.join(dir, filename);
7515
7846
  if (!await exists2(abs)) continue;
7516
7847
  const raw = await import_node_fs21.promises.readFile(abs, "utf8");
7517
7848
  const config = filename === "wrangler.toml" ? (0, import_smol_toml2.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -7555,8 +7886,8 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
7555
7886
  source: anchorId,
7556
7887
  target: node.id,
7557
7888
  type: edgeType,
7558
- provenance: import_types26.Provenance.EXTRACTED,
7559
- confidence: (0, import_types26.confidenceForExtracted)("structural"),
7889
+ provenance: import_types27.Provenance.EXTRACTED,
7890
+ confidence: (0, import_types27.confidenceForExtracted)("structural"),
7560
7891
  evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
7561
7892
  };
7562
7893
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7574,11 +7905,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7574
7905
  try {
7575
7906
  read = await readWranglerConfig(service.dir);
7576
7907
  } catch (err) {
7577
- recordExtractionError("infra cloudflare", import_node_path37.default.relative(scanPath, service.dir), err);
7908
+ recordExtractionError("infra cloudflare", import_node_path38.default.relative(scanPath, service.dir), err);
7578
7909
  continue;
7579
7910
  }
7580
7911
  if (!read || !read.config.name) continue;
7581
- const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, read.relFile)));
7912
+ const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, read.relFile)));
7582
7913
  discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
7583
7914
  }
7584
7915
  for (const worker of discovered) {
@@ -7590,7 +7921,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7590
7921
  }
7591
7922
  let anchorId = service.node.id;
7592
7923
  if (config.main) {
7593
- const entryRelPath = toPosix2(import_node_path37.default.normalize(config.main));
7924
+ const entryRelPath = toPosix2(import_node_path38.default.normalize(config.main));
7594
7925
  const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
7595
7926
  graph,
7596
7927
  service.pkg.name,
@@ -7617,15 +7948,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7617
7948
  nodesAdded++;
7618
7949
  }
7619
7950
  if (runtimeNode.id !== anchorId) {
7620
- const runsOnId = (0, import_types5.extractedEdgeId)(anchorId, runtimeNode.id, import_types26.EdgeType.RUNS_ON);
7951
+ const runsOnId = (0, import_types5.extractedEdgeId)(anchorId, runtimeNode.id, import_types27.EdgeType.RUNS_ON);
7621
7952
  if (!graph.hasEdge(runsOnId)) {
7622
7953
  const edge = {
7623
7954
  id: runsOnId,
7624
7955
  source: anchorId,
7625
7956
  target: runtimeNode.id,
7626
- type: import_types26.EdgeType.RUNS_ON,
7627
- provenance: import_types26.Provenance.EXTRACTED,
7628
- confidence: (0, import_types26.confidenceForExtracted)("structural"),
7957
+ type: import_types27.EdgeType.RUNS_ON,
7958
+ provenance: import_types27.Provenance.EXTRACTED,
7959
+ confidence: (0, import_types27.confidenceForExtracted)("structural"),
7629
7960
  evidence: {
7630
7961
  file: evidenceFile,
7631
7962
  ...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
@@ -7639,7 +7970,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7639
7970
  const result = addResourceEdge(
7640
7971
  graph,
7641
7972
  anchorId,
7642
- import_types26.EdgeType.CONNECTS_TO,
7973
+ import_types27.EdgeType.CONNECTS_TO,
7643
7974
  "cloudflare-route",
7644
7975
  route,
7645
7976
  evidenceFile,
@@ -7663,7 +7994,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7663
7994
  const result = addResourceEdge(
7664
7995
  graph,
7665
7996
  anchorId,
7666
- import_types26.EdgeType.DEPENDS_ON,
7997
+ import_types27.EdgeType.DEPENDS_ON,
7667
7998
  group.kind,
7668
7999
  name,
7669
8000
  evidenceFile,
@@ -7677,7 +8008,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7677
8008
  const result = addResourceEdge(
7678
8009
  graph,
7679
8010
  anchorId,
7680
- import_types26.EdgeType.DEPENDS_ON,
8011
+ import_types27.EdgeType.DEPENDS_ON,
7681
8012
  "cloudflare-cron",
7682
8013
  cron,
7683
8014
  evidenceFile,
@@ -7690,7 +8021,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7690
8021
  const result = addResourceEdge(
7691
8022
  graph,
7692
8023
  anchorId,
7693
- import_types26.EdgeType.DEPENDS_ON,
8024
+ import_types27.EdgeType.DEPENDS_ON,
7694
8025
  "cloudflare-env-var",
7695
8026
  varName,
7696
8027
  evidenceFile,
@@ -7703,15 +8034,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7703
8034
  if (!svc.service) continue;
7704
8035
  const target = workerIndex.get(svc.service);
7705
8036
  if (target && target.anchorId !== anchorId) {
7706
- const edgeId = (0, import_types5.extractedEdgeId)(anchorId, target.anchorId, import_types26.EdgeType.CALLS);
8037
+ const edgeId = (0, import_types5.extractedEdgeId)(anchorId, target.anchorId, import_types27.EdgeType.CALLS);
7707
8038
  if (!graph.hasEdge(edgeId)) {
7708
8039
  const edge = {
7709
8040
  id: edgeId,
7710
8041
  source: anchorId,
7711
8042
  target: target.anchorId,
7712
- type: import_types26.EdgeType.CALLS,
7713
- provenance: import_types26.Provenance.EXTRACTED,
7714
- confidence: (0, import_types26.confidenceForExtracted)("structural"),
8043
+ type: import_types27.EdgeType.CALLS,
8044
+ provenance: import_types27.Provenance.EXTRACTED,
8045
+ confidence: (0, import_types27.confidenceForExtracted)("structural"),
7715
8046
  evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
7716
8047
  };
7717
8048
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -7722,7 +8053,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7722
8053
  const result = addResourceEdge(
7723
8054
  graph,
7724
8055
  anchorId,
7725
- import_types26.EdgeType.DEPENDS_ON,
8056
+ import_types27.EdgeType.DEPENDS_ON,
7726
8057
  "cloudflare-service-binding",
7727
8058
  svc.service,
7728
8059
  evidenceFile,
@@ -7738,12 +8069,12 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7738
8069
  // src/extract/infra/vercel.ts
7739
8070
  init_cjs_shims();
7740
8071
  var import_node_fs22 = require("fs");
7741
- var import_node_path38 = __toESM(require("path"), 1);
7742
- var import_types27 = require("@neat.is/types");
8072
+ var import_node_path39 = __toESM(require("path"), 1);
8073
+ var import_types28 = require("@neat.is/types");
7743
8074
  var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
7744
8075
  async function readVercelConfig(dir) {
7745
8076
  for (const filename of VERCEL_CONFIG_FILENAMES) {
7746
- const abs = import_node_path38.default.join(dir, filename);
8077
+ const abs = import_node_path39.default.join(dir, filename);
7747
8078
  if (!await exists2(abs)) continue;
7748
8079
  const raw = await import_node_fs22.promises.readFile(abs, "utf8");
7749
8080
  const config = JSON.parse(maskCommentsInSource(raw));
@@ -7752,7 +8083,7 @@ async function readVercelConfig(dir) {
7752
8083
  return null;
7753
8084
  }
7754
8085
  async function readLinkedProjectName(dir) {
7755
- const abs = import_node_path38.default.join(dir, ".vercel", "project.json");
8086
+ const abs = import_node_path39.default.join(dir, ".vercel", "project.json");
7756
8087
  if (!await exists2(abs)) return void 0;
7757
8088
  const parsed = JSON.parse(await import_node_fs22.promises.readFile(abs, "utf8"));
7758
8089
  return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
@@ -7770,7 +8101,7 @@ async function addVercelServices(graph, services, scanPath) {
7770
8101
  read = await readVercelConfig(service.dir);
7771
8102
  projectName = await readLinkedProjectName(service.dir);
7772
8103
  } catch (err) {
7773
- recordExtractionError("infra vercel", import_node_path38.default.relative(scanPath, service.dir), err);
8104
+ recordExtractionError("infra vercel", import_node_path39.default.relative(scanPath, service.dir), err);
7774
8105
  continue;
7775
8106
  }
7776
8107
  if (!read && !projectName) continue;
@@ -7786,7 +8117,7 @@ async function addVercelServices(graph, services, scanPath) {
7786
8117
  const anchorId = service.node.id;
7787
8118
  if (!read) continue;
7788
8119
  const { config, relFile, raw } = read;
7789
- const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
8120
+ const evidenceFile = toPosix2(import_node_path39.default.relative(scanPath, import_node_path39.default.join(service.dir, relFile)));
7790
8121
  const add = (edgeType, kind, name) => {
7791
8122
  if (!name) return;
7792
8123
  const result = emitPlatformResourceEdge(
@@ -7802,12 +8133,12 @@ async function addVercelServices(graph, services, scanPath) {
7802
8133
  nodesAdded += result.nodesAdded;
7803
8134
  edgesAdded += result.edgesAdded;
7804
8135
  };
7805
- add(import_types27.EdgeType.RUNS_ON, "vercel", "vercel");
7806
- for (const cron of config.crons ?? []) add(import_types27.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
7807
- for (const varName of Object.keys(config.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7808
- for (const varName of Object.keys(config.build?.env ?? {})) add(import_types27.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
8136
+ add(import_types28.EdgeType.RUNS_ON, "vercel", "vercel");
8137
+ for (const cron of config.crons ?? []) add(import_types28.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
8138
+ for (const varName of Object.keys(config.env ?? {})) add(import_types28.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
8139
+ for (const varName of Object.keys(config.build?.env ?? {})) add(import_types28.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7809
8140
  for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
7810
- add(import_types27.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
8141
+ add(import_types28.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7811
8142
  }
7812
8143
  }
7813
8144
  return { nodesAdded, edgesAdded };
@@ -7816,13 +8147,13 @@ async function addVercelServices(graph, services, scanPath) {
7816
8147
  // src/extract/infra/railway.ts
7817
8148
  init_cjs_shims();
7818
8149
  var import_node_fs23 = require("fs");
7819
- var import_node_path39 = __toESM(require("path"), 1);
8150
+ var import_node_path40 = __toESM(require("path"), 1);
7820
8151
  var import_smol_toml3 = require("smol-toml");
7821
- var import_types28 = require("@neat.is/types");
8152
+ var import_types29 = require("@neat.is/types");
7822
8153
  var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
7823
8154
  async function readRailwayConfig(dir) {
7824
8155
  for (const filename of RAILWAY_FILENAMES) {
7825
- const abs = import_node_path39.default.join(dir, filename);
8156
+ const abs = import_node_path40.default.join(dir, filename);
7826
8157
  if (!await exists2(abs)) continue;
7827
8158
  const raw = await import_node_fs23.promises.readFile(abs, "utf8");
7828
8159
  const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
@@ -7838,7 +8169,7 @@ async function addRailwayServices(graph, services, scanPath) {
7838
8169
  try {
7839
8170
  read = await readRailwayConfig(service.dir);
7840
8171
  } catch (err) {
7841
- recordExtractionError("infra railway", import_node_path39.default.relative(scanPath, service.dir), err);
8172
+ recordExtractionError("infra railway", import_node_path40.default.relative(scanPath, service.dir), err);
7842
8173
  continue;
7843
8174
  }
7844
8175
  if (!read) continue;
@@ -7848,7 +8179,7 @@ async function addRailwayServices(graph, services, scanPath) {
7848
8179
  }
7849
8180
  const anchorId = service.node.id;
7850
8181
  const { config, relFile, raw } = read;
7851
- const evidenceFile = toPosix2(import_node_path39.default.relative(scanPath, import_node_path39.default.join(service.dir, relFile)));
8182
+ const evidenceFile = toPosix2(import_node_path40.default.relative(scanPath, import_node_path40.default.join(service.dir, relFile)));
7852
8183
  const add = (edgeType, kind, name) => {
7853
8184
  if (!name) return;
7854
8185
  const result = emitPlatformResourceEdge(
@@ -7864,9 +8195,9 @@ async function addRailwayServices(graph, services, scanPath) {
7864
8195
  nodesAdded += result.nodesAdded;
7865
8196
  edgesAdded += result.edgesAdded;
7866
8197
  };
7867
- add(import_types28.EdgeType.RUNS_ON, "railway", "railway");
7868
- add(import_types28.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
7869
- add(import_types28.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
8198
+ add(import_types29.EdgeType.RUNS_ON, "railway", "railway");
8199
+ add(import_types29.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
8200
+ add(import_types29.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
7870
8201
  }
7871
8202
  return { nodesAdded, edgesAdded };
7872
8203
  }
@@ -7874,12 +8205,12 @@ async function addRailwayServices(graph, services, scanPath) {
7874
8205
  // src/extract/infra/supabase.ts
7875
8206
  init_cjs_shims();
7876
8207
  var import_node_fs24 = require("fs");
7877
- var import_node_path40 = __toESM(require("path"), 1);
8208
+ var import_node_path41 = __toESM(require("path"), 1);
7878
8209
  var import_smol_toml4 = require("smol-toml");
7879
- var import_types29 = require("@neat.is/types");
8210
+ var import_types30 = require("@neat.is/types");
7880
8211
  async function readSupabaseConfig(dir) {
7881
- const relFile = import_node_path40.default.join("supabase", "config.toml");
7882
- const abs = import_node_path40.default.join(dir, relFile);
8212
+ const relFile = import_node_path41.default.join("supabase", "config.toml");
8213
+ const abs = import_node_path41.default.join(dir, relFile);
7883
8214
  if (!await exists2(abs)) return null;
7884
8215
  const raw = await import_node_fs24.promises.readFile(abs, "utf8");
7885
8216
  const config = (0, import_smol_toml4.parse)(raw);
@@ -7893,7 +8224,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
7893
8224
  try {
7894
8225
  read = await readSupabaseConfig(service.dir);
7895
8226
  } catch (err) {
7896
- recordExtractionError("infra supabase", import_node_path40.default.relative(scanPath, service.dir), err);
8227
+ recordExtractionError("infra supabase", import_node_path41.default.relative(scanPath, service.dir), err);
7897
8228
  continue;
7898
8229
  }
7899
8230
  if (!read) continue;
@@ -7908,7 +8239,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
7908
8239
  });
7909
8240
  }
7910
8241
  const anchorId = service.node.id;
7911
- const evidenceFile = toPosix2(import_node_path40.default.relative(scanPath, import_node_path40.default.join(service.dir, relFile)));
8242
+ const evidenceFile = toPosix2(import_node_path41.default.relative(scanPath, import_node_path41.default.join(service.dir, relFile)));
7912
8243
  const add = (edgeType, kind, name) => {
7913
8244
  if (!name) return;
7914
8245
  const result = emitPlatformResourceEdge(
@@ -7924,10 +8255,10 @@ async function addSupabaseProjects(graph, services, scanPath) {
7924
8255
  nodesAdded += result.nodesAdded;
7925
8256
  edgesAdded += result.edgesAdded;
7926
8257
  };
7927
- add(import_types29.EdgeType.RUNS_ON, "supabase", "supabase");
7928
- for (const fn of Object.keys(config.functions ?? {})) add(import_types29.EdgeType.DEPENDS_ON, "supabase-function", fn);
7929
- if (config.storage) add(import_types29.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
7930
- if (config.auth) add(import_types29.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
8258
+ add(import_types30.EdgeType.RUNS_ON, "supabase", "supabase");
8259
+ for (const fn of Object.keys(config.functions ?? {})) add(import_types30.EdgeType.DEPENDS_ON, "supabase-function", fn);
8260
+ if (config.storage) add(import_types30.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
8261
+ if (config.auth) add(import_types30.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
7931
8262
  }
7932
8263
  return { nodesAdded, edgesAdded };
7933
8264
  }
@@ -7949,17 +8280,17 @@ async function addInfra(graph, scanPath, services) {
7949
8280
  }
7950
8281
 
7951
8282
  // src/extract/index.ts
7952
- var import_node_path42 = __toESM(require("path"), 1);
8283
+ var import_node_path43 = __toESM(require("path"), 1);
7953
8284
 
7954
8285
  // src/extract/retire.ts
7955
8286
  init_cjs_shims();
7956
8287
  var import_node_fs25 = require("fs");
7957
- var import_node_path41 = __toESM(require("path"), 1);
7958
- var import_types30 = require("@neat.is/types");
8288
+ var import_node_path42 = __toESM(require("path"), 1);
8289
+ var import_types31 = require("@neat.is/types");
7959
8290
  function dropOrphanedFileNodes(graph) {
7960
8291
  const orphans = [];
7961
8292
  graph.forEachNode((id, attrs) => {
7962
- if (attrs.type !== import_types30.NodeType.FileNode) return;
8293
+ if (attrs.type !== import_types31.NodeType.FileNode) return;
7963
8294
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
7964
8295
  orphans.push(id);
7965
8296
  }
@@ -7972,14 +8303,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
7972
8303
  const bases = [scanPath, ...serviceDirs];
7973
8304
  graph.forEachEdge((id, attrs) => {
7974
8305
  const edge = attrs;
7975
- if (edge.provenance !== import_types30.Provenance.EXTRACTED) return;
8306
+ if (edge.provenance !== import_types31.Provenance.EXTRACTED) return;
7976
8307
  const evidenceFile = edge.evidence?.file;
7977
8308
  if (!evidenceFile) return;
7978
- if (import_node_path41.default.isAbsolute(evidenceFile)) {
8309
+ if (import_node_path42.default.isAbsolute(evidenceFile)) {
7979
8310
  if (!(0, import_node_fs25.existsSync)(evidenceFile)) toDrop.push(id);
7980
8311
  return;
7981
8312
  }
7982
- const found = bases.some((base) => (0, import_node_fs25.existsSync)(import_node_path41.default.join(base, evidenceFile)));
8313
+ const found = bases.some((base) => (0, import_node_fs25.existsSync)(import_node_path42.default.join(base, evidenceFile)));
7983
8314
  if (!found) toDrop.push(id);
7984
8315
  });
7985
8316
  for (const id of toDrop) graph.dropEdge(id);
@@ -8021,7 +8352,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
8021
8352
  }
8022
8353
  const droppedEntries = drainDroppedExtracted();
8023
8354
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
8024
- const rejectedPath = import_node_path42.default.join(import_node_path42.default.dirname(opts.errorsPath), "rejected.ndjson");
8355
+ const rejectedPath = import_node_path43.default.join(import_node_path43.default.dirname(opts.errorsPath), "rejected.ndjson");
8025
8356
  try {
8026
8357
  await writeRejectedExtracted(droppedEntries, rejectedPath);
8027
8358
  } catch (err) {
@@ -8133,8 +8464,8 @@ function canonicalJson(value) {
8133
8464
  // src/persist.ts
8134
8465
  init_cjs_shims();
8135
8466
  var import_node_fs27 = require("fs");
8136
- var import_node_path43 = __toESM(require("path"), 1);
8137
- var import_types31 = require("@neat.is/types");
8467
+ var import_node_path44 = __toESM(require("path"), 1);
8468
+ var import_types32 = require("@neat.is/types");
8138
8469
  var SCHEMA_VERSION = 4;
8139
8470
  function migrateV1ToV2(payload) {
8140
8471
  const nodes = payload.graph.nodes;
@@ -8156,12 +8487,12 @@ function migrateV2ToV3(payload) {
8156
8487
  for (const edge of edges) {
8157
8488
  const attrs = edge.attributes;
8158
8489
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
8159
- attrs.provenance = import_types31.Provenance.OBSERVED;
8490
+ attrs.provenance = import_types32.Provenance.OBSERVED;
8160
8491
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
8161
8492
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
8162
8493
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
8163
8494
  if (type && source && target) {
8164
- const newId = (0, import_types31.observedEdgeId)(source, target, type);
8495
+ const newId = (0, import_types32.observedEdgeId)(source, target, type);
8165
8496
  attrs.id = newId;
8166
8497
  if (edge.key) edge.key = newId;
8167
8498
  }
@@ -8170,7 +8501,7 @@ function migrateV2ToV3(payload) {
8170
8501
  return { ...payload, schemaVersion: 3 };
8171
8502
  }
8172
8503
  async function ensureDir(filePath) {
8173
- await import_node_fs27.promises.mkdir(import_node_path43.default.dirname(filePath), { recursive: true });
8504
+ await import_node_fs27.promises.mkdir(import_node_path44.default.dirname(filePath), { recursive: true });
8174
8505
  }
8175
8506
  async function saveGraphToDisk(graph, outPath) {
8176
8507
  await ensureDir(outPath);
@@ -8251,23 +8582,23 @@ function startPersistLoop(graph, outPath, opts = {}) {
8251
8582
 
8252
8583
  // src/projects.ts
8253
8584
  init_cjs_shims();
8254
- var import_node_path44 = __toESM(require("path"), 1);
8585
+ var import_node_path45 = __toESM(require("path"), 1);
8255
8586
  function pathsForProject(project, baseDir) {
8256
8587
  if (project === DEFAULT_PROJECT) {
8257
8588
  return {
8258
- snapshotPath: import_node_path44.default.join(baseDir, "graph.json"),
8259
- errorsPath: import_node_path44.default.join(baseDir, "errors.ndjson"),
8260
- staleEventsPath: import_node_path44.default.join(baseDir, "stale-events.ndjson"),
8261
- embeddingsCachePath: import_node_path44.default.join(baseDir, "embeddings.json"),
8262
- policyViolationsPath: import_node_path44.default.join(baseDir, "policy-violations.ndjson")
8589
+ snapshotPath: import_node_path45.default.join(baseDir, "graph.json"),
8590
+ errorsPath: import_node_path45.default.join(baseDir, "errors.ndjson"),
8591
+ staleEventsPath: import_node_path45.default.join(baseDir, "stale-events.ndjson"),
8592
+ embeddingsCachePath: import_node_path45.default.join(baseDir, "embeddings.json"),
8593
+ policyViolationsPath: import_node_path45.default.join(baseDir, "policy-violations.ndjson")
8263
8594
  };
8264
8595
  }
8265
8596
  return {
8266
- snapshotPath: import_node_path44.default.join(baseDir, `${project}.json`),
8267
- errorsPath: import_node_path44.default.join(baseDir, `errors.${project}.ndjson`),
8268
- staleEventsPath: import_node_path44.default.join(baseDir, `stale-events.${project}.ndjson`),
8269
- embeddingsCachePath: import_node_path44.default.join(baseDir, `embeddings.${project}.json`),
8270
- policyViolationsPath: import_node_path44.default.join(baseDir, `policy-violations.${project}.ndjson`)
8597
+ snapshotPath: import_node_path45.default.join(baseDir, `${project}.json`),
8598
+ errorsPath: import_node_path45.default.join(baseDir, `errors.${project}.ndjson`),
8599
+ staleEventsPath: import_node_path45.default.join(baseDir, `stale-events.${project}.ndjson`),
8600
+ embeddingsCachePath: import_node_path45.default.join(baseDir, `embeddings.${project}.json`),
8601
+ policyViolationsPath: import_node_path45.default.join(baseDir, `policy-violations.${project}.ndjson`)
8271
8602
  };
8272
8603
  }
8273
8604
  var Projects = class {
@@ -8309,15 +8640,15 @@ function parseExtraProjects(raw) {
8309
8640
  init_cjs_shims();
8310
8641
  var import_node_fs28 = require("fs");
8311
8642
  var import_node_os3 = __toESM(require("os"), 1);
8312
- var import_node_path45 = __toESM(require("path"), 1);
8313
- var import_types32 = require("@neat.is/types");
8643
+ var import_node_path46 = __toESM(require("path"), 1);
8644
+ var import_types33 = require("@neat.is/types");
8314
8645
  function neatHome() {
8315
8646
  const override = process.env.NEAT_HOME;
8316
- if (override && override.length > 0) return import_node_path45.default.resolve(override);
8317
- return import_node_path45.default.join(import_node_os3.default.homedir(), ".neat");
8647
+ if (override && override.length > 0) return import_node_path46.default.resolve(override);
8648
+ return import_node_path46.default.join(import_node_os3.default.homedir(), ".neat");
8318
8649
  }
8319
8650
  function registryPath() {
8320
- return import_node_path45.default.join(neatHome(), "projects.json");
8651
+ return import_node_path46.default.join(neatHome(), "projects.json");
8321
8652
  }
8322
8653
  async function readRegistry() {
8323
8654
  const file = registryPath();
@@ -8331,7 +8662,7 @@ async function readRegistry() {
8331
8662
  throw err;
8332
8663
  }
8333
8664
  const parsed = JSON.parse(raw);
8334
- return import_types32.RegistryFileSchema.parse(parsed);
8665
+ return import_types33.RegistryFileSchema.parse(parsed);
8335
8666
  }
8336
8667
  async function getProject(name) {
8337
8668
  const reg = await readRegistry();
@@ -8406,16 +8737,16 @@ init_auth();
8406
8737
  // src/connectors-config.ts
8407
8738
  init_cjs_shims();
8408
8739
  var import_node_os4 = __toESM(require("os"), 1);
8409
- var import_node_path46 = __toESM(require("path"), 1);
8740
+ var import_node_path47 = __toESM(require("path"), 1);
8410
8741
  var import_node_fs29 = require("fs");
8411
8742
  var CONNECTORS_CONFIG_VERSION = 1;
8412
8743
  function neatHome2() {
8413
8744
  const override = process.env.NEAT_HOME;
8414
- if (override && override.length > 0) return import_node_path46.default.resolve(override);
8415
- return import_node_path46.default.join(import_node_os4.default.homedir(), ".neat");
8745
+ if (override && override.length > 0) return import_node_path47.default.resolve(override);
8746
+ return import_node_path47.default.join(import_node_os4.default.homedir(), ".neat");
8416
8747
  }
8417
8748
  function connectorsConfigPath(home = neatHome2()) {
8418
- return import_node_path46.default.join(home, "connectors.json");
8749
+ return import_node_path47.default.join(home, "connectors.json");
8419
8750
  }
8420
8751
  var MODE_MASK_LOOSER_THAN_0600 = 63;
8421
8752
  async function warnIfModeLooserThan0600(file) {
@@ -8714,11 +9045,11 @@ function registerRoutes(scope, ctx) {
8714
9045
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
8715
9046
  const parsed = [];
8716
9047
  for (const c of candidates) {
8717
- const r = import_types33.DivergenceTypeSchema.safeParse(c);
9048
+ const r = import_types34.DivergenceTypeSchema.safeParse(c);
8718
9049
  if (!r.success) {
8719
9050
  return reply.code(400).send({
8720
9051
  error: `unknown divergence type "${c}"`,
8721
- allowed: import_types33.DivergenceTypeSchema.options
9052
+ allowed: import_types34.DivergenceTypeSchema.options
8722
9053
  });
8723
9054
  }
8724
9055
  parsed.push(r.data);
@@ -8989,7 +9320,7 @@ function registerRoutes(scope, ctx) {
8989
9320
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
8990
9321
  let violations = await log.readAll();
8991
9322
  if (req.query.severity) {
8992
- const sev = import_types33.PolicySeveritySchema.safeParse(req.query.severity);
9323
+ const sev = import_types34.PolicySeveritySchema.safeParse(req.query.severity);
8993
9324
  if (!sev.success) {
8994
9325
  return reply.code(400).send({
8995
9326
  error: "invalid severity",
@@ -9028,7 +9359,7 @@ function registerRoutes(scope, ctx) {
9028
9359
  scope.post("/policies/check", async (req, reply) => {
9029
9360
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
9030
9361
  if (!proj) return;
9031
- const parsed = import_types33.PoliciesCheckBodySchema.safeParse(req.body ?? {});
9362
+ const parsed = import_types34.PoliciesCheckBodySchema.safeParse(req.body ?? {});
9032
9363
  if (!parsed.success) {
9033
9364
  return reply.code(400).send({
9034
9365
  error: "invalid /policies/check body",
@@ -9100,6 +9431,42 @@ function registerRoutes(scope, ctx) {
9100
9431
  return reply.code(500).send({ error: err.message });
9101
9432
  }
9102
9433
  });
9434
+ scope.get("/instrumentation", async (req, reply) => {
9435
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
9436
+ if (!proj) return;
9437
+ if (!proj.scanPath) {
9438
+ return { engaged: null };
9439
+ }
9440
+ try {
9441
+ const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath });
9442
+ const uninstrumented = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath });
9443
+ if (state.hookFiles.length === 0) {
9444
+ return {
9445
+ engaged: false,
9446
+ diagnosis: {
9447
+ reason: "No instrumented entry point found \u2014 NEAT hasn't written an OTel init hook for this project.",
9448
+ fixCommand: "neat init",
9449
+ detail: "Run `neat init` so NEAT writes the instrumentation hook next to your entry point, then run your app."
9450
+ }
9451
+ };
9452
+ }
9453
+ if (uninstrumented.length > 0) {
9454
+ const names = uninstrumented.map((u) => u.library);
9455
+ const more = names.length > 1 ? ` (and ${names.length - 1} more)` : "";
9456
+ return {
9457
+ engaged: false,
9458
+ diagnosis: {
9459
+ reason: `\`${names[0]}\`${more} isn't in the auto-instrumentation set, so spans from it won't reach the graph.`,
9460
+ fixCommand: "neat extend",
9461
+ detail: `Uninstrumented on your hot path: ${names.join(", ")}. Run \`neat extend\` to wire the missing instrumentation.`
9462
+ }
9463
+ };
9464
+ }
9465
+ return { engaged: true };
9466
+ } catch {
9467
+ return { engaged: null };
9468
+ }
9469
+ });
9103
9470
  scope.post("/extend/apply", async (req, reply) => {
9104
9471
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
9105
9472
  if (!proj) return;
@@ -9294,7 +9661,7 @@ init_otel_grpc();
9294
9661
  // src/search.ts
9295
9662
  init_cjs_shims();
9296
9663
  var import_node_fs30 = require("fs");
9297
- var import_node_path49 = __toESM(require("path"), 1);
9664
+ var import_node_path50 = __toESM(require("path"), 1);
9298
9665
  var import_node_crypto3 = require("crypto");
9299
9666
  var DEFAULT_LIMIT = 10;
9300
9667
  var NOMIC_DIM = 768;
@@ -9457,7 +9824,7 @@ async function readCache(cachePath) {
9457
9824
  }
9458
9825
  }
9459
9826
  async function writeCache(cachePath, cache) {
9460
- await import_node_fs30.promises.mkdir(import_node_path49.default.dirname(cachePath), { recursive: true });
9827
+ await import_node_fs30.promises.mkdir(import_node_path50.default.dirname(cachePath), { recursive: true });
9461
9828
  await import_node_fs30.promises.writeFile(cachePath, JSON.stringify(cache));
9462
9829
  }
9463
9830
  var VectorIndex = class {
@@ -9638,14 +10005,14 @@ async function bootProject(registry, name, scanPath, baseDir) {
9638
10005
  async function main() {
9639
10006
  const baseDirEnv = process.env.NEAT_OUT_DIR;
9640
10007
  const legacyOutPath = process.env.NEAT_OUT_PATH;
9641
- const baseDir = baseDirEnv ? import_node_path50.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path50.default.resolve(import_node_path50.default.dirname(legacyOutPath)) : import_node_path50.default.resolve("./neat-out");
9642
- const defaultScanPath = import_node_path50.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
10008
+ const baseDir = baseDirEnv ? import_node_path51.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path51.default.resolve(import_node_path51.default.dirname(legacyOutPath)) : import_node_path51.default.resolve("./neat-out");
10009
+ const defaultScanPath = import_node_path51.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
9643
10010
  const registry = new Projects();
9644
10011
  await bootProject(registry, DEFAULT_PROJECT, defaultScanPath, baseDir);
9645
10012
  for (const name of parseExtraProjects(process.env.NEAT_PROJECTS)) {
9646
10013
  const envKey = `NEAT_PROJECT_SCAN_PATH_${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
9647
10014
  const projectScan = process.env[envKey];
9648
- await bootProject(registry, name, projectScan ? import_node_path50.default.resolve(projectScan) : void 0, baseDir);
10015
+ await bootProject(registry, name, projectScan ? import_node_path51.default.resolve(projectScan) : void 0, baseDir);
9649
10016
  }
9650
10017
  const host = process.env.HOST ?? "0.0.0.0";
9651
10018
  const port = Number(process.env.PORT ?? 8080);