@neat.is/core 0.7.7 → 0.7.9

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
@@ -504,13 +504,18 @@ function loadProtobufResponseEncoder() {
504
504
  );
505
505
  return exportTraceServiceResponseType;
506
506
  }
507
- function encodeProtobufResponseBody() {
508
- if (cachedProtobufResponseBody) return cachedProtobufResponseBody;
507
+ function encodeProtobufResponseBody(rejected, message) {
509
508
  const Type = loadProtobufResponseEncoder();
510
- const msg = Type.create({});
511
- const encoded = Type.encode(msg).finish();
512
- cachedProtobufResponseBody = Buffer.from(encoded);
513
- return cachedProtobufResponseBody;
509
+ if (!rejected) {
510
+ if (cachedProtobufResponseBody) return cachedProtobufResponseBody;
511
+ const msg2 = Type.create({});
512
+ cachedProtobufResponseBody = Buffer.from(Type.encode(msg2).finish());
513
+ return cachedProtobufResponseBody;
514
+ }
515
+ const msg = Type.fromObject({
516
+ partial_success: { rejected_spans: rejected, error_message: message ?? "" }
517
+ });
518
+ return Buffer.from(Type.encode(msg).finish());
514
519
  }
515
520
  async function decodeProtobufBody(buf) {
516
521
  const Type = loadProtobufDecoder();
@@ -625,6 +630,13 @@ async function buildOtelReceiver(opts) {
625
630
  }
626
631
  return reply.code(200).header("content-type", "application/json").send({ partialSuccess: {} });
627
632
  }
633
+ function sendOtlpPartial(reply, flavor, rejected, message) {
634
+ if (flavor === "protobuf") {
635
+ const buf = encodeProtobufResponseBody(rejected, message);
636
+ return reply.code(200).header("content-type", "application/x-protobuf").send(buf);
637
+ }
638
+ return reply.code(200).header("content-type", "application/json").send({ partialSuccess: { rejectedSpans: rejected, errorMessage: message } });
639
+ }
628
640
  app.addContentTypeParser(
629
641
  "application/x-protobuf",
630
642
  { parseAs: "buffer", bodyLimit: opts.bodyLimit ?? 16 * 1024 * 1024 },
@@ -652,6 +664,10 @@ async function buildOtelReceiver(opts) {
652
664
  }
653
665
  }
654
666
  enqueue(spans);
667
+ if (opts.classifyBareRoutability) {
668
+ const { rejected, message } = opts.classifyBareRoutability(spans);
669
+ if (rejected > 0) return sendOtlpPartial(reply, result.flavor, rejected, message);
670
+ }
655
671
  return sendOtlpSuccess(reply, result.flavor);
656
672
  });
