@neat.is/core 0.9.21-dev.20260924 → 0.9.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/neatd.cjs CHANGED
@@ -789,13 +789,13 @@ __export(neatd_exports, {
789
789
  module.exports = __toCommonJS(neatd_exports);
790
790
  init_cjs_shims();
791
791
  var import_node_fs43 = require("fs");
792
- var import_node_path80 = __toESM(require("path"), 1);
792
+ var import_node_path81 = __toESM(require("path"), 1);
793
793
  var import_node_module2 = require("module");
794
794
 
795
795
  // src/daemon.ts
796
796
  init_cjs_shims();
797
797
  var import_node_fs41 = require("fs");
798
- var import_node_path78 = __toESM(require("path"), 1);
798
+ var import_node_path79 = __toESM(require("path"), 1);
799
799
  var import_node_module = require("module");
800
800
 
801
801
  // src/graph.ts
@@ -8670,7 +8670,7 @@ function collectPhpSymbolDefs(root) {
8670
8670
  });
8671
8671
  };
8672
8672
  const dot = (s) => s.replace(/\\/g, ".").replace(/::/g, ".").replace(/^\.+/, "");
8673
- const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
8673
+ const join2 = (prefix, name) => prefix ? `${prefix}.${name}` : name;
8674
8674
  const walkChildren = (container, nsCtx, classCtx) => {
8675
8675
  let ns = nsCtx;
8676
8676
  for (let i = 0; i < container.namedChildCount; i++) {
@@ -8692,7 +8692,7 @@ function collectPhpSymbolDefs(root) {
8692
8692
  switch (node.type) {
8693
8693
  case "function_definition": {
8694
8694
  const name = node.childForFieldName("name")?.text;
8695
- if (name) push("function", join(nsCtx, name), node);
8695
+ if (name) push("function", join2(nsCtx, name), node);
8696
8696
  walkBody(node, nsCtx, void 0);
8697
8697
  return;
8698
8698
  }
@@ -8700,7 +8700,7 @@ function collectPhpSymbolDefs(root) {
8700
8700
  case "trait_declaration":
8701
8701
  case "interface_declaration": {
8702
8702
  const name = node.childForFieldName("name")?.text;
8703
- const full = name ? join(nsCtx, name) : classCtx;
8703
+ const full = name ? join2(nsCtx, name) : classCtx;
8704
8704
  if (name) push("class", full, node);
8705
8705
  walkBody(node, nsCtx, full);
8706
8706
  return;
@@ -8709,7 +8709,7 @@ function collectPhpSymbolDefs(root) {
8709
8709
  const name = node.childForFieldName("name")?.text;
8710
8710
  if (name) {
8711
8711
  const kind = name === "__construct" ? "constructor" : "method";
8712
- push(kind, join(classCtx ?? nsCtx, name), node);
8712
+ push(kind, join2(classCtx ?? nsCtx, name), node);
8713
8713
  }
8714
8714
  return;
8715
8715
  }
@@ -8735,7 +8735,7 @@ function collectCsharpSymbolDefs(root) {
8735
8735
  endLine: node.endPosition.row + 1
8736
8736
  });
8737
8737
  };
8738
- const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
8738
+ const join2 = (prefix, name) => prefix ? `${prefix}.${name}` : name;
8739
8739
  const walkChildren = (container, nsCtx, classCtx) => {
8740
8740
  let ns = nsCtx;
8741
8741
  for (let i = 0; i < container.namedChildCount; i++) {
@@ -8743,7 +8743,7 @@ function collectCsharpSymbolDefs(root) {
8743
8743
  if (!child) continue;
8744
8744
  if (child.type === "file_scoped_namespace_declaration") {
8745
8745
  const nameNode = child.childForFieldName("name");
8746
- if (nameNode) ns = join(nsCtx, nameNode.text);
8746
+ if (nameNode) ns = join2(nsCtx, nameNode.text);
8747
8747
  continue;
8748
8748
  }
8749
8749
  visit(child, ns, classCtx);
@@ -8757,7 +8757,7 @@ function collectCsharpSymbolDefs(root) {
8757
8757
  switch (node.type) {
8758
8758
  case "namespace_declaration": {
8759
8759
  const nameNode = node.childForFieldName("name");
8760
- const ns = nameNode ? join(nsCtx, nameNode.text) : nsCtx;
8760
+ const ns = nameNode ? join2(nsCtx, nameNode.text) : nsCtx;
8761
8761
  walkBody(node, ns, classCtx);
8762
8762
  return;
8763
8763
  }
@@ -8766,19 +8766,19 @@ function collectCsharpSymbolDefs(root) {
8766
8766
  case "struct_declaration":
8767
8767
  case "record_declaration": {
8768
8768
  const name = node.childForFieldName("name")?.text;
8769
- const full = name ? join(classCtx ?? nsCtx, name) : classCtx;
8769
+ const full = name ? join2(classCtx ?? nsCtx, name) : classCtx;
8770
8770
  if (name) push("class", full, node);
8771
8771
  walkBody(node, nsCtx, full);
8772
8772
  return;
8773
8773
  }
8774
8774
  case "method_declaration": {
8775
8775
  const name = node.childForFieldName("name")?.text;
8776
- if (name) push("method", join(classCtx ?? nsCtx, name), node);
8776
+ if (name) push("method", join2(classCtx ?? nsCtx, name), node);
8777
8777
  return;
8778
8778
  }
8779
8779
  case "constructor_declaration": {
8780
8780
  const name = node.childForFieldName("name")?.text;
8781
- if (name) push("constructor", join(classCtx ?? nsCtx, name), node);
8781
+ if (name) push("constructor", join2(classCtx ?? nsCtx, name), node);
8782
8782
  return;
8783
8783
  }
8784
8784
  }
@@ -8797,7 +8797,7 @@ function collectJavaSymbolDefs(root) {
8797
8797
  endLine: node.endPosition.row + 1
8798
8798
  });
8799
8799
  };
8800
- const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
8800
+ const join2 = (prefix, name) => prefix ? `${prefix}.${name}` : name;
8801
8801
  let pkg;
8802
8802
  for (let i = 0; i < root.namedChildCount; i++) {
8803
8803
  const child = root.namedChild(i);
@@ -8823,19 +8823,19 @@ function collectJavaSymbolDefs(root) {
8823
8823
  case "enum_declaration":
8824
8824
  case "record_declaration": {
8825
8825
  const name = node.childForFieldName("name")?.text;
8826
- const full = name ? join(classCtx ?? pkg, name) : classCtx;
8826
+ const full = name ? join2(classCtx ?? pkg, name) : classCtx;
8827
8827
  if (name) push("class", full, node);
8828
8828
  walkBody(node, full);
8829
8829
  return;
8830
8830
  }
8831
8831
  case "method_declaration": {
8832
8832
  const name = node.childForFieldName("name")?.text;
8833
- if (name) push("method", join(classCtx ?? pkg, name), node);
8833
+ if (name) push("method", join2(classCtx ?? pkg, name), node);
8834
8834
  return;
8835
8835
  }
8836
8836
  case "constructor_declaration": {
8837
8837
  const name = node.childForFieldName("name")?.text;
8838
- if (name) push("constructor", join(classCtx ?? pkg, name), node);
8838
+ if (name) push("constructor", join2(classCtx ?? pkg, name), node);
8839
8839
  return;
8840
8840
  }
8841
8841
  }
@@ -8854,7 +8854,7 @@ function collectKotlinSymbolDefs(root) {
8854
8854
  endLine: node.endPosition.row + 1
8855
8855
  });
8856
8856
  };
8857
- const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
8857
+ const join2 = (prefix, name) => prefix ? `${prefix}.${name}` : name;
8858
8858
  const firstChildOfType2 = (node, types) => {
8859
8859
  for (let i = 0; i < node.namedChildCount; i++) {
8860
8860
  const child = node.namedChild(i);
@@ -8883,7 +8883,7 @@ function collectKotlinSymbolDefs(root) {
8883
8883
  case "class_declaration":
8884
8884
  case "object_declaration": {
8885
8885
  const name = nameOf(node, "type_identifier");
8886
- const full = name ? join(classCtx ?? pkg, name) : classCtx;
8886
+ const full = name ? join2(classCtx ?? pkg, name) : classCtx;
8887
8887
  if (name) push("class", full, node);
8888
8888
  const body = bodyOf(node);
8889
8889
  if (body) walkChildren(body, full);
@@ -8891,20 +8891,20 @@ function collectKotlinSymbolDefs(root) {
8891
8891
  }
8892
8892
  case "companion_object": {
8893
8893
  const name = nameOf(node, "type_identifier");
8894
- const full = name ? join(classCtx ?? pkg, name) : classCtx ?? pkg;
8894
+ const full = name ? join2(classCtx ?? pkg, name) : classCtx ?? pkg;
8895
8895
  const body = bodyOf(node);
8896
8896
  if (body) walkChildren(body, full);
8897
8897
  return;
8898
8898
  }
8899
8899
  case "function_declaration": {
8900
8900
  const name = nameOf(node, "simple_identifier");
8901
- if (name) push(classCtx ? "method" : "function", join(classCtx ?? pkg, name), node);
8901
+ if (name) push(classCtx ? "method" : "function", join2(classCtx ?? pkg, name), node);
8902
8902
  return;
8903
8903
  }
8904
8904
  case "secondary_constructor": {
8905
8905
  if (classCtx) {
8906
8906
  const typeName = classCtx.slice(classCtx.lastIndexOf(".") + 1);
8907
- push("constructor", join(classCtx, typeName), node);
8907
+ push("constructor", join2(classCtx, typeName), node);
8908
8908
  }
8909
8909
  return;
8910
8910
  }
@@ -8924,14 +8924,14 @@ function collectRustSymbolDefs(root) {
8924
8924
  endLine: node.endPosition.row + 1
8925
8925
  });
8926
8926
  };
8927
- const join = (prefix, name) => prefix ? `${prefix}::${name}` : name;
8927
+ const join2 = (prefix, name) => prefix ? `${prefix}::${name}` : name;
8928
8928
  const walkTypeBody = (body, typeCtx) => {
8929
8929
  for (let i = 0; i < body.namedChildCount; i++) {
8930
8930
  const child = body.namedChild(i);
8931
8931
  if (!child) continue;
8932
8932
  if (child.type === "function_item" || child.type === "function_signature_item") {
8933
8933
  const name = child.childForFieldName("name")?.text;
8934
- if (name) push("method", join(typeCtx, name), child);
8934
+ if (name) push("method", join2(typeCtx, name), child);
8935
8935
  }
8936
8936
  }
8937
8937
  };
@@ -8945,7 +8945,7 @@ function collectRustSymbolDefs(root) {
8945
8945
  switch (node.type) {
8946
8946
  case "mod_item": {
8947
8947
  const name = node.childForFieldName("name")?.text;
8948
- const full = name ? join(modCtx, name) : modCtx;
8948
+ const full = name ? join2(modCtx, name) : modCtx;
8949
8949
  const body = node.childForFieldName("body");
8950
8950
  if (body) walkChildren(body, full);
8951
8951
  return;
@@ -8953,12 +8953,12 @@ function collectRustSymbolDefs(root) {
8953
8953
  case "struct_item":
8954
8954
  case "enum_item": {
8955
8955
  const name = node.childForFieldName("name")?.text;
8956
- if (name) push("class", join(modCtx, name), node);
8956
+ if (name) push("class", join2(modCtx, name), node);
8957
8957
  return;
8958
8958
  }
8959
8959
  case "trait_item": {
8960
8960
  const name = node.childForFieldName("name")?.text;
8961
- const full = name ? join(modCtx, name) : modCtx;
8961
+ const full = name ? join2(modCtx, name) : modCtx;
8962
8962
  if (name) push("class", full, node);
8963
8963
  const body = node.childForFieldName("body");
8964
8964
  if (body && full) walkTypeBody(body, full);
@@ -8966,14 +8966,14 @@ function collectRustSymbolDefs(root) {
8966
8966
  }
8967
8967
  case "impl_item": {
8968
8968
  const typeName = node.childForFieldName("type")?.text;
8969
- const full = typeName ? join(modCtx, typeName) : modCtx;
8969
+ const full = typeName ? join2(modCtx, typeName) : modCtx;
8970
8970
  const body = node.childForFieldName("body");
8971
8971
  if (body && full) walkTypeBody(body, full);
8972
8972
  return;
8973
8973
  }
8974
8974
  case "function_item": {
8975
8975
  const name = node.childForFieldName("name")?.text;
8976
- if (name) push("function", join(modCtx, name), node);
8976
+ if (name) push("function", join2(modCtx, name), node);
8977
8977
  return;
8978
8978
  }
8979
8979
  }
@@ -8992,7 +8992,7 @@ function collectCppSymbolDefs(root) {
8992
8992
  endLine: node.endPosition.row + 1
8993
8993
  });
8994
8994
  };
8995
- const join = (prefix, name) => prefix ? `${prefix}::${name}` : name;
8995
+ const join2 = (prefix, name) => prefix ? `${prefix}::${name}` : name;
8996
8996
  const DECLARATOR_WRAPPERS = /* @__PURE__ */ new Set([
8997
8997
  "function_declarator",
8998
8998
  "pointer_declarator",
@@ -9018,7 +9018,7 @@ function collectCppSymbolDefs(root) {
9018
9018
  return void 0;
9019
9019
  };
9020
9020
  const classify = (nameText, prefix, enclosingClass) => {
9021
- const qualname = join(prefix, nameText);
9021
+ const qualname = join2(prefix, nameText);
9022
9022
  let className;
9023
9023
  let memberName;
9024
9024
  const idx = nameText.lastIndexOf("::");
@@ -9047,7 +9047,7 @@ function collectCppSymbolDefs(root) {
9047
9047
  switch (node.type) {
9048
9048
  case "namespace_definition": {
9049
9049
  const name = node.childForFieldName("name")?.text;
9050
- const newPrefix = name ? join(prefix, name) : prefix;
9050
+ const newPrefix = name ? join2(prefix, name) : prefix;
9051
9051
  const body = node.childForFieldName("body");
9052
9052
  if (body) walkChildren(body, newPrefix, void 0);
9053
9053
  return;
@@ -9055,7 +9055,7 @@ function collectCppSymbolDefs(root) {
9055
9055
  case "class_specifier":
9056
9056
  case "struct_specifier": {
9057
9057
  const name = node.childForFieldName("name")?.text;
9058
- const full = name ? join(prefix, name) : prefix;
9058
+ const full = name ? join2(prefix, name) : prefix;
9059
9059
  if (name) push("class", full, node);
9060
9060
  const body = node.childForFieldName("body");
9061
9061
  if (body && name) walkChildren(body, full, name);
@@ -16483,7 +16483,7 @@ var Projects = class {
16483
16483
  init_cjs_shims();
16484
16484
  var import_fastify2 = __toESM(require("fastify"), 1);
16485
16485
  var import_cors = __toESM(require("@fastify/cors"), 1);
16486
- var import_types100 = require("@neat.is/types");
16486
+ var import_types101 = require("@neat.is/types");
16487
16487
 
16488
16488
  // src/extend/index.ts
16489
16489
  init_cjs_shims();
@@ -17713,7 +17713,7 @@ var INTENT_RULES = [
17713
17713
  },
17714
17714
  {
17715
17715
  intent: "blast-radius",
17716
- test: /\b(blast|break[\s-]?if|breaks[\s-]?if|impact|downstream|dependents?|redeploy|who\s+(?:uses|calls|depends)|what\s+depends\s+on|affect(?:s|ed)?)\b/
17716
+ test: /\b(blast|break[\s-]?if|breaks[\s-]?if|impact|downstream|dependents?|redeploy|who\s+(?:uses|calls|depends(?:\s+on)?)|what\s+depends\s+on|consumers?\s+of|callers?\s+of|affect(?:s|ed)?)\b/
17717
17717
  },
17718
17718
  {
17719
17719
  intent: "divergence",
@@ -17725,11 +17725,11 @@ var INTENT_RULES = [
17725
17725
  },
17726
17726
  {
17727
17727
  intent: "observed",
17728
- test: /\b(at\s+runtime|in\s+prod(?:uction)?|actually\s+call\w*|really\s+call\w*|observed|runtime\s+traffic)\b/
17728
+ test: /\b(at\s+runtime|in\s+prod(?:uction)?|actually|really\s+call\w*|observed|runtime\s+traffic|slow|latency|p95|timing)\b/
17729
17729
  },
17730
17730
  {
17731
17731
  intent: "dependencies",
17732
- test: /\b(depend\w*|calls?|uses?|imports?|relies\s+on|needs?)\b/
17732
+ test: /\b(depend\w*|calls?|uses?|imports?|relies\s+on|needs?|talks?\s+to|connects?\s+to|hits?|reads?\s+from|writes?\s+to)\b/
17733
17733
  }
17734
17734
  ];
17735
17735
  function classifyIntent(question) {
@@ -17896,7 +17896,25 @@ var INTENT_WORDS = /* @__PURE__ */ new Set([
17896
17896
  "rely",
17897
17897
  "needs",
17898
17898
  "need",
17899
- "sync"
17899
+ "sync",
17900
+ "talk",
17901
+ "talks",
17902
+ "connect",
17903
+ "connects",
17904
+ "hit",
17905
+ "hits",
17906
+ "read",
17907
+ "reads",
17908
+ "write",
17909
+ "writes",
17910
+ "consumer",
17911
+ "consumers",
17912
+ "caller",
17913
+ "callers",
17914
+ "slow",
17915
+ "latency",
17916
+ "p95",
17917
+ "timing"
17900
17918
  ]);
17901
17919
  function tokens(input) {
17902
17920
  const spaced = input.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
@@ -17920,6 +17938,13 @@ function idBody(id) {
17920
17938
  const colon = id.indexOf(":");
17921
17939
  return colon >= 0 ? id.slice(colon + 1) : id;
17922
17940
  }
17941
+ var TYPE_NOUNS = [
17942
+ { nouns: ["database", "db", "table"], type: import_types62.NodeType.DatabaseNode },
17943
+ { nouns: ["service", "api", "backend"], type: import_types62.NodeType.ServiceNode },
17944
+ { nouns: ["route", "endpoint"], type: import_types62.NodeType.RouteNode },
17945
+ { nouns: ["file"], type: import_types62.NodeType.FileNode },
17946
+ { nouns: ["config"], type: import_types62.NodeType.ConfigNode }
17947
+ ];
17923
17948
  var EMBED_MIN_SCORE = 0.35;
17924
17949
  async function resolveEntities(graph, question, searchIndex, maxNodes) {
17925
17950
  const qTokens = queryTokens(question);
@@ -17927,6 +17952,7 @@ async function resolveEntities(graph, question, searchIndex, maxNodes) {
17927
17952
  const best = /* @__PURE__ */ new Map();
17928
17953
  const consider = (cand) => {
17929
17954
  const cur = best.get(cand.nodeId);
17955
+ if (cur?.via === "id" && cand.via === "embedding") return;
17930
17956
  if (!cur || cand.score > cur.score) best.set(cand.nodeId, cand);
17931
17957
  };
17932
17958
  graph.forEachNode((id, attrs) => {
@@ -17936,12 +17962,12 @@ async function resolveEntities(graph, question, searchIndex, maxNodes) {
17936
17962
  const body = idBody(id);
17937
17963
  const labelTokens = /* @__PURE__ */ new Set([...tokens(body), ...tokens(name)]);
17938
17964
  if (labelTokens.size === 0) return;
17939
- let matched = 0;
17940
- for (const t of qTokens) if (labelTokens.has(t)) matched += 1;
17965
+ let matched2 = 0;
17966
+ for (const t of qTokens) if (labelTokens.has(t)) matched2 += 1;
17941
17967
  const named2 = body.length >= 2 && normalized.includes(body.toLowerCase()) || name.length >= 2 && normalized.includes(name.toLowerCase());
17942
- if (matched === 0 && !named2) return;
17943
- const coverage = qTokens.length > 0 ? matched / qTokens.length : 0;
17944
- const via = named2 ? "id" : matched > 0 && tokens(name).some((t) => qTokens.includes(t)) ? "label" : "token";
17968
+ if (matched2 === 0 && !named2) return;
17969
+ const coverage = qTokens.length > 0 ? matched2 / qTokens.length : 0;
17970
+ const via = named2 ? "id" : matched2 > 0 && tokens(name).some((t) => qTokens.includes(t)) ? "label" : "token";
17945
17971
  const score = named2 ? Math.max(0.9, coverage) : Math.min(0.85, 0.3 + 0.7 * coverage);
17946
17972
  consider({ nodeId: id, label: name, via, score: Math.min(1, score) });
17947
17973
  });
@@ -17951,6 +17977,7 @@ async function resolveEntities(graph, question, searchIndex, maxNodes) {
17951
17977
  if (res.provider !== "substring") {
17952
17978
  for (const m of res.matches) {
17953
17979
  if (m.node.type === import_types62.NodeType.FrontierNode) continue;
17980
+ if (!graph.hasNode(m.node.id)) continue;
17954
17981
  const already = best.get(m.node.id);
17955
17982
  if (!already && m.score < EMBED_MIN_SCORE) continue;
17956
17983
  consider({
@@ -17964,10 +17991,31 @@ async function resolveEntities(graph, question, searchIndex, maxNodes) {
17964
17991
  } catch {
17965
17992
  }
17966
17993
  }
17967
- const viaRank = { id: 3, label: 2, token: 1, embedding: 0 };
17968
- return [...best.values()].sort(
17969
- (a, b) => b.score - a.score || viaRank[b.via] - viaRank[a.via] || a.nodeId.localeCompare(b.nodeId)
17994
+ if (best.size === 0) {
17995
+ const words = new Set(tokens(question));
17996
+ const kind = TYPE_NOUNS.find((entry2) => entry2.nouns.some((noun) => words.has(noun)));
17997
+ if (kind) {
17998
+ const ids = graph.filterNodes((_id, attrs) => attrs.type === kind.type).sort();
17999
+ if (ids.length > 1) {
18000
+ return { matched: [], ambiguousType: { noun: kind.nouns.find((noun) => words.has(noun)), ids } };
18001
+ }
18002
+ if (ids.length === 1) {
18003
+ const id = ids[0];
18004
+ return { matched: [{ nodeId: id, label: nodeName(graph.getNodeAttributes(id)), via: "type", score: 0.6 }].slice(0, maxNodes) };
18005
+ }
18006
+ return { matched: [] };
18007
+ }
18008
+ }
18009
+ const viaRank = { id: 4, label: 3, token: 2, type: 1, embedding: 0 };
18010
+ const ranked = [...best.values()].sort(
18011
+ (a, b) => Number(b.via === "id") - Number(a.via === "id") || b.score - a.score || viaRank[b.via] - viaRank[a.via] || a.nodeId.localeCompare(b.nodeId)
18012
+ );
18013
+ const primary = ranked[0];
18014
+ const primaryType = primary && graph.getNodeAttributes(primary.nodeId).type;
18015
+ const matched = ranked.filter(
18016
+ (candidate) => !primary || primary.via !== "id" || primary.score < 0.85 || candidate === primary || graph.getNodeAttributes(candidate.nodeId).type === primaryType || candidate.via !== "embedding" && candidate.score >= primary.score - 0.2
17970
18017
  ).slice(0, maxNodes).map((s) => ({ nodeId: s.nodeId, label: s.label, via: s.via, score: Number(s.score.toFixed(3)) }));
18018
+ return { matched };
17971
18019
  }
17972
18020
  function incidentsForNode(nodeId, incidents) {
17973
18021
  if (!incidents || incidents.length === 0) return [];
@@ -18059,6 +18107,23 @@ function buildBlastSection(graph, node) {
18059
18107
  facts
18060
18108
  };
18061
18109
  }
18110
+ function buildDatabaseCallersSection(graph, node) {
18111
+ const callers = getBlastRadius(graph, node).affectedNodes.filter((item) => {
18112
+ if (graph.getNodeAttributes(item.nodeId).type !== import_types62.NodeType.ServiceNode) return false;
18113
+ return item.path.slice(1, -1).every((id) => {
18114
+ const type = graph.getNodeAttributes(id).type;
18115
+ return type === import_types62.NodeType.FileNode || type === import_types62.NodeType.SymbolNode;
18116
+ });
18117
+ });
18118
+ return {
18119
+ heading: `Services calling ${node} \u2014 ${callers.length}`,
18120
+ facts: callers.length > 0 ? callers.slice(0, MAX_FACTS_PER_SECTION).map((item) => ({
18121
+ text: `${item.nodeId} (distance ${item.distance})`,
18122
+ provenance: item.edgeProvenance,
18123
+ confidence: item.confidence
18124
+ })) : [{ text: `No inbound service callers found for ${node}.` }]
18125
+ };
18126
+ }
18062
18127
  function buildIncidentsSection(node, incidents) {
18063
18128
  const relevant = incidentsForNode(node, incidents);
18064
18129
  if (relevant.length === 0) return null;
@@ -18085,6 +18150,29 @@ function divergenceLine(d) {
18085
18150
  }
18086
18151
  return `[${d.type}] ${d.source} \u2192 ${d.target} \u2014 ${d.reason}`;
18087
18152
  }
18153
+ function runtimeEdgeState(graph) {
18154
+ let observed = false;
18155
+ let stale = false;
18156
+ graph.forEachEdge((_id, attrs) => {
18157
+ if (attrs.provenance === import_types62.Provenance.OBSERVED) observed = true;
18158
+ if (attrs.provenance === import_types62.Provenance.STALE) stale = true;
18159
+ });
18160
+ return { observed, stale };
18161
+ }
18162
+ function pendingRuntimeMessage(graph, divergences) {
18163
+ const count = divergences.filter((d) => d.type === "missing-observed").length;
18164
+ if (count === 0) return void 0;
18165
+ const { observed, stale } = runtimeEdgeState(graph);
18166
+ if (observed) return void 0;
18167
+ const prefix = stale ? "No current runtime observations \u2014 earlier runtime evidence is stale" : "No runtime observed yet";
18168
+ return `${prefix} \u2014 ${count} declared dependenc${count === 1 ? "y is" : "ies are"} waiting to be confirmed. Send traces (or connect a provider) and ask again.`;
18169
+ }
18170
+ function noDivergenceMessage(graph) {
18171
+ if (graph.order === 0) return "Graph is empty \u2014 no declared or observed dependencies to compare.";
18172
+ const { observed, stale } = runtimeEdgeState(graph);
18173
+ if (!observed) return stale ? "No current runtime dependency observations to compare \u2014 earlier runtime evidence is stale." : "No runtime dependency observations to compare with declared code.";
18174
+ return "None \u2014 declared code and observed runtime agree across the graph.";
18175
+ }
18088
18176
  function buildDivergenceSection(graph, node, incidents) {
18089
18177
  const result = computeDivergences(graph, { node, ...incidents ? { incidents } : {} });
18090
18178
  if (result.totalAffected === 0) return null;
@@ -18101,19 +18189,28 @@ function buildDivergenceSection(graph, node, incidents) {
18101
18189
  }
18102
18190
  function buildGlobalDivergenceSection(graph, incidents) {
18103
18191
  const result = computeDivergences(graph, incidents ? { incidents } : {});
18192
+ const pending = pendingRuntimeMessage(graph, result.divergences);
18193
+ const visible = pending ? result.divergences.filter((d) => d.type !== "missing-observed") : result.divergences;
18194
+ if (visible.length === 0 && pending) {
18195
+ return {
18196
+ heading: "Divergences (EXTRACTED vs OBSERVED)",
18197
+ facts: [{ text: pending }]
18198
+ };
18199
+ }
18104
18200
  if (result.totalAffected === 0) {
18105
18201
  return {
18106
18202
  heading: "Divergences (EXTRACTED vs OBSERVED)",
18107
- facts: [{ text: "None \u2014 declared code and observed runtime agree across the graph." }]
18203
+ facts: [{ text: noDivergenceMessage(graph) }]
18108
18204
  };
18109
18205
  }
18110
- const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
18206
+ const facts = visible.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
18111
18207
  text: divergenceLine(d),
18112
18208
  confidence: d.confidence
18113
18209
  // Composite by construction (EXTRACTED vs OBSERVED), so no single provenance.
18114
18210
  }));
18211
+ if (pending && facts.length < MAX_FACTS_PER_SECTION) facts.push({ text: pending });
18115
18212
  return {
18116
- heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
18213
+ heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${visible.length}`,
18117
18214
  facts
18118
18215
  };
18119
18216
  }
@@ -18199,13 +18296,12 @@ function buildOverviewSections(graph, incidents) {
18199
18296
  });
18200
18297
  }
18201
18298
  const div = computeDivergences(graph, incidents ? { incidents } : {});
18299
+ const pending = pendingRuntimeMessage(graph, div.divergences);
18300
+ const visibleCount = pending ? div.divergences.filter((d) => d.type !== "missing-observed").length : div.totalAffected;
18301
+ const divergenceText = visibleCount > 0 ? `${visibleCount} divergence${visibleCount === 1 ? "" : "s"} between declared code and observed runtime \u2014 ask "are there any divergences?" for the list.${pending ? ` ${pending}` : ""}` : pending ?? noDivergenceMessage(graph);
18202
18302
  sections.push({
18203
18303
  heading: "Divergences",
18204
- facts: [
18205
- {
18206
- text: div.totalAffected === 0 ? "None \u2014 declared code and observed runtime agree across the graph." : `${div.totalAffected} divergence${div.totalAffected === 1 ? "" : "s"} between declared code and observed runtime \u2014 ask "are there any divergences?" for the list.`
18207
- }
18208
- ]
18304
+ facts: [{ text: divergenceText }]
18209
18305
  });
18210
18306
  return sections;
18211
18307
  }
@@ -18250,6 +18346,8 @@ function summarizeGlobal(intent, sections) {
18250
18346
  const lead = sections[0];
18251
18347
  switch (intent) {
18252
18348
  case "divergence": {
18349
+ const pending = lead?.facts[0]?.text;
18350
+ if (pending?.startsWith("No runtime") || pending?.startsWith("No current runtime") || pending?.startsWith("Graph is empty")) return pending;
18253
18351
  const none = lead?.facts[0]?.text.startsWith("None") ?? true;
18254
18352
  return none ? "No divergences across the graph \u2014 declared code and observed runtime agree." : `${lead.heading} across the graph, highest-confidence first below.`;
18255
18353
  }
@@ -18262,11 +18360,16 @@ function summarizeGlobal(intent, sections) {
18262
18360
  return `System overview across the whole graph: ${sections.length} view${sections.length === 1 ? "" : "s"} below \u2014 shape (nodes/edges by provenance), busiest services, incidents, and divergences.`;
18263
18361
  }
18264
18362
  }
18265
- function summarize(question, intent, matched, primary, sections, scope) {
18363
+ function summarize(question, intent, matched, primary, sections, scope, ambiguousType) {
18266
18364
  if (scope === "global") {
18267
18365
  return summarizeGlobal(intent, sections);
18268
18366
  }
18269
18367
  if (!primary) {
18368
+ if (ambiguousType) {
18369
+ const shown = ambiguousType.ids.slice(0, 10);
18370
+ const more = ambiguousType.ids.length - shown.length;
18371
+ return `"${ambiguousType.noun}" matches ${ambiguousType.ids.length} nodes. Name one by ID: ${shown.join(", ")}${more > 0 ? `, and ${more} more` : ""}.`;
18372
+ }
18270
18373
  return `Nothing in "${question}" resolved to a node in the graph. Name a service, file, route, or table \u2014 e.g. \`ask "what does <service> depend on?"\`. For a graph-wide look, ask for an overview, divergences, or incidents.`;
18271
18374
  }
18272
18375
  const lead = sections[0];
@@ -18289,10 +18392,13 @@ function summarize(question, intent, matched, primary, sections, scope) {
18289
18392
  core = lead ? `${lead.heading} of ${primary}.` : `${primary} has no dependents \u2014 nothing else would break if it failed.`;
18290
18393
  break;
18291
18394
  case "dependencies":
18292
- core = lead ? `${primary}: ${lead.heading.toLowerCase()} listed below.` : `${primary} has no declared dependencies in the graph.`;
18395
+ core = lead?.heading.startsWith("Services calling") ? `${lead.heading}.` : lead ? `${primary}: ${lead.heading.toLowerCase()} listed below.` : `${primary} has no declared dependencies in the graph.`;
18293
18396
  break;
18294
18397
  case "observed":
18295
- core = lead ? `${primary} at runtime: ${lead.facts.length} OBSERVED fact${lead.facts.length === 1 ? "" : "s"}.` : `No runtime traffic OBSERVED for ${primary}.`;
18398
+ {
18399
+ const observed = sections.find((s) => s.heading === "Runtime dependencies (OBSERVED)");
18400
+ core = observed ? `${primary} at runtime: ${observed.facts.length} OBSERVED fact${observed.facts.length === 1 ? "" : "s"}.` : `No runtime traffic OBSERVED for ${primary}.`;
18401
+ }
18296
18402
  break;
18297
18403
  case "incidents":
18298
18404
  core = lead ? `${primary}: ${lead.heading.toLowerCase()}.` : `No incidents recorded against ${primary}.`;
@@ -18309,24 +18415,28 @@ async function askGraph(graph, question, opts = {}) {
18309
18415
  const now = opts.now ?? Date.now();
18310
18416
  const maxNodes = opts.maxNodes ?? DEFAULT_MAX_NODES;
18311
18417
  const intent = classifyIntent(question);
18312
- const matched = await resolveEntities(graph, question, opts.searchIndex, maxNodes);
18418
+ const resolution = await resolveEntities(graph, question, opts.searchIndex, maxNodes);
18419
+ const matched = resolution.matched;
18313
18420
  const primary = matched[0]?.nodeId;
18314
18421
  const sections = [];
18315
18422
  let scope;
18316
18423
  if (primary) {
18317
18424
  scope = "node";
18425
+ const callersQuestion = intent === "dependencies" && graph.getNodeAttributes(primary).type === import_types62.NodeType.DatabaseNode && /\b(?:which|what)\s+services?\s+(?:talks?\s+to|connects?\s+to|uses?|hits?|calls?|reads?\s+from|writes?\s+to)\b/i.test(question);
18426
+ if (callersQuestion) sections.push(buildDatabaseCallersSection(graph, primary));
18318
18427
  for (const kind of SECTION_ORDER[intent]) {
18428
+ if (callersQuestion && kind === "blast") continue;
18319
18429
  const s = buildSection(kind, graph, primary, opts.incidents, now);
18320
18430
  if (s && s.facts.length > 0) sections.push(s);
18321
18431
  }
18322
18432
  } else {
18323
- const global = buildGlobalSections(intent, graph, opts.incidents);
18433
+ const global = resolution.ambiguousType ? null : buildGlobalSections(intent, graph, opts.incidents);
18324
18434
  if (global) {
18325
18435
  scope = "global";
18326
18436
  for (const s of global) if (s.facts.length > 0) sections.push(s);
18327
18437
  }
18328
18438
  }
18329
- const answer = summarize(question, intent, matched, primary, sections, scope);
18439
+ const answer = summarize(question, intent, matched, primary, sections, scope, resolution.ambiguousType);
18330
18440
  const provSet = /* @__PURE__ */ new Set();
18331
18441
  for (const s of sections) for (const f of s.facts) if (f.provenance) provSet.add(f.provenance);
18332
18442
  const confidence = sections[0]?.facts[0]?.confidence;
@@ -20154,9 +20264,9 @@ var RESOURCE_TYPES = [
20154
20264
  function isFirebaseResourceType(value) {
20155
20265
  return RESOURCE_TYPES.includes(value);
20156
20266
  }
20157
- function buildEntriesFilter(sinceIso) {
20267
+ function buildEntriesFilter(sinceIso, resourceTypes = RESOURCE_TYPES) {
20158
20268
  return [
20159
- 'resource.type = ("cloud_function" OR "cloud_run_revision" OR "firebase_domain")',
20269
+ `resource.type = (${resourceTypes.map((t) => `"${t}"`).join(" OR ")})`,
20160
20270
  "httpRequest:*",
20161
20271
  `timestamp >= "${sinceIso}"`
20162
20272
  ].join(" AND ");
@@ -20165,8 +20275,8 @@ var DEFAULT_LOOKBACK_MS = 24 * 60 * 60 * 1e3;
20165
20275
  var ENTRIES_LIST_URL = "https://logging.googleapis.com/v2/entries:list";
20166
20276
  var PAGE_SIZE = 1e3;
20167
20277
  var MAX_PAGES = 20;
20168
- async function fetchHttpRequestLogEntries(creds, sinceIso) {
20169
- const filter = buildEntriesFilter(sinceIso);
20278
+ async function fetchHttpRequestLogEntries(creds, sinceIso, resourceTypes) {
20279
+ const filter = buildEntriesFilter(sinceIso, resourceTypes);
20170
20280
  const out = [];
20171
20281
  let pageToken;
20172
20282
  for (let page = 0; page < MAX_PAGES; page++) {
@@ -20281,7 +20391,38 @@ function mapLogEntriesToSignals(entries) {
20281
20391
 
20282
20392
  // src/connectors/firebase/resolve.ts
20283
20393
  init_cjs_shims();
20394
+ var import_types72 = require("@neat.is/types");
20395
+
20396
+ // src/connectors/infer-service.ts
20397
+ init_cjs_shims();
20284
20398
  var import_types71 = require("@neat.is/types");
20399
+ function normalizeName(name) {
20400
+ return name.toLowerCase().replace(/[^a-z0-9]/g, "");
20401
+ }
20402
+ function inferServiceName(graph, resourceName, method, path81) {
20403
+ const wantedMethod = method.toUpperCase();
20404
+ const wantedPath = normalizePathTemplate(path81);
20405
+ const services = [];
20406
+ const owners = /* @__PURE__ */ new Set();
20407
+ graph.forEachNode((_id, attrs) => {
20408
+ const node = attrs;
20409
+ if (node.type === import_types71.NodeType.ServiceNode && typeof node.name === "string") {
20410
+ services.push(node.name);
20411
+ return;
20412
+ }
20413
+ if (node.type !== import_types71.NodeType.RouteNode) return;
20414
+ const route = attrs;
20415
+ if (!route.pathTemplate || normalizePathTemplate(route.pathTemplate) !== wantedPath) return;
20416
+ const routeMethod = route.method.toUpperCase();
20417
+ if (routeMethod === "ALL" || routeMethod === wantedMethod) owners.add(route.service);
20418
+ });
20419
+ const wanted = normalizeName(resourceName);
20420
+ const byName = services.find((s) => normalizeName(s) === wanted);
20421
+ if (byName) return byName;
20422
+ return owners.size === 1 ? [...owners][0] : null;
20423
+ }
20424
+
20425
+ // src/connectors/firebase/resolve.ts
20285
20426
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
20286
20427
  switch (resourceType) {
20287
20428
  case "cloud_function":
@@ -20296,7 +20437,7 @@ function routeEntriesFor(graph, serviceName) {
20296
20437
  const entries = [];
20297
20438
  graph.forEachNode((_id, attrs) => {
20298
20439
  const node = attrs;
20299
- if (node.type !== import_types71.NodeType.RouteNode) return;
20440
+ if (node.type !== import_types72.NodeType.RouteNode) return;
20300
20441
  const route = attrs;
20301
20442
  if (route.service !== serviceName) return;
20302
20443
  entries.push({
@@ -20320,32 +20461,38 @@ function createFirebaseResolveTarget(graph, serviceMap) {
20320
20461
  }
20321
20462
  const identity = parseFirebaseTargetName(signal.targetName);
20322
20463
  if (!identity) return null;
20323
- const serviceName = neatServiceNameFor(resourceType, identity.resourceName, serviceMap);
20324
- if (!serviceName) return null;
20325
20464
  const normalizedPath = normalizePathTemplate(identity.path);
20465
+ const serviceName = neatServiceNameFor(resourceType, identity.resourceName, serviceMap) ?? (serviceMap.inferServices ? inferServiceName(graph, identity.resourceName, identity.method, identity.path) : null);
20466
+ if (!serviceName) return null;
20326
20467
  const match = findRoute2(routeEntriesFor(graph, serviceName), identity.method, normalizedPath);
20327
20468
  if (!match) return null;
20328
20469
  return {
20329
20470
  targetNodeId: match.routeNodeId,
20330
20471
  serviceName,
20331
- edgeType: import_types71.EdgeType.CALLS
20472
+ edgeType: import_types72.EdgeType.CALLS
20332
20473
  };
20333
20474
  };
20334
20475
  }
20335
20476
 
20336
20477
  // src/connectors/firebase/index.ts
20337
20478
  var FirebaseConnector = class {
20479
+ constructor(opts = {}) {
20480
+ this.opts = opts;
20481
+ }
20482
+ opts;
20338
20483
  provider = "firebase";
20339
20484
  async poll(ctx) {
20340
20485
  const creds = readFirebaseCredentials(ctx.credentials);
20341
20486
  const sinceIso = ctx.since ?? new Date(Date.now() - DEFAULT_LOOKBACK_MS).toISOString();
20342
- const entries = await fetchHttpRequestLogEntries(creds, sinceIso);
20487
+ const entries = await fetchHttpRequestLogEntries(creds, sinceIso, this.opts.resourceTypes);
20343
20488
  return mapLogEntriesToSignals(entries);
20344
20489
  }
20345
20490
  };
20346
20491
  function createFirebaseConnector(graph, serviceMap) {
20347
20492
  return {
20348
- connector: new FirebaseConnector(),
20493
+ connector: new FirebaseConnector(
20494
+ serviceMap.excludeCloudRun ? { resourceTypes: ["cloud_function", "firebase_domain"] } : {}
20495
+ ),
20349
20496
  resolveTarget: createFirebaseResolveTarget(graph, serviceMap)
20350
20497
  };
20351
20498
  }
@@ -20355,7 +20502,7 @@ init_cjs_shims();
20355
20502
 
20356
20503
  // src/connectors/cloudflare/connector.ts
20357
20504
  init_cjs_shims();
20358
- var import_types73 = require("@neat.is/types");
20505
+ var import_types74 = require("@neat.is/types");
20359
20506
 
20360
20507
  // src/connectors/cloudflare/client.ts
20361
20508
  init_cjs_shims();
@@ -20519,7 +20666,7 @@ function findTaggedWorkerFileNode(graph, workerName) {
20519
20666
  graph.forEachNode((id, attrs) => {
20520
20667
  if (found) return;
20521
20668
  const a = attrs;
20522
- if (a.type === import_types73.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
20669
+ if (a.type === import_types74.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
20523
20670
  found = id;
20524
20671
  }
20525
20672
  });
@@ -20531,7 +20678,7 @@ function findMatchingRouteNode(graph, serviceName, method, path81) {
20531
20678
  graph.forEachNode((id, attrs) => {
20532
20679
  if (found) return;
20533
20680
  const a = attrs;
20534
- if (a.type !== import_types73.NodeType.RouteNode || a.service !== serviceName) return;
20681
+ if (a.type !== import_types74.NodeType.RouteNode || a.service !== serviceName) return;
20535
20682
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
20536
20683
  const routeMethod = (a.method ?? "").toUpperCase();
20537
20684
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -20550,11 +20697,11 @@ function createCloudflareResolveTarget(config, graph) {
20550
20697
  };
20551
20698
  const mapping = config.workers?.[scriptName];
20552
20699
  if (mapping) {
20553
- const wholeFileId = (0, import_types73.fileId)(mapping.service, mapping.entryFile);
20700
+ const wholeFileId = (0, import_types74.fileId)(mapping.service, mapping.entryFile);
20554
20701
  return {
20555
20702
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
20556
20703
  serviceName: mapping.service,
20557
- edgeType: import_types73.EdgeType.CALLS
20704
+ edgeType: import_types74.EdgeType.CALLS
20558
20705
  };
20559
20706
  }
20560
20707
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -20563,13 +20710,13 @@ function createCloudflareResolveTarget(config, graph) {
20563
20710
  return {
20564
20711
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
20565
20712
  serviceName: fileNode.service,
20566
- edgeType: import_types73.EdgeType.CALLS
20713
+ edgeType: import_types74.EdgeType.CALLS
20567
20714
  };
20568
20715
  }
20569
20716
  return {
20570
- targetNodeId: (0, import_types73.infraId)("cloudflare-worker", scriptName),
20717
+ targetNodeId: (0, import_types74.infraId)("cloudflare-worker", scriptName),
20571
20718
  serviceName: scriptName,
20572
- edgeType: import_types73.EdgeType.CALLS,
20719
+ edgeType: import_types74.EdgeType.CALLS,
20573
20720
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
20574
20721
  };
20575
20722
  };
@@ -20765,14 +20912,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
20765
20912
 
20766
20913
  // src/connectors/neon/resolve.ts
20767
20914
  init_cjs_shims();
20768
- var import_types77 = require("@neat.is/types");
20915
+ var import_types78 = require("@neat.is/types");
20769
20916
  function createNeonResolveTarget(config) {
20770
20917
  return (signal) => {
20771
20918
  if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
20772
20919
  return {
20773
- targetNodeId: (0, import_types77.infraId)("sql-table", signal.targetName),
20920
+ targetNodeId: (0, import_types78.infraId)("sql-table", signal.targetName),
20774
20921
  serviceName: config.serviceName,
20775
- edgeType: import_types77.EdgeType.CALLS,
20922
+ edgeType: import_types78.EdgeType.CALLS,
20776
20923
  ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
20777
20924
  };
20778
20925
  };
@@ -20953,14 +21100,14 @@ function mapLogEntriesToSignals2(entries) {
20953
21100
 
20954
21101
  // src/connectors/cloud-run/resolve.ts
20955
21102
  init_cjs_shims();
20956
- var import_types81 = require("@neat.is/types");
21103
+ var import_types82 = require("@neat.is/types");
20957
21104
  var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
20958
21105
  function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
20959
21106
  let found = null;
20960
21107
  graph.forEachNode((_id, attrs) => {
20961
21108
  if (found) return;
20962
21109
  const node = attrs;
20963
- if (node.type !== import_types81.NodeType.RouteNode) return;
21110
+ if (node.type !== import_types82.NodeType.RouteNode) return;
20964
21111
  const route = attrs;
20965
21112
  if (route.service !== serviceName || !route.pathTemplate) return;
20966
21113
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -20976,7 +21123,7 @@ function createCloudRunResolveTarget(graph, config) {
20976
21123
  const identity = parseCloudRunTargetName(signal.targetName);
20977
21124
  if (!identity) return null;
20978
21125
  const { serviceName: gcpServiceName, method, path: path81 } = identity;
20979
- const mappedService = config.serviceMap?.[gcpServiceName];
21126
+ const mappedService = config.serviceMap?.[gcpServiceName] ?? (config.inferServices ? inferServiceName(graph, gcpServiceName, method, path81) ?? void 0 : void 0);
20980
21127
  if (mappedService) {
20981
21128
  const routeNodeId = findMatchingRouteNode2(
20982
21129
  graph,
@@ -20985,13 +21132,13 @@ function createCloudRunResolveTarget(graph, config) {
20985
21132
  normalizePathTemplate(path81)
20986
21133
  );
20987
21134
  if (routeNodeId) {
20988
- return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types81.EdgeType.CALLS };
21135
+ return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types82.EdgeType.CALLS };
20989
21136
  }
20990
21137
  }
20991
21138
  return {
20992
- targetNodeId: (0, import_types81.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
21139
+ targetNodeId: (0, import_types82.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
20993
21140
  serviceName: mappedService ?? gcpServiceName,
20994
- edgeType: import_types81.EdgeType.CALLS,
21141
+ edgeType: import_types82.EdgeType.CALLS,
20995
21142
  ensureInfraNode: {
20996
21143
  kind: CLOUD_RUN_SERVICE_INFRA_KIND,
20997
21144
  name: gcpServiceName,
@@ -21173,14 +21320,14 @@ function mapLogEntriesToSignals3(entries) {
21173
21320
 
21174
21321
  // src/connectors/gcp-lb/resolve.ts
21175
21322
  init_cjs_shims();
21176
- var import_types85 = require("@neat.is/types");
21323
+ var import_types86 = require("@neat.is/types");
21177
21324
  var GCP_LB_BACKEND_INFRA_KIND = "gcp-lb-backend";
21178
21325
  function findMatchingRouteNode3(graph, serviceName, method, normalizedPath) {
21179
21326
  let found = null;
21180
21327
  graph.forEachNode((_id, attrs) => {
21181
21328
  if (found) return;
21182
21329
  const node = attrs;
21183
- if (node.type !== import_types85.NodeType.RouteNode) return;
21330
+ if (node.type !== import_types86.NodeType.RouteNode) return;
21184
21331
  const route = attrs;
21185
21332
  if (route.service !== serviceName || !route.pathTemplate) return;
21186
21333
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -21196,7 +21343,7 @@ function createGcpLbResolveTarget(graph, config) {
21196
21343
  const identity = parseGcpLbTargetName(signal.targetName);
21197
21344
  if (!identity) return null;
21198
21345
  const { backendServiceName, method, path: path81 } = identity;
21199
- const mappedService = config.backendServiceMap?.[backendServiceName];
21346
+ const mappedService = config.backendServiceMap?.[backendServiceName] ?? (config.inferServices ? inferServiceName(graph, backendServiceName, method, path81) ?? void 0 : void 0);
21200
21347
  if (mappedService) {
21201
21348
  const routeNodeId = findMatchingRouteNode3(
21202
21349
  graph,
@@ -21205,13 +21352,13 @@ function createGcpLbResolveTarget(graph, config) {
21205
21352
  normalizePathTemplate(path81)
21206
21353
  );
21207
21354
  if (routeNodeId) {
21208
- return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types85.EdgeType.CALLS };
21355
+ return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types86.EdgeType.CALLS };
21209
21356
  }
21210
21357
  }
21211
21358
  return {
21212
- targetNodeId: (0, import_types85.infraId)(GCP_LB_BACKEND_INFRA_KIND, backendServiceName),
21359
+ targetNodeId: (0, import_types86.infraId)(GCP_LB_BACKEND_INFRA_KIND, backendServiceName),
21213
21360
  serviceName: mappedService ?? backendServiceName,
21214
- edgeType: import_types85.EdgeType.CALLS,
21361
+ edgeType: import_types86.EdgeType.CALLS,
21215
21362
  ensureInfraNode: {
21216
21363
  kind: GCP_LB_BACKEND_INFRA_KIND,
21217
21364
  name: backendServiceName,
@@ -21252,7 +21399,7 @@ function createGcpLbConnector(graph, config = {}) {
21252
21399
 
21253
21400
  // src/connectors/render/index.ts
21254
21401
  init_cjs_shims();
21255
- var import_types88 = require("@neat.is/types");
21402
+ var import_types89 = require("@neat.is/types");
21256
21403
 
21257
21404
  // src/connectors/render/types.ts
21258
21405
  init_cjs_shims();
@@ -21330,7 +21477,7 @@ function buildRenderRouteIndex(graph, serviceName) {
21330
21477
  const out = [];
21331
21478
  graph.forEachNode((_id, attrs) => {
21332
21479
  const node = attrs;
21333
- if (node.type !== import_types88.NodeType.RouteNode) return;
21480
+ if (node.type !== import_types89.NodeType.RouteNode) return;
21334
21481
  const route = attrs;
21335
21482
  if (route.service !== serviceName) return;
21336
21483
  out.push({
@@ -21415,7 +21562,7 @@ function mapRenderRequestLogsToSignals(entries, routeIndex) {
21415
21562
  function createRenderResolveTarget(config) {
21416
21563
  return (signal) => {
21417
21564
  if (signal.targetKind === ROUTE_TARGET_KIND2) {
21418
- return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types88.EdgeType.CALLS };
21565
+ return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types89.EdgeType.CALLS };
21419
21566
  }
21420
21567
  return null;
21421
21568
  };
@@ -21553,21 +21700,21 @@ function mapInsightsToSignals(rows, observedAtIso) {
21553
21700
 
21554
21701
  // src/connectors/planetscale/resolve.ts
21555
21702
  init_cjs_shims();
21556
- var import_types92 = require("@neat.is/types");
21703
+ var import_types93 = require("@neat.is/types");
21557
21704
  var PLANETSCALE_DATABASE_KIND = "planetscale-database";
21558
21705
  function createPlanetscaleResolveTarget(graph, config) {
21559
21706
  const databaseName = `${config.organization}/${config.database}`;
21560
21707
  return (signal, _ctx) => {
21561
21708
  if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
21562
- const tableId = (0, import_types92.infraId)("sql-table", signal.targetName);
21709
+ const tableId = (0, import_types93.infraId)("sql-table", signal.targetName);
21563
21710
  if (graph.hasNode(tableId)) {
21564
- return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types92.EdgeType.CALLS };
21711
+ return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types93.EdgeType.CALLS };
21565
21712
  }
21566
- const providerId = (0, import_types92.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
21713
+ const providerId = (0, import_types93.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
21567
21714
  return {
21568
21715
  targetNodeId: providerId,
21569
21716
  serviceName: config.serviceName,
21570
- edgeType: import_types92.EdgeType.CALLS,
21717
+ edgeType: import_types93.EdgeType.CALLS,
21571
21718
  ensureInfraNode: { kind: PLANETSCALE_DATABASE_KIND, name: databaseName, provider: "planetscale" }
21572
21719
  };
21573
21720
  };
@@ -21832,7 +21979,7 @@ function mapBuildsToSignals(builds, serviceName) {
21832
21979
 
21833
21980
  // src/connectors/eas/resolve.ts
21834
21981
  init_cjs_shims();
21835
- var import_types97 = require("@neat.is/types");
21982
+ var import_types98 = require("@neat.is/types");
21836
21983
  var NO_ENV2 = "unknown";
21837
21984
  var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
21838
21985
  var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
@@ -21848,8 +21995,8 @@ function configBasenamesForPhase(phase) {
21848
21995
  function configNodeService(graph, configNodeId) {
21849
21996
  for (const edgeId of graph.inboundEdges(configNodeId)) {
21850
21997
  const edge = graph.getEdgeAttributes(edgeId);
21851
- if (edge.type !== import_types97.EdgeType.CONFIGURED_BY) continue;
21852
- const parsed = (0, import_types97.parseFileId)(edge.source);
21998
+ if (edge.type !== import_types98.EdgeType.CONFIGURED_BY) continue;
21999
+ const parsed = (0, import_types98.parseFileId)(edge.source);
21853
22000
  if (parsed) return parsed.service;
21854
22001
  }
21855
22002
  return null;
@@ -21860,7 +22007,7 @@ function findConfigNode(graph, basenames, serviceName) {
21860
22007
  graph.forEachNode((id, attrs) => {
21861
22008
  if (scoped) return;
21862
22009
  const node = attrs;
21863
- if (node.type !== import_types97.NodeType.ConfigNode) return;
22010
+ if (node.type !== import_types98.NodeType.ConfigNode) return;
21864
22011
  if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
21865
22012
  if (anyMatch === null) anyMatch = id;
21866
22013
  if (configNodeService(graph, id) === serviceName) scoped = id;
@@ -21877,13 +22024,13 @@ function createEasResolveTarget(graph) {
21877
22024
  if (basenames.length > 0) {
21878
22025
  const configNodeId = findConfigNode(graph, basenames, serviceName);
21879
22026
  if (configNodeId) {
21880
- return { targetNodeId: configNodeId, serviceName, edgeType: import_types97.EdgeType.CALLS };
22027
+ return { targetNodeId: configNodeId, serviceName, edgeType: import_types98.EdgeType.CALLS };
21881
22028
  }
21882
22029
  }
21883
22030
  return {
21884
22031
  targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV2),
21885
22032
  serviceName,
21886
- edgeType: import_types97.EdgeType.CALLS
22033
+ edgeType: import_types98.EdgeType.CALLS
21887
22034
  };
21888
22035
  };
21889
22036
  }
@@ -22371,6 +22518,9 @@ var PUSH_PROVIDER_DISPATCH = {
22371
22518
  }
22372
22519
  }
22373
22520
  };
22521
+ function getPushProviderDispatch(provider) {
22522
+ return PUSH_PROVIDER_DISPATCH[provider];
22523
+ }
22374
22524
  function isPushProvider(provider) {
22375
22525
  return provider in PUSH_PROVIDER_DISPATCH;
22376
22526
  }
@@ -22664,11 +22814,11 @@ function registerRoutes(scope, ctx) {
22664
22814
  const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
22665
22815
  const parsed = [];
22666
22816
  for (const c of candidates) {
22667
- const r = import_types100.DivergenceTypeSchema.safeParse(c);
22817
+ const r = import_types101.DivergenceTypeSchema.safeParse(c);
22668
22818
  if (!r.success) {
22669
22819
  return reply.code(400).send({
22670
22820
  error: `unknown divergence type "${c}"`,
22671
- allowed: import_types100.DivergenceTypeSchema.options
22821
+ allowed: import_types101.DivergenceTypeSchema.options
22672
22822
  });
22673
22823
  }
22674
22824
  parsed.push(r.data);
@@ -23064,7 +23214,7 @@ function registerRoutes(scope, ctx) {
23064
23214
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
23065
23215
  let violations = await log.readAll();
23066
23216
  if (req2.query.severity) {
23067
- const sev = import_types100.PolicySeveritySchema.safeParse(req2.query.severity);
23217
+ const sev = import_types101.PolicySeveritySchema.safeParse(req2.query.severity);
23068
23218
  if (!sev.success) {
23069
23219
  return reply.code(400).send({
23070
23220
  error: "invalid severity",
@@ -23103,7 +23253,7 @@ function registerRoutes(scope, ctx) {
23103
23253
  scope.post("/policies/check", async (req2, reply) => {
23104
23254
  const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
23105
23255
  if (!proj) return;
23106
- const parsed = import_types100.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
23256
+ const parsed = import_types101.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
23107
23257
  if (!parsed.success) {
23108
23258
  return reply.code(400).send({
23109
23259
  error: "invalid /policies/check body",
@@ -23423,7 +23573,7 @@ init_otel();
23423
23573
 
23424
23574
  // src/hang-sensor.ts
23425
23575
  init_cjs_shims();
23426
- var import_types101 = require("@neat.is/types");
23576
+ var import_types102 = require("@neat.is/types");
23427
23577
  var RECONCILE_INCIDENT_LIMIT = 500;
23428
23578
  function stageHangSurfaces(graph, incidents) {
23429
23579
  const boundaries = /* @__PURE__ */ new Set();
@@ -23436,7 +23586,7 @@ function stageHangSurfaces(graph, incidents) {
23436
23586
  if (!isBoundaryTimeoutSymptom(ctx)) continue;
23437
23587
  const hop = resolveHangHop(graph, boundary, incidents);
23438
23588
  if (!hop) continue;
23439
- if (stageFrontierEdge(graph, hop.source, hop.target, import_types101.EdgeType.CALLS)) staged++;
23589
+ if (stageFrontierEdge(graph, hop.source, hop.target, import_types102.EdgeType.CALLS)) staged++;
23440
23590
  }
23441
23591
  return staged;
23442
23592
  }
@@ -23453,6 +23603,8 @@ async function reconcileFrontierSurfaces(graph, errorsPath) {
23453
23603
 
23454
23604
  // src/connectors/hosted.ts
23455
23605
  init_cjs_shims();
23606
+ var import_promises = require("fs/promises");
23607
+ var import_node_path75 = require("path");
23456
23608
 
23457
23609
  // src/connectors/railway/target-ref.ts
23458
23610
  init_cjs_shims();
@@ -23499,11 +23651,14 @@ function credentialRecord(provider, cred) {
23499
23651
  switch (provider) {
23500
23652
  case "supabase":
23501
23653
  return { managementToken: cred.accessToken };
23654
+ case "gcp":
23655
+ if (!cred.projectRef) throw new Error("gcp credential delivered without a project ref");
23656
+ return { projectId: cred.projectRef, accessToken: cred.accessToken };
23502
23657
  default:
23503
23658
  return { token: cred.accessToken };
23504
23659
  }
23505
23660
  }
23506
- function hostedOptions(provider, summary, serviceName) {
23661
+ function hostedOptions(provider, summary, serviceName, firebaseServiceMap) {
23507
23662
  switch (provider) {
23508
23663
  case "supabase": {
23509
23664
  const ref = summary.projectRef;
@@ -23519,10 +23674,125 @@ function hostedOptions(provider, summary, serviceName) {
23519
23674
  serviceNameById: { [target.serviceId]: serviceName }
23520
23675
  };
23521
23676
  }
23677
+ case "firebase": {
23678
+ return { inferServices: true, excludeCloudRun: true, ...firebaseServiceMap ?? {} };
23679
+ }
23680
+ case "cloud-run":
23681
+ case "gcp-lb":
23682
+ return { inferServices: true };
23522
23683
  default:
23523
23684
  return null;
23524
23685
  }
23525
23686
  }
23687
+ function parseFirebaseServiceMap(raw) {
23688
+ if (!raw) return void 0;
23689
+ try {
23690
+ const parsed = JSON.parse(raw);
23691
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
23692
+ const out = {};
23693
+ for (const key of ["functions", "cloudRun", "hosting"]) {
23694
+ const group = parsed[key];
23695
+ if (group === void 0) continue;
23696
+ if (!group || typeof group !== "object" || Array.isArray(group)) return void 0;
23697
+ const entries = Object.entries(group);
23698
+ if (entries.some(([, v]) => typeof v !== "string" || v.length === 0)) return void 0;
23699
+ out[key] = Object.fromEntries(entries);
23700
+ }
23701
+ return Object.keys(out).length > 0 ? out : void 0;
23702
+ } catch {
23703
+ return void 0;
23704
+ }
23705
+ }
23706
+ function pushHandlesPath(projectDir) {
23707
+ return (0, import_node_path75.join)(projectDir, "neat-out", "connectors-hosted.json");
23708
+ }
23709
+ async function readPushHandles(projectDir) {
23710
+ try {
23711
+ const parsed = JSON.parse(await (0, import_promises.readFile)(pushHandlesPath(projectDir), "utf8"));
23712
+ return parsed && typeof parsed === "object" ? parsed : {};
23713
+ } catch {
23714
+ return {};
23715
+ }
23716
+ }
23717
+ async function writePushHandle(projectDir, provider, handle) {
23718
+ const path81 = pushHandlesPath(projectDir);
23719
+ const handles = await readPushHandles(projectDir);
23720
+ handles[provider] = handle;
23721
+ await (0, import_promises.mkdir)((0, import_node_path75.dirname)(path81), { recursive: true });
23722
+ const tmp = `${path81}.${process.pid}.tmp`;
23723
+ await (0, import_promises.writeFile)(tmp, `${JSON.stringify(handles, null, 2)}
23724
+ `, "utf8");
23725
+ await (0, import_promises.rename)(tmp, path81);
23726
+ }
23727
+ function pushCredentialRecord(provider, cred, deps) {
23728
+ switch (provider) {
23729
+ case "vercel":
23730
+ return { token: cred.accessToken, otelToken: deps.otelToken ?? deps.daemonToken };
23731
+ default:
23732
+ return { token: cred.accessToken };
23733
+ }
23734
+ }
23735
+ function hostedPushOptions(provider, summary, deps) {
23736
+ switch (provider) {
23737
+ case "vercel": {
23738
+ if (!deps.publicUrl) {
23739
+ return { skip: "no public URL for this daemon (NEAT_PUBLIC_URL) \u2014 a drain has nowhere to deliver" };
23740
+ }
23741
+ if (!summary.projectRef) return { skip: "no Vercel team selected yet \u2014 drains are team-scoped" };
23742
+ const endpoint2 = `${deps.publicUrl.replace(/\/+$/, "")}/v1/traces`;
23743
+ return { options: { teamId: summary.projectRef, endpoint: endpoint2 }, endpoint: endpoint2 };
23744
+ }
23745
+ default:
23746
+ return { skip: "no hosted option mapping for this push provider" };
23747
+ }
23748
+ }
23749
+ async function provisionHostedPushProvider(summary, dispatch, input) {
23750
+ const { deps, projectDir, onSkip, onProvisioned } = input;
23751
+ const provider = summary.provider;
23752
+ const mapped = hostedPushOptions(provider, summary, deps);
23753
+ if ("skip" in mapped) {
23754
+ onSkip?.(provider, mapped.skip);
23755
+ return;
23756
+ }
23757
+ let cred;
23758
+ try {
23759
+ cred = await cpGet(
23760
+ `/internal/projects/${deps.projectId}/connections/${provider}/credential`,
23761
+ deps
23762
+ );
23763
+ } catch (err) {
23764
+ onSkip?.(provider, `credential delivery failed \u2014 ${err.message}`);
23765
+ return;
23766
+ }
23767
+ const credentials = pushCredentialRecord(provider, cred, deps);
23768
+ const existing = (await readPushHandles(projectDir))[provider];
23769
+ const alreadyProvisioned = existing !== void 0 && existing.endpoint === mapped.endpoint;
23770
+ const options = alreadyProvisioned ? { ...mapped.options, ...existing.options } : mapped.options;
23771
+ const seam = deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {};
23772
+ const validation = await dispatch.validate({ credentials, options, ...seam });
23773
+ if (!validation.ok) {
23774
+ onSkip?.(provider, `drain delivery test failed \u2014 ${validation.reason}`);
23775
+ return;
23776
+ }
23777
+ if (alreadyProvisioned) {
23778
+ onProvisioned?.(provider, "drain already provisioned \u2014 delivery re-validated");
23779
+ return;
23780
+ }
23781
+ const result = await dispatch.provision({ credentials, options, ...seam });
23782
+ if (!result.ok) {
23783
+ onSkip?.(provider, `drain provisioning failed \u2014 ${result.reason}`);
23784
+ return;
23785
+ }
23786
+ await writePushHandle(projectDir, provider, {
23787
+ endpoint: mapped.endpoint,
23788
+ options: result.options ?? {},
23789
+ provisionedAt: (/* @__PURE__ */ new Date()).toISOString()
23790
+ });
23791
+ onProvisioned?.(provider, result.note);
23792
+ }
23793
+ var HOSTED_CONNECTORS_FOR = {
23794
+ gcp: ["firebase", "cloud-run", "gcp-lb"]
23795
+ };
23526
23796
  function createHostedCredentialSource(provider, deps) {
23527
23797
  let cached = null;
23528
23798
  return async () => {
@@ -23551,40 +23821,50 @@ async function startHostedConnectors(input) {
23551
23821
  if (!Array.isArray(connections)) return () => {
23552
23822
  };
23553
23823
  const stops = [];
23824
+ const lookupPush = input.pushDispatch ?? getPushProviderDispatch;
23554
23825
  for (const c of connections) {
23555
- const dispatch = PROVIDER_DISPATCH[c.provider];
23556
- if (!dispatch) {
23557
- onSkip?.(c.provider, "no pull connector for this provider");
23826
+ const push = lookupPush(c.provider);
23827
+ if (push) {
23828
+ await provisionHostedPushProvider(c, push, input);
23558
23829
  continue;
23559
23830
  }
23560
- if (c.needsProjectSelection || !c.projectRef) {
23561
- onSkip?.(c.provider, "no project selected yet \u2014 not pullable");
23562
- continue;
23563
- }
23564
- const options = hostedOptions(c.provider, c, project);
23565
- if (!options) {
23566
- onSkip?.(c.provider, "no hosted option mapping for this provider");
23567
- continue;
23568
- }
23569
- let built;
23570
- try {
23571
- built = dispatch.build(graph, options);
23572
- } catch (err) {
23573
- onSkip?.(c.provider, err.message);
23574
- continue;
23831
+ const credentialSource = createHostedCredentialSource(c.provider, deps);
23832
+ for (const name of HOSTED_CONNECTORS_FOR[c.provider] ?? [c.provider]) {
23833
+ const dispatch = PROVIDER_DISPATCH[name];
23834
+ if (!dispatch) {
23835
+ onSkip?.(name, "no pull connector for this provider");
23836
+ continue;
23837
+ }
23838
+ if (c.needsProjectSelection || !c.projectRef) {
23839
+ onSkip?.(name, "no project selected yet \u2014 not pullable");
23840
+ continue;
23841
+ }
23842
+ const options = hostedOptions(name, c, project, input.firebaseServiceMap);
23843
+ if (!options) {
23844
+ onSkip?.(name, "no hosted option mapping for this provider");
23845
+ continue;
23846
+ }
23847
+ let built;
23848
+ try {
23849
+ built = dispatch.build(graph, options);
23850
+ } catch (err) {
23851
+ onSkip?.(name, err.message);
23852
+ continue;
23853
+ }
23854
+ stops.push(
23855
+ startLoop(
23856
+ built.connector,
23857
+ { projectDir, project, credentials: {}, ...errorsPath ? { errorsPath } : {} },
23858
+ graph,
23859
+ built.resolveTarget,
23860
+ {
23861
+ connectorId: `hosted:${name}`,
23862
+ // The credential is fetched by the CONNECTION's provider (`gcp`), not the connector's name.
23863
+ refreshCredentials: credentialSource
23864
+ }
23865
+ )
23866
+ );
23575
23867
  }
23576
- stops.push(
23577
- startLoop(
23578
- built.connector,
23579
- { projectDir, project, credentials: {}, ...errorsPath ? { errorsPath } : {} },
23580
- graph,
23581
- built.resolveTarget,
23582
- {
23583
- connectorId: `hosted:${c.provider}`,
23584
- refreshCredentials: createHostedCredentialSource(c.provider, deps)
23585
- }
23586
- )
23587
- );
23588
23868
  }
23589
23869
  return () => {
23590
23870
  for (const stop of stops) stop();
@@ -23598,10 +23878,18 @@ async function maybeStartHostedConnectors(input) {
23598
23878
  if (!cpUrl || !projectId || !daemonToken) return () => {
23599
23879
  };
23600
23880
  return startHostedConnectors({
23601
- deps: { cpUrl, projectId, daemonToken, ...input.fetchImpl ? { fetchImpl: input.fetchImpl } : {} },
23881
+ deps: {
23882
+ cpUrl,
23883
+ projectId,
23884
+ daemonToken,
23885
+ ...env.NEAT_PUBLIC_URL ? { publicUrl: env.NEAT_PUBLIC_URL } : {},
23886
+ ...env.NEAT_OTEL_TOKEN ? { otelToken: env.NEAT_OTEL_TOKEN } : {},
23887
+ ...input.fetchImpl ? { fetchImpl: input.fetchImpl } : {}
23888
+ },
23602
23889
  graph: input.graph,
23603
23890
  projectDir: input.projectDir,
23604
23891
  project: input.project,
23892
+ ...parseFirebaseServiceMap(env.NEAT_FIREBASE_SERVICE_MAP) ? { firebaseServiceMap: parseFirebaseServiceMap(env.NEAT_FIREBASE_SERVICE_MAP) } : {},
23605
23893
  ...input.errorsPath ? { errorsPath: input.errorsPath } : {},
23606
23894
  ...input.onSkip ? { onSkip: input.onSkip } : {}
23607
23895
  });
@@ -23610,8 +23898,8 @@ async function maybeStartHostedConnectors(input) {
23610
23898
  // src/connectors/hosted-repos.ts
23611
23899
  init_cjs_shims();
23612
23900
  var import_node_os5 = __toESM(require("os"), 1);
23613
- var import_node_path75 = __toESM(require("path"), 1);
23614
- var import_promises = require("fs/promises");
23901
+ var import_node_path76 = __toESM(require("path"), 1);
23902
+ var import_promises2 = require("fs/promises");
23615
23903
  var CP_REQUEST_TIMEOUT_MS2 = 1e4;
23616
23904
  var DEFAULT_SYNC_INTERVAL_MS = 5 * 6e4;
23617
23905
  var CLONE_TIMEOUT_MS = 5 * 6e4;
@@ -23681,7 +23969,9 @@ async function syncOneRepo(r, input) {
23681
23969
  const tmpRoot = input.tmpRoot ?? import_node_os5.default.tmpdir();
23682
23970
  let dir;
23683
23971
  try {
23684
- dir = await (0, import_promises.mkdtemp)(import_node_path75.default.join(tmpRoot, "neat-repo-"));
23972
+ dir = await (0, import_promises2.mkdtemp)(import_node_path76.default.join(tmpRoot, "neat-repo-"));
23973
+ await cpPostStatus(deps, r.owner, r.name, { syncStatus: "syncing", detail: "cloning" }).catch(() => {
23974
+ });
23685
23975
  await cloneRepo(r.cloneUrl, r.defaultBranch, dir);
23686
23976
  const extracted = await extract(graph, dir);
23687
23977
  const nodes = extracted?.nodesAdded ?? 0;
@@ -23699,7 +23989,7 @@ async function syncOneRepo(r, input) {
23699
23989
  }).catch(() => {
23700
23990
  });
23701
23991
  } finally {
23702
- if (dir) await (0, import_promises.rm)(dir, { recursive: true, force: true }).catch(() => {
23992
+ if (dir) await (0, import_promises2.rm)(dir, { recursive: true, force: true }).catch(() => {
23703
23993
  });
23704
23994
  }
23705
23995
  }
@@ -23709,23 +23999,26 @@ async function runRepoSyncPass(input) {
23709
23999
  repos = await cpGet2(`/internal/projects/${input.deps.projectId}/repos`, input.deps);
23710
24000
  } catch (err) {
23711
24001
  input.onSkip?.("(all)", `control plane repo list unreadable \u2014 ${err.message}`);
23712
- return;
24002
+ return false;
23713
24003
  }
23714
- if (!Array.isArray(repos)) return;
24004
+ if (!Array.isArray(repos)) return false;
23715
24005
  for (const r of repos) {
23716
- if (!needsSync(r)) continue;
24006
+ if (!input.forceResync && !needsSync(r)) continue;
23717
24007
  await syncOneRepo(r, input);
23718
24008
  }
24009
+ return true;
23719
24010
  }
23720
24011
  async function startRepoSync(input) {
23721
24012
  const intervalMs = input.intervalMs ?? DEFAULT_SYNC_INTERVAL_MS;
23722
24013
  let stopped = false;
23723
24014
  let running = false;
24015
+ let bootResyncDone = false;
23724
24016
  const tick = async () => {
23725
24017
  if (stopped || running) return;
23726
24018
  running = true;
23727
24019
  try {
23728
- await runRepoSyncPass(input);
24020
+ const listed = await runRepoSyncPass({ ...input, forceResync: !bootResyncDone });
24021
+ if (listed) bootResyncDone = true;
23729
24022
  } finally {
23730
24023
  running = false;
23731
24024
  }
@@ -24109,7 +24402,7 @@ function mapWorkloadsToSignals(deployments, pods, config) {
24109
24402
 
24110
24403
  // src/connectors/kubernetes/resolve.ts
24111
24404
  init_cjs_shims();
24112
- var import_types103 = require("@neat.is/types");
24405
+ var import_types104 = require("@neat.is/types");
24113
24406
  var NO_ENV3 = "unknown";
24114
24407
  function createK8sResolveTarget(graph) {
24115
24408
  return (signal) => {
@@ -24120,28 +24413,28 @@ function createK8sResolveTarget(graph) {
24120
24413
  return {
24121
24414
  targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV3),
24122
24415
  serviceName,
24123
- edgeType: import_types103.EdgeType.CALLS
24416
+ edgeType: import_types104.EdgeType.CALLS
24124
24417
  };
24125
24418
  };
24126
24419
  }
24127
24420
 
24128
24421
  // src/connectors/kubernetes/substrate.ts
24129
24422
  init_cjs_shims();
24130
- var import_promises2 = require("fs/promises");
24423
+ var import_promises3 = require("fs/promises");
24131
24424
  var import_node_os6 = __toESM(require("os"), 1);
24132
- var import_node_path76 = __toESM(require("path"), 1);
24425
+ var import_node_path77 = __toESM(require("path"), 1);
24133
24426
  function defaultHome() {
24134
24427
  const override = process.env.NEAT_HOME;
24135
- if (override && override.length > 0) return import_node_path76.default.resolve(override);
24136
- return import_node_path76.default.join(import_node_os6.default.homedir(), ".neat");
24428
+ if (override && override.length > 0) return import_node_path77.default.resolve(override);
24429
+ return import_node_path77.default.join(import_node_os6.default.homedir(), ".neat");
24137
24430
  }
24138
24431
  function k8sSubstrateConfigPath(home = defaultHome()) {
24139
- return import_node_path76.default.join(home, "k8s.json");
24432
+ return import_node_path77.default.join(home, "k8s.json");
24140
24433
  }
24141
24434
  async function readK8sSubstrateConfig(home = defaultHome()) {
24142
24435
  let raw;
24143
24436
  try {
24144
- raw = await (0, import_promises2.readFile)(k8sSubstrateConfigPath(home), "utf8");
24437
+ raw = await (0, import_promises3.readFile)(k8sSubstrateConfigPath(home), "utf8");
24145
24438
  } catch {
24146
24439
  return { version: 1, deployments: [] };
24147
24440
  }
@@ -24236,7 +24529,7 @@ init_auth();
24236
24529
  // src/unrouted.ts
24237
24530
  init_cjs_shims();
24238
24531
  var import_node_fs40 = require("fs");
24239
- var import_node_path77 = __toESM(require("path"), 1);
24532
+ var import_node_path78 = __toESM(require("path"), 1);
24240
24533
  function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
24241
24534
  return {
24242
24535
  timestamp: now.toISOString(),
@@ -24246,34 +24539,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
24246
24539
  };
24247
24540
  }
24248
24541
  async function appendUnroutedSpan(neatHome4, record) {
24249
- const target = import_node_path77.default.join(neatHome4, "errors.ndjson");
24542
+ const target = import_node_path78.default.join(neatHome4, "errors.ndjson");
24250
24543
  await import_node_fs40.promises.mkdir(neatHome4, { recursive: true });
24251
24544
  await import_node_fs40.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
24252
24545
  }
24253
24546
  function unroutedErrorsPath(neatHome4) {
24254
- return import_node_path77.default.join(neatHome4, "errors.ndjson");
24547
+ return import_node_path78.default.join(neatHome4, "errors.ndjson");
24255
24548
  }
24256
24549
 
24257
24550
  // src/daemon.ts
24258
- var import_types106 = require("@neat.is/types");
24551
+ var import_types107 = require("@neat.is/types");
24259
24552
  function daemonJsonPath(scanPath) {
24260
- return import_node_path78.default.join(scanPath, "neat-out", "daemon.json");
24553
+ return import_node_path79.default.join(scanPath, "neat-out", "daemon.json");
24261
24554
  }
24262
24555
  function daemonsDiscoveryDir(home) {
24263
24556
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
24264
- return import_node_path78.default.join(base, "daemons");
24557
+ return import_node_path79.default.join(base, "daemons");
24265
24558
  }
24266
24559
  function daemonDiscoveryPath(project, home) {
24267
- return import_node_path78.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
24560
+ return import_node_path79.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
24268
24561
  }
24269
24562
  function sanitizeDiscoveryName(project) {
24270
24563
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
24271
24564
  }
24272
24565
  function neatHomeFromEnv() {
24273
24566
  const env = process.env.NEAT_HOME;
24274
- if (env && env.length > 0) return import_node_path78.default.resolve(env);
24567
+ if (env && env.length > 0) return import_node_path79.default.resolve(env);
24275
24568
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
24276
- return import_node_path78.default.join(home, ".neat");
24569
+ return import_node_path79.default.join(home, ".neat");
24277
24570
  }
24278
24571
  function resolveNeatVersion() {
24279
24572
  if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
@@ -24354,11 +24647,11 @@ function teardownSlot(slot) {
24354
24647
  }
24355
24648
  }
24356
24649
  function neatHomeFor(opts) {
24357
- if (opts.neatHome && opts.neatHome.length > 0) return import_node_path78.default.resolve(opts.neatHome);
24650
+ if (opts.neatHome && opts.neatHome.length > 0) return import_node_path79.default.resolve(opts.neatHome);
24358
24651
  const env = process.env.NEAT_HOME;
24359
- if (env && env.length > 0) return import_node_path78.default.resolve(env);
24652
+ if (env && env.length > 0) return import_node_path79.default.resolve(env);
24360
24653
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
24361
- return import_node_path78.default.join(home, ".neat");
24654
+ return import_node_path79.default.join(home, ".neat");
24362
24655
  }
24363
24656
  function routeSpanToProject(serviceName, projects) {
24364
24657
  if (!serviceName) return DEFAULT_PROJECT;
@@ -24406,11 +24699,11 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
24406
24699
  if (!serviceName) return true;
24407
24700
  if (serviceNameMatchesProject(serviceName, project)) return true;
24408
24701
  return graph.someNode(
24409
- (_id, attrs) => attrs.type === import_types106.NodeType.ServiceNode && attrs.name === serviceName
24702
+ (_id, attrs) => attrs.type === import_types107.NodeType.ServiceNode && attrs.name === serviceName
24410
24703
  );
24411
24704
  }
24412
24705
  async function bootstrapProject(entry2, connectors = [], neatHome4) {
24413
- const paths = pathsForProject(entry2.name, import_node_path78.default.join(entry2.path, "neat-out"));
24706
+ const paths = pathsForProject(entry2.name, import_node_path79.default.join(entry2.path, "neat-out"));
24414
24707
  try {
24415
24708
  const stat = await import_node_fs41.promises.stat(entry2.path);
24416
24709
  if (!stat.isDirectory()) {
@@ -24564,7 +24857,7 @@ async function startDaemon(opts = {}) {
24564
24857
  const projectArg = typeof opts.project === "string" && opts.project.length > 0 ? opts.project : process.env.NEAT_PROJECT && process.env.NEAT_PROJECT.length > 0 ? process.env.NEAT_PROJECT : null;
24565
24858
  const projectPathArg = opts.projectPath && opts.projectPath.length > 0 ? opts.projectPath : process.env.NEAT_PROJECT_PATH && process.env.NEAT_PROJECT_PATH.length > 0 ? process.env.NEAT_PROJECT_PATH : null;
24566
24859
  const singleProject = projectArg;
24567
- const singleProjectPath = singleProject && projectPathArg ? import_node_path78.default.resolve(projectPathArg) : null;
24860
+ const singleProjectPath = singleProject && projectPathArg ? import_node_path79.default.resolve(projectPathArg) : null;
24568
24861
  if (singleProject && !singleProjectPath) {
24569
24862
  throw new Error(
24570
24863
  `neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
@@ -24579,7 +24872,7 @@ async function startDaemon(opts = {}) {
24579
24872
  );
24580
24873
  }
24581
24874
  }
24582
- const pidPath = import_node_path78.default.join(home, "neatd.pid");
24875
+ const pidPath = import_node_path79.default.join(home, "neatd.pid");
24583
24876
  await writeAtomically(pidPath, `${process.pid}
24584
24877
  `);
24585
24878
  const slots = /* @__PURE__ */ new Map();
@@ -25051,8 +25344,8 @@ async function startDaemon(opts = {}) {
25051
25344
  let registryWatcher = null;
25052
25345
  let reloadTimer = null;
25053
25346
  if (!singleProject) try {
25054
- const regDir = import_node_path78.default.dirname(regPath);
25055
- const regBase = import_node_path78.default.basename(regPath);
25347
+ const regDir = import_node_path79.default.dirname(regPath);
25348
+ const regBase = import_node_path79.default.basename(regPath);
25056
25349
  registryWatcher = (0, import_node_fs41.watch)(regDir, (_eventType, filename) => {
25057
25350
  if (filename !== null && filename !== regBase) return;
25058
25351
  if (reloadTimer) clearTimeout(reloadTimer);
@@ -25127,7 +25420,7 @@ init_cjs_shims();
25127
25420
  var import_node_child_process2 = require("child_process");
25128
25421
  var import_node_fs42 = require("fs");
25129
25422
  var import_node_net = __toESM(require("net"), 1);
25130
- var import_node_path79 = __toESM(require("path"), 1);
25423
+ var import_node_path80 = __toESM(require("path"), 1);
25131
25424
  var DEFAULT_WEB_PORT = 6328;
25132
25425
  var DEFAULT_REST_PORT = 8080;
25133
25426
  function asValidPort(value) {
@@ -25136,11 +25429,11 @@ function asValidPort(value) {
25136
25429
  }
25137
25430
  function projectRoot() {
25138
25431
  const fromEnv = process.env.NEAT_SCAN_PATH;
25139
- return import_node_path79.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
25432
+ return import_node_path80.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
25140
25433
  }
25141
25434
  async function readDaemonPorts(root) {
25142
25435
  try {
25143
- const raw = await import_node_fs42.promises.readFile(import_node_path79.default.join(root, "neat-out", "daemon.json"), "utf8");
25436
+ const raw = await import_node_fs42.promises.readFile(import_node_path80.default.join(root, "neat-out", "daemon.json"), "utf8");
25144
25437
  const parsed = JSON.parse(raw);
25145
25438
  const ports = parsed?.ports ?? {};
25146
25439
  return { web: asValidPort(ports.web), rest: asValidPort(ports.rest) };
@@ -25185,10 +25478,10 @@ function resolveWebPackageDir() {
25185
25478
  eval("require")
25186
25479
  );
25187
25480
  const pkgJsonPath = req.resolve("@neat.is/web/package.json");
25188
- return import_node_path79.default.dirname(pkgJsonPath);
25481
+ return import_node_path80.default.dirname(pkgJsonPath);
25189
25482
  }
25190
25483
  function resolveStandaloneServerEntry(webDir) {
25191
- return import_node_path79.default.join(webDir, ".next/standalone/packages/web/server.js");
25484
+ return import_node_path80.default.join(webDir, ".next/standalone/packages/web/server.js");
25192
25485
  }
25193
25486
  async function pickInternalPort() {
25194
25487
  return new Promise((resolve, reject) => {
@@ -25241,7 +25534,7 @@ async function spawnWebUI(restPort, opts = {}) {
25241
25534
  NEAT_API_URL: apiUrl
25242
25535
  };
25243
25536
  child = (0, import_node_child_process2.spawn)(process.execPath, [serverEntry], {
25244
- cwd: import_node_path79.default.dirname(serverEntry),
25537
+ cwd: import_node_path80.default.dirname(serverEntry),
25245
25538
  env,
25246
25539
  stdio: ["ignore", "inherit", "inherit"],
25247
25540
  detached: false
@@ -25340,13 +25633,13 @@ async function waitForListening(port, timeoutMs = 15e3) {
25340
25633
 
25341
25634
  // src/version-skew.ts
25342
25635
  init_cjs_shims();
25343
- var import_promises3 = require("timers/promises");
25636
+ var import_promises4 = require("timers/promises");
25344
25637
  var NPM_REGISTRY_URL = "https://registry.npmjs.org/neat.is/latest";
25345
25638
  var DEFAULT_TIMEOUT_MS = 2e3;
25346
25639
  async function fetchRegistryVersion(url, timeoutMs, fetchImpl) {
25347
25640
  const controller = new AbortController();
25348
25641
  let timedOut = false;
25349
- const timer = (0, import_promises3.setTimeout)(timeoutMs).then(() => {
25642
+ const timer = (0, import_promises4.setTimeout)(timeoutMs).then(() => {
25350
25643
  timedOut = true;
25351
25644
  controller.abort();
25352
25645
  });
@@ -25417,14 +25710,14 @@ function localVersion() {
25417
25710
  }
25418
25711
  function neatHome3() {
25419
25712
  if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
25420
- return import_node_path80.default.resolve(process.env.NEAT_HOME);
25713
+ return import_node_path81.default.resolve(process.env.NEAT_HOME);
25421
25714
  }
25422
25715
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
25423
- return import_node_path80.default.join(home, ".neat");
25716
+ return import_node_path81.default.join(home, ".neat");
25424
25717
  }
25425
25718
  async function readPid() {
25426
25719
  try {
25427
- const raw = await import_node_fs43.promises.readFile(import_node_path80.default.join(neatHome3(), "neatd.pid"), "utf8");
25720
+ const raw = await import_node_fs43.promises.readFile(import_node_path81.default.join(neatHome3(), "neatd.pid"), "utf8");
25428
25721
  const n = Number.parseInt(raw.trim(), 10);
25429
25722
  return Number.isFinite(n) ? n : null;
25430
25723
  } catch {