657
673
  app.post("/projects/:project/v1/traces", async (req, reply) => {
@@ -3065,6 +3081,7 @@ var ROUTER_METHODS = /* @__PURE__ */ new Set([
3065
3081
  "all"
3066
3082
  ]);
3067
3083
  var NEXT_APP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
3084
+ var NET_HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
3068
3085
  var JS_ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx"]);
3069
3086
  function goRouterRoutesFromSource(source, parser, framework) {
3070
3087
  const tree = parseSource2(parser, source);
@@ -3118,6 +3135,101 @@ function echoRoutesFromSource(source, parser) {
3118
3135
  function fiberRoutesFromSource(source, parser) {
3119
3136
  return goRouterRoutesFromSource(source, parser, "fiber");
3120
3137
  }
3138
+ function chiRoutesFromSource(source, parser) {
3139
+ const tree = parseSource2(parser, source);
3140
+ const out = [];
3141
+ chiWalk(tree.rootNode, "", out);
3142
+ return out;
3143
+ }
3144
+ function stripChiRegex(path82) {
3145
+ return path82.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
3146
+ }
3147
+ function chiWalk(node, prefix, out) {
3148
+ for (let i = 0; i < node.namedChildCount; i++) {
3149
+ const child = node.namedChild(i);
3150
+ if (child) chiHandle(child, prefix, out);
3151
+ }
3152
+ }
3153
+ function chiHandle(node, prefix, out) {
3154
+ if (node.type === "call_expression") {
3155
+ const fn = node.childForFieldName("function");
3156
+ if (fn?.type === "selector_expression") {
3157
+ const field = fn.childForFieldName("field")?.text;
3158
+ const args = node.childForFieldName("arguments");
3159
+ if (field === "Route") {
3160
+ const leaf = goStringLiteral(args?.namedChild(0));
3161
+ const closure = args?.namedChild(1);
3162
+ if (leaf !== null && closure?.type === "func_literal") {
3163
+ const body = closure.childForFieldName("body");
3164
+ if (body) chiWalk(body, prefix + leaf, out);
3165
+ }
3166
+ return;
3167
+ }
3168
+ if (field === "Group") {
3169
+ const closure = args?.namedChild(0);
3170
+ if (closure?.type === "func_literal") {
3171
+ const body = closure.childForFieldName("body");
3172
+ if (body) chiWalk(body, prefix, out);
3173
+ }
3174
+ return;
3175
+ }
3176
+ if (field === "Mount") {
3177
+ return;
3178
+ }
3179
+ if (field && ROUTER_METHODS.has(field.toLowerCase())) {
3180
+ const leaf = goStringLiteral(args?.namedChild(0));
3181
+ if (leaf !== null) {
3182
+ out.push({
3183
+ method: field.toUpperCase(),
3184
+ pathTemplate: canonicalizeTemplate(stripChiRegex(prefix + leaf)),
3185
+ line: node.startPosition.row + 1,
3186
+ framework: "chi"
3187
+ });
3188
+ }
3189
+ return;
3190
+ }
3191
+ }
3192
+ }
3193
+ chiWalk(node, prefix, out);
3194
+ }
3195
+ function netHttpRoutesFromSource(source, parser) {
3196
+ const tree = parseSource2(parser, source);
3197
+ if (!goImportsNetHttp(tree.rootNode)) return [];
3198
+ const out = [];
3199
+ walk(tree.rootNode, (node) => {
3200
+ if (node.type !== "call_expression") return;
3201
+ const fn = node.childForFieldName("function");
3202
+ if (fn?.type !== "selector_expression") return;
3203
+ const field = fn.childForFieldName("field")?.text;
3204
+ if (field !== "HandleFunc" && field !== "Handle") return;
3205
+ const leaf = goStringLiteral(node.childForFieldName("arguments")?.namedChild(0));
3206
+ if (leaf === null) return;
3207
+ const sp = leaf.indexOf(" ");
3208
+ if (sp < 0) return;
3209
+ const method = leaf.slice(0, sp);
3210
+ const rest = leaf.slice(sp + 1);
3211
+ if (!NET_HTTP_METHODS.has(method)) return;
3212
+ if (!rest.startsWith("/")) return;
3213
+ out.push({
3214
+ method,
3215
+ pathTemplate: canonicalizeTemplate(rest),
3216
+ line: node.startPosition.row + 1,
3217
+ framework: "net/http"
3218
+ });
3219
+ });
3220
+ return out;
3221
+ }
3222
+ function goImportsNetHttp(root) {
3223
+ let found = false;
3224
+ walk(root, (node) => {
3225
+ if (found || node.type !== "import_spec") return;
3226
+ for (let i = 0; i < node.namedChildCount; i++) {
3227
+ const child = node.namedChild(i);
3228
+ if (goStringLiteral(child) === "net/http") found = true;
3229
+ }
3230
+ });
3231
+ return found;
3232
+ }
3121
3233
  var FASTAPI_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete", "options", "head", "trace"]);
3122
3234
  var NESTJS_METHODS = /* @__PURE__ */ new Map([
3123
3235
  ["Get", "GET"],
@@ -4383,9 +4495,11 @@ async function addRoutes(graph, services) {
4383
4495
  const hasGin = deps["github.com/gin-gonic/gin"] !== void 0;
4384
4496
  const hasEcho = deps["github.com/labstack/echo/v4"] !== void 0 || deps["github.com/labstack/echo"] !== void 0;
4385
4497
  const hasFiber = deps["github.com/gofiber/fiber/v2"] !== void 0 || deps["github.com/gofiber/fiber/v3"] !== void 0;
4498
+ const hasChi = deps["github.com/go-chi/chi/v5"] !== void 0 || deps["github.com/go-chi/chi"] !== void 0;
4499
+ const isGoService = service.node.language === "go";
4386
4500
  const hasRails = deps["rails"] !== void 0;
4387
4501
  const hasLaravel = deps["laravel/framework"] !== void 0;
4388
- if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin && !hasEcho && !hasFiber && !hasRails && !hasLaravel)
4502
+ if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin && !hasEcho && !hasFiber && !hasChi && !isGoService && !hasRails && !hasLaravel)
4389
4503
  continue;
4390
4504
  const files = await loadSourceFiles(service.dir);
4391
4505
  const mountPrefixes = hasExpress ? await expressMountPrefixes(files, service.dir, await loadTsPathConfig(service.dir)) : /* @__PURE__ */ new Map();
@@ -4412,7 +4526,9 @@ async function addRoutes(graph, services) {
4412
4526
  if (hasGin) routes = ginRoutesFromSource(file.content, goParser);
4413
4527
  else if (hasEcho) routes = echoRoutesFromSource(file.content, goParser);
4414
4528
  else if (hasFiber) routes = fiberRoutesFromSource(file.content, goParser);
4529
+ else if (hasChi) routes = chiRoutesFromSource(file.content, goParser);
4415
4530
  else routes = [];
4531
+ routes = routes.concat(netHttpRoutesFromSource(file.content, goParser));
4416
4532
  } else if (isPy) {
4417
4533
  routes = hasFastapi || hasFlask ? pythonRoutesFromSource(file.content, pyParser, hasFastapi ? "fastapi" : "flask") : [];
4418
4534
  if (hasDjango) routes = routes.concat(djangoRoutesFromSource(file.content, pyParser));
@@ -4785,10 +4901,15 @@ function resolveDistToSrc(absFilepath, line) {
4785
4901
  }
4786
4902
  if (!entry2) return null;
4787
4903
  try {
4788
- const pos = entry2.consumer.originalPositionFor({
4789
- line: line !== void 0 && Number.isFinite(line) ? line : 1,
4790
- column: 0
4791
- });
4904
+ const queryLine = line !== void 0 && Number.isFinite(line) ? line : 1;
4905
+ let pos = entry2.consumer.originalPositionFor({ line: queryLine, column: 0 });
4906
+ if (!pos || !pos.source) {
4907
+ pos = entry2.consumer.originalPositionFor({
4908
+ line: queryLine,
4909
+ column: 0,
4910
+ bias: sourceMapJs.SourceMapConsumer.LEAST_UPPER_BOUND
4911
+ });
4912
+ }
4792
4913
  if (!pos || !pos.source) return null;
4793
4914
  const root = entry2.consumer.sourceRoot ?? "";
4794
4915
  const resolved = import_node_path9.default.resolve(entry2.dir, root, pos.source);
@@ -4797,6 +4918,9 @@ function resolveDistToSrc(absFilepath, line) {
4797
4918
  return null;
4798
4919
  }
4799
4920
  }
4921
+ function hasAdjacentSourceMap(absFilepath) {
4922
+ return sourceMapCache.get(absFilepath) != null;
4923
+ }
4800
4924
  function callSiteFromSpan(span, serviceNode, scanPath) {
4801
4925
  const filepath = codeFilepathOf(span.attributes);
4802
4926
  if (filepath === void 0) return null;
@@ -4812,7 +4936,7 @@ function callSiteFromSpan(span, serviceNode, scanPath) {
4812
4936
  }
4813
4937
  const relPath = relPathForRuntimeFile(effectivePath, serviceNode, scanPath);
4814
4938
  if (!relPath) return null;
4815
- if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && serviceNode?.name) {
4939
+ if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && !hasAdjacentSourceMap(abs) && serviceNode?.name) {
4816
4940
  warnNoSourceMaps(serviceNode.name);
4817
4941
  }
4818
4942
  const fn = codeFunctionOf(span.attributes);
@@ -5088,7 +5212,7 @@ function resolveServiceId(graph, host, env) {
5088
5212
  function frontierIdFor(host) {
5089
5213
  return (0, import_types8.frontierId)(host);
5090
5214
  }
5091
- function ensureServiceNode(graph, serviceName, env) {
5215
+ function resolveFusedServiceId(graph, serviceName, env) {
5092
5216
  const id = (0, import_types8.serviceId)(serviceName, env);
5093
5217
  if (graph.hasNode(id)) return id;
5094
5218
  const wanted = serviceName.toLowerCase();
@@ -5098,17 +5222,21 @@ function ensureServiceNode(graph, serviceName, env) {
5098
5222
  if (svc.discoveredVia === "otel") return false;
5099
5223
  return typeof svc.name === "string" && svc.name.toLowerCase() === wanted;
5100
5224
  });
5101
- if (extractedId) return extractedId;
5225
+ return extractedId ?? id;
5226
+ }
5227
+ function ensureServiceNode(graph, serviceName, env) {
5228
+ const resolved = resolveFusedServiceId(graph, serviceName, env);
5229
+ if (graph.hasNode(resolved)) return resolved;
5102
5230
  const node = {
5103
- id,
5231
+ id: resolved,
5104
5232
  type: import_types8.NodeType.ServiceNode,
5105
5233
  name: serviceName,
5106
5234
  language: "unknown",
5107
5235
  discoveredVia: "otel",
5108
5236
  ...env !== "unknown" ? { env } : {}
5109
5237
  };
5110
- graph.addNode(id, node);
5111
- return id;
5238
+ graph.addNode(resolved, node);
5239
+ return resolved;
5112
5240
  }
5113
5241
  function ensureInfraNode(graph, kind, name, provider) {
5114
5242
  const id = (0, import_types8.infraId)(kind, name);
@@ -5302,7 +5430,7 @@ async function appendErrorEvent(ctx, ev) {
5302
5430
  await import_node_fs8.promises.appendFile(ctx.errorsPath, JSON.stringify(ev) + "\n", "utf8");
5303
5431
  }
5304
5432
  function incidentAffectedNode(span, graph, scanPath) {
5305
- const sid = (0, import_types8.serviceId)(span.service, span.env);
5433
+ const sid = graph ? resolveFusedServiceId(graph, span.service, span.env) : (0, import_types8.serviceId)(span.service, span.env);
5306
5434
  const serviceNode = graph && graph.hasNode(sid) ? graph.getNodeAttributes(sid) : void 0;
5307
5435
  const callSite = callSiteFromSpan(span, serviceNode, scanPath);
5308
5436
  if (callSite) {
@@ -6026,6 +6154,7 @@ function goFramework(deps) {
6026
6154
  if (deps["github.com/gin-gonic/gin"]) return "gin";
6027
6155
  if (deps["github.com/labstack/echo/v4"] || deps["github.com/labstack/echo"]) return "echo";
6028
6156
  if (deps["github.com/gofiber/fiber/v2"] || deps["github.com/gofiber/fiber/v3"]) return "fiber";
6157
+ if (deps["github.com/go-chi/chi/v5"] || deps["github.com/go-chi/chi"]) return "chi";
6029
6158
  return void 0;
6030
6159
  }
6031
6160
  async function discoverGoService(scanPath, dir) {
@@ -6703,7 +6832,7 @@ function disambiguate(defs) {
6703
6832
  }
6704
6833
  async function addSymbols(graph, services) {
6705
6834
  const parsers = /* @__PURE__ */ new Map();
6706
- const parserForExt4 = (ext) => {
6835
+ const parserForExt5 = (ext) => {
6707
6836
  const grammar = GRAMMAR_BY_EXT[ext];
6708
6837
  if (!grammar) return null;
6709
6838
  let parser = parsers.get(ext);
@@ -6719,7 +6848,7 @@ async function addSymbols(graph, services) {
6719
6848
  for (const service of services) {
6720
6849
  const files = await loadSourceFiles(service.dir);
6721
6850
  for (const file of files) {
6722
- const parser = parserForExt4(import_node_path18.default.extname(file.path));
6851
+ const parser = parserForExt5(import_node_path18.default.extname(file.path));
6723
6852
  if (!parser) continue;
6724
6853
  const relPath = toPosix(import_node_path18.default.relative(service.dir, file.path));
6725
6854
  let defs;
@@ -6871,7 +7000,7 @@ function stringInner(node) {
6871
7000
  }
6872
7001
  async function addSymbolEdges(graph, services) {
6873
7002
  const parsers = /* @__PURE__ */ new Map();
6874
- const parserForExt4 = (ext) => {
7003
+ const parserForExt5 = (ext) => {
6875
7004
  const grammar = GRAMMAR_BY_EXT[ext];
6876
7005
  if (!grammar) return null;
6877
7006
  let parser = parsers.get(ext);
@@ -6888,7 +7017,7 @@ async function addSymbolEdges(graph, services) {
6888
7017
  const tsPaths = await loadTsPathConfig(service.dir);
6889
7018
  const files = await loadSourceFiles(service.dir);
6890
7019
  for (const file of files) {
6891
- const parser = parserForExt4(import_node_path19.default.extname(file.path));
7020
+ const parser = parserForExt5(import_node_path19.default.extname(file.path));
6892
7021
  if (!parser) continue;
6893
7022
  const relPath = toPosix(import_node_path19.default.relative(service.dir, file.path));
6894
7023
  const fileDir = import_node_path19.default.dirname(file.path);
@@ -7150,7 +7279,7 @@ function firstReferenceLines(root, wanted) {
7150
7279
  }
7151
7280
  async function addServerActions(graph, services) {
7152
7281
  const parsers = /* @__PURE__ */ new Map();
7153
- const parserForExt4 = (ext) => {
7282
+ const parserForExt5 = (ext) => {
7154
7283
  const grammar = GRAMMAR_BY_EXT[ext];
7155
7284
  if (!grammar) return null;
7156
7285
  let parser = parsers.get(ext);
@@ -7173,7 +7302,7 @@ async function addServerActions(graph, services) {
7173
7302
  const files = await loadSourceFiles(service.dir);
7174
7303
  for (const file of files) {
7175
7304
  if (isTestPath(file.path)) continue;
7176
- const parser = parserForExt4(import_node_path20.default.extname(file.path));
7305
+ const parser = parserForExt5(import_node_path20.default.extname(file.path));
7177
7306
  if (!parser) continue;
7178
7307
  const relPath = toPosix(import_node_path20.default.relative(service.dir, file.path));
7179
7308
  let root;
@@ -7236,7 +7365,7 @@ async function addServerActions(graph, services) {
7236
7365
  }
7237
7366
  for (const file of files) {
7238
7367
  if (isTestPath(file.path)) continue;
7239
- const parser = parserForExt4(import_node_path20.default.extname(file.path));
7368
+ const parser = parserForExt5(import_node_path20.default.extname(file.path));
7240
7369
  if (!parser) continue;
7241
7370
  const relPath = toPosix(import_node_path20.default.relative(service.dir, file.path));
7242
7371
  const fileDir = import_node_path20.default.dirname(file.path);
@@ -8200,6 +8329,7 @@ init_cjs_shims();
8200
8329
  var import_node_path31 = __toESM(require("path"), 1);
8201
8330
  var import_tree_sitter6 = __toESM(require("tree-sitter"), 1);
8202
8331
  var import_tree_sitter_javascript4 = __toESM(require("tree-sitter-javascript"), 1);
8332
+ var import_tree_sitter_typescript2 = __toESM(require("tree-sitter-typescript"), 1);
8203
8333
  var import_tree_sitter_python3 = __toESM(require("tree-sitter-python"), 1);
8204
8334
  var import_types20 = require("@neat.is/types");
8205
8335
  var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
@@ -8250,19 +8380,27 @@ function callsFromSource(source, parser, knownHosts) {
8250
8380
  }
8251
8381
  return out;
8252
8382
  }
8253
- function makeJsParser3() {
8254
- const p = new import_tree_sitter6.default();
8255
- p.setLanguage(import_tree_sitter_javascript4.default);
8256
- return p;
8257
- }
8258
- function makePyParser3() {
8259
- const p = new import_tree_sitter6.default();
8260
- p.setLanguage(import_tree_sitter_python3.default);
8261
- return p;
8383
+ var GRAMMAR_BY_EXT2 = {
8384
+ ".ts": import_tree_sitter_typescript2.default.typescript,
8385
+ ".tsx": import_tree_sitter_typescript2.default.tsx,
8386
+ ".js": import_tree_sitter_javascript4.default,
8387
+ ".jsx": import_tree_sitter_javascript4.default,
8388
+ ".mjs": import_tree_sitter_javascript4.default,
8389
+ ".cjs": import_tree_sitter_javascript4.default,
8390
+ ".py": import_tree_sitter_python3.default
8391
+ };
8392
+ function parserForExt(ext, cache) {
8393
+ const grammar = GRAMMAR_BY_EXT2[ext] ?? import_tree_sitter_javascript4.default;
8394
+ let parser = cache.get(grammar);
8395
+ if (!parser) {
8396
+ parser = new import_tree_sitter6.default();
8397
+ parser.setLanguage(grammar);
8398
+ cache.set(grammar, parser);
8399
+ }
8400
+ return parser;
8262
8401
  }
8263
8402
  async function addHttpCallEdges(graph, services) {
8264
- const jsParser = makeJsParser3();
8265
- const pyParser = makePyParser3();
8403
+ const parserCache = /* @__PURE__ */ new Map();
8266
8404
  const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
8267
8405
  let nodesAdded = 0;
8268
8406
  let edgesAdded = 0;
@@ -8271,7 +8409,7 @@ async function addHttpCallEdges(graph, services) {
8271
8409
  const seen = /* @__PURE__ */ new Set();
8272
8410
  for (const file of files) {
8273
8411
  if (isTestPath(file.path)) continue;
8274
- const parser = import_node_path31.default.extname(file.path) === ".py" ? pyParser : jsParser;
8412
+ const parser = parserForExt(import_node_path31.default.extname(file.path), parserCache);
8275
8413
  let sites;
8276
8414
  try {
8277
8415
  sites = callsFromSource(file.content, parser, knownHosts);
@@ -8344,7 +8482,7 @@ function parseSource5(parser, source) {
8344
8482
  (index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK5)
8345
8483
  );
8346
8484
  }
8347
- function makeJsParser4() {
8485
+ function makeJsParser3() {
8348
8486
  const p = new import_tree_sitter7.default();
8349
8487
  p.setLanguage(import_tree_sitter_javascript5.default);
8350
8488
  return p;
@@ -8522,7 +8660,7 @@ function findRoute(entries, method, normalizedPath) {
8522
8660
  );
8523
8661
  }
8524
8662
  async function addRouteCallEdges(graph, services) {
8525
- const jsParser = makeJsParser4();
8663
+ const jsParser = makeJsParser3();
8526
8664
  const { knownHosts, hostToNodeId } = buildServiceHostIndex(services);
8527
8665
  const routeIndex = buildRouteIndex(graph);
8528
8666
  if (routeIndex.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
@@ -8914,7 +9052,7 @@ var import_tree_sitter_javascript6 = __toESM(require("tree-sitter-javascript"),
8914
9052
  var import_types27 = require("@neat.is/types");
8915
9053
  var FIRESTORE_CLIENT_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])firebase\/firestore['"`]/;
8916
9054
  var FIRESTORE_ADMIN_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])firebase-admin(?:\/firestore)?['"`]/;
8917
- function parserForExt(ext) {
9055
+ function parserForExt2(ext) {
8918
9056
  const p = new import_tree_sitter8.default();
8919
9057
  p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript6.default);
8920
9058
  return p;
@@ -9079,7 +9217,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
9079
9217
  const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
9080
9218
  if (!hasClient && !hasAdmin) return [];
9081
9219
  const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
9082
- const tree = parseSource3(parserForExt(import_node_path38.default.extname(file.path)), file.content);
9220
+ const tree = parseSource3(parserForExt2(import_node_path38.default.extname(file.path)), file.content);
9083
9221
  const clientVars = firestoreClientVars(tree.rootNode);
9084
9222
  const collLine = /* @__PURE__ */ new Map();
9085
9223
  const writes = /* @__PURE__ */ new Map();
@@ -9496,7 +9634,7 @@ var import_tree_sitter_python4 = __toESM(require("tree-sitter-python"), 1);
9496
9634
  var import_types29 = require("@neat.is/types");
9497
9635
  var SQLALCHEMY_IMPORT_RE = /(?:from|import)\s+(?:flask_sqlalchemy|sqlalchemy)\b/;
9498
9636
  var PARSE_CHUNK6 = 16384;
9499
- function makePyParser4() {
9637
+ function makePyParser3() {
9500
9638
  const p = new import_tree_sitter9.default();
9501
9639
  p.setLanguage(import_tree_sitter_python4.default);
9502
9640
  return p;
@@ -9603,7 +9741,7 @@ function foreignKeyParentTable(call) {
9603
9741
  }
9604
9742
  function sqlalchemyForeignKeys(file, serviceDir) {
9605
9743
  if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
9606
- const tree = parseSource6(makePyParser4(), file.content);
9744
+ const tree = parseSource6(makePyParser3(), file.content);
9607
9745
  const out = [];
9608
9746
  const seen = /* @__PURE__ */ new Set();
9609
9747
  walk3(tree.rootNode, (node) => {
@@ -9640,7 +9778,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
9640
9778
  }
9641
9779
  function sqlalchemyEndpointsFromFile(file, serviceDir) {
9642
9780
  if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
9643
- const tree = parseSource6(makePyParser4(), file.content);
9781
+ const tree = parseSource6(makePyParser3(), file.content);
9644
9782
  const out = [];
9645
9783
  const seen = /* @__PURE__ */ new Set();
9646
9784
  const push = (name, line, columns) => {
@@ -9692,7 +9830,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
9692
9830
  function buildSqlalchemyModelRegistry(files) {
9693
9831
  const table = /* @__PURE__ */ new Map();
9694
9832
  const ambiguous = /* @__PURE__ */ new Set();
9695
- const parser = makePyParser4();
9833
+ const parser = makePyParser3();
9696
9834
  for (const file of files) {
9697
9835
  if (!SQLALCHEMY_IMPORT_RE.test(file.content)) continue;
9698
9836
  const tree = parseSource6(parser, file.content);
@@ -9741,7 +9879,7 @@ function importsModelName(content, name) {
9741
9879
  function pythonOrmCrossFileEndpoints(files, serviceDir) {
9742
9880
  const registry = buildSqlalchemyModelRegistry(files);
9743
9881
  if (registry.size === 0) return [];
9744
- const parser = makePyParser4();
9882
+ const parser = makePyParser3();
9745
9883
  const out = [];
9746
9884
  const seen = /* @__PURE__ */ new Set();
9747
9885
  for (const file of files) {
@@ -9779,7 +9917,7 @@ var import_tree_sitter_python5 = __toESM(require("tree-sitter-python"), 1);
9779
9917
  var import_types30 = require("@neat.is/types");
9780
9918
  var DJANGO_IMPORT_RE = /(?:from|import)\s+django\b/;
9781
9919
  var PARSE_CHUNK7 = 16384;
9782
- function makePyParser5() {
9920
+ function makePyParser4() {
9783
9921
  const p = new import_tree_sitter10.default();
9784
9922
  p.setLanguage(import_tree_sitter_python5.default);
9785
9923
  return p;
@@ -9840,7 +9978,7 @@ function readMeta(body) {
9840
9978
  }
9841
9979
  function djangoOrmEndpointsFromFile(file, serviceDir) {
9842
9980
  if (!DJANGO_IMPORT_RE.test(file.content)) return [];
9843
- const tree = parseSource7(makePyParser5(), file.content);
9981
+ const tree = parseSource7(makePyParser4(), file.content);
9844
9982
  const out = [];
9845
9983
  const seen = /* @__PURE__ */ new Set();
9846
9984
  const defaultAppLabel = import_node_path41.default.basename(import_node_path41.default.dirname(file.path));
@@ -9875,7 +10013,7 @@ var import_tree_sitter_javascript7 = __toESM(require("tree-sitter-javascript"),
9875
10013
  var import_types31 = require("@neat.is/types");
9876
10014
  var DRIZZLE_IMPORT_RE = /drizzle-orm/;
9877
10015
  var TABLE_BUILDERS = /* @__PURE__ */ new Set(["pgTable", "mysqlTable", "sqliteTable"]);
9878
- function parserForExt2(ext) {
10016
+ function parserForExt3(ext) {
9879
10017
  const p = new import_tree_sitter11.default();
9880
10018
  p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript7.default);
9881
10019
  return p;
@@ -9947,7 +10085,7 @@ function columnsFromObject(obj) {
9947
10085
  }
9948
10086
  function drizzleEndpointsFromFile(file, serviceDir) {
9949
10087
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
9950
- const tree = parseSource3(parserForExt2(import_node_path42.default.extname(file.path)), file.content);
10088
+ const tree = parseSource3(parserForExt3(import_node_path42.default.extname(file.path)), file.content);
9951
10089
  const out = [];
9952
10090
  const seen = /* @__PURE__ */ new Set();
9953
10091
  const walk9 = (node) => {
@@ -10036,7 +10174,7 @@ function referencesTargetVar(call) {
10036
10174
  }
10037
10175
  function drizzleForeignKeys(file, serviceDir) {
10038
10176
  if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
10039
- const tree = parseSource3(parserForExt2(import_node_path42.default.extname(file.path)), file.content);
10177
+ const tree = parseSource3(parserForExt3(import_node_path42.default.extname(file.path)), file.content);
10040
10178
  const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
10041
10179
  const out = [];
10042
10180
  const seen = /* @__PURE__ */ new Set();
@@ -12743,7 +12881,7 @@ var import_tree_sitter_javascript8 = __toESM(require("tree-sitter-javascript"),
12743
12881
  var import_types47 = require("@neat.is/types");
12744
12882
  var ZOD_IMPORT_RE = /\bzod\b/;
12745
12883
  var ZOD_OBJECTS = /* @__PURE__ */ new Set(["z", "zod"]);
12746
- function parserForExt3(ext) {
12884
+ function parserForExt4(ext) {
12747
12885
  const p = new import_tree_sitter16.default();
12748
12886
  p.setLanguage(GRAMMAR_BY_EXT[ext] ?? import_tree_sitter_javascript8.default);
12749
12887
  return p;
@@ -12832,7 +12970,7 @@ function topLevelSchemas(root) {
12832
12970
  }
12833
12971
  function zodShapesFromFile(file, serviceDir) {
12834
12972
  if (!ZOD_IMPORT_RE.test(file.content)) return [];
12835
- const tree = parseSource3(parserForExt3(import_node_path58.default.extname(file.path)), file.content);
12973
+ const tree = parseSource3(parserForExt4(import_node_path58.default.extname(file.path)), file.content);
12836
12974
  const out = [];
12837
12975
  const seen = /* @__PURE__ */ new Set();
12838
12976
  for (const { name, call } of topLevelSchemas(tree.rootNode)) {
@@ -19174,12 +19312,46 @@ var SubstringIndex = class {
19174
19312
  this.graph = graph;
19175
19313
  }
19176
19314
  };
19315
+ var DEFAULT_SEARCH_INIT_TIMEOUT_MS = 3e4;
19316
+ function searchInitTimeoutMs() {
19317
+ const env = process.env.NEAT_SEARCH_INIT_TIMEOUT_MS;
19318
+ if (env !== void 0 && env.length > 0) {
19319
+ const n = Number.parseInt(env, 10);
19320
+ if (Number.isFinite(n) && n >= 0) return n;
19321
+ }
19322
+ return DEFAULT_SEARCH_INIT_TIMEOUT_MS;
19323
+ }
19324
+ async function resolveEmbedderBounded(factory, timeoutMs) {
19325
+ if (timeoutMs <= 0) return factory();
19326
+ let timer;
19327
+ const TIMED_OUT = /* @__PURE__ */ Symbol("embedder-init-timeout");
19328
+ const timeout = new Promise((resolve) => {
19329
+ timer = setTimeout(() => resolve(TIMED_OUT), timeoutMs);
19330
+ timer.unref?.();
19331
+ });
19332
+ try {
19333
+ const result = await Promise.race([factory(), timeout]);
19334
+ if (result === TIMED_OUT) {
19335
+ console.warn(
19336
+ `semantic_search: embedder init exceeded ${timeoutMs}ms; falling back to substring search. Set NEAT_SEARCH_INIT_TIMEOUT_MS to raise the bound (or 0 to wait indefinitely).`
19337
+ );
19338
+ return null;
19339
+ }
19340
+ return result;
19341
+ } finally {
19342
+ if (timer) clearTimeout(timer);
19343
+ }
19344
+ }
19177
19345
  async function buildSearchIndex(graph, options = {}) {
19178
19346
  let embedder = null;
19179
19347
  if (options.embedder) {
19180
19348
  embedder = options.embedder;
19181
19349
  } else if (options.forceProvider !== "substring") {
19182
- embedder = await pickEmbedder();
19350
+ const factory = options.embedderFactory ?? pickEmbedder;
19351
+ embedder = await resolveEmbedderBounded(
19352
+ factory,
19353
+ options.initTimeoutMs ?? searchInitTimeoutMs()
19354
+ );
19183
19355
  if (options.forceProvider === "ollama" && embedder?.provider !== "ollama") {
19184
19356
  embedder = null;
19185
19357
  }
@@ -19802,7 +19974,8 @@ var OTEL_ENDPOINT_RESOLVER_CJS = `;(function () {
19802
19974
  try {
19803
19975
  const __rec = JSON.parse(__neatFs.readFileSync(__neatPath.join(__neatDir, 'neat-out', 'daemon.json'), 'utf8'))
19804
19976
  if (__rec && __rec.ports && typeof __rec.ports.otlp === 'number') {
19805
- process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://localhost:' + __rec.ports.otlp + '/v1/traces'
19977
+ const __neatProj = (typeof __rec.project === 'string' && __rec.project) || '__PROJECT__'
19978
+ process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://localhost:' + __rec.ports.otlp + '/projects/' + __neatProj + '/v1/traces'
19806
19979
  break
19807
19980
  }
19808
19981
  } catch (_e) {}
@@ -19811,7 +19984,7 @@ var OTEL_ENDPOINT_RESOLVER_CJS = `;(function () {
19811
19984
  __neatDir = __parent
19812
19985
  }
19813
19986
  } catch (_e) {}
19814
- process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/v1/traces'
19987
+ process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
19815
19988
  })()`;
19816
19989
  var OTEL_ESM_NODE_IMPORTS = "import { readFileSync as __neatReadFileSync } from 'node:fs'\nimport { join as __neatJoin, dirname as __neatDirname } from 'node:path'";
19817
19990
  var OTEL_ENDPOINT_RESOLVER_ESM = `;(function () {
@@ -19822,7 +19995,8 @@ var OTEL_ENDPOINT_RESOLVER_ESM = `;(function () {
19822
19995
  try {
19823
19996
  const __rec = JSON.parse(__neatReadFileSync(__neatJoin(__neatDir, 'neat-out', 'daemon.json'), 'utf8'))
19824
19997
  if (__rec && __rec.ports && typeof __rec.ports.otlp === 'number') {
19825
- process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://localhost:' + __rec.ports.otlp + '/v1/traces'
19998
+ const __neatProj = (typeof __rec.project === 'string' && __rec.project) || '__PROJECT__'
19999
+ process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://localhost:' + __rec.ports.otlp + '/projects/' + __neatProj + '/v1/traces'
19826
20000
  break
19827
20001
  }
19828
20002
  } catch (_e) {}
@@ -19831,7 +20005,7 @@ var OTEL_ENDPOINT_RESOLVER_ESM = `;(function () {
19831
20005
  __neatDir = __parent
19832
20006
  }
19833
20007
  } catch (_e) {}
19834
- process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/v1/traces'
20008
+ process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
19835
20009
  })()`;
19836
20010
  function neatCaptureSource(ts) {
19837
20011
  const spanT = ts ? ": any" : "";
@@ -20233,14 +20407,15 @@ ${registrations.join("\n")}
20233
20407
  `;
20234
20408
  return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName).replace(/__INSTRUMENTATION_BLOCK__\n?/g, block);
20235
20409
  }
20236
- function renderEnvNeat(serviceName, _projectName) {
20410
+ function renderEnvNeat(serviceName, projectName) {
20237
20411
  return [
20238
20412
  "# Generated by `neat init --apply` (ADR-069).",
20239
20413
  `OTEL_SERVICE_NAME=${serviceName}`,
20240
20414
  "# Advisory only \u2014 the generated otel-init resolves the live endpoint from",
20241
- "# <project>/neat-out/daemon.json (ports.otlp) at boot (ADR-096). This is the",
20242
- "# canonical default the daemon takes when its first-choice OTLP port is free.",
20243
- "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces",
20415
+ "# <project>/neat-out/daemon.json (ports.otlp + project) at boot (ADR-096).",
20416
+ "# This is the canonical default the daemon takes when its first-choice OTLP",
20417
+ "# port is free; the project scope routes the span without service.name guessing (#879).",
20418
+ `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/projects/${projectName}/v1/traces`,
20244
20419
  "OTEL_EXPORTER_OTLP_PROTOCOL=http/json",
20245
20420
  "# Set NEAT_OTEL_TOKEN to the daemon's OTLP secret to authenticate exported spans (#410).",
20246
20421
  "# NEAT_OTEL_TOKEN=",
@@ -20306,7 +20481,7 @@ var NEXT_INSTRUMENTATION_EDGE_TS = `${NEXT_INSTRUMENTATION_EDGE_HEADER}
20306
20481
  import { registerOTel } from '@vercel/otel'
20307
20482
 
20308
20483
  process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
20309
- process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/v1/traces'
20484
+ process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
20310
20485
  ${OTEL_OTLP_PROTOCOL_JS}
20311
20486
  ${OTEL_OTLP_HEADERS_JS}
20312
20487