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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -8710,7 +8710,7 @@ function collectPhpSymbolDefs(root) {
8710
8710
  });
8711
8711
  };
8712
8712
  const dot = (s) => s.replace(/\\/g, ".").replace(/::/g, ".").replace(/^\.+/, "");
8713
- const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
8713
+ const join2 = (prefix, name) => prefix ? `${prefix}.${name}` : name;
8714
8714
  const walkChildren = (container, nsCtx, classCtx) => {
8715
8715
  let ns = nsCtx;
8716
8716
  for (let i = 0; i < container.namedChildCount; i++) {
@@ -8732,7 +8732,7 @@ function collectPhpSymbolDefs(root) {
8732
8732
  switch (node.type) {
8733
8733
  case "function_definition": {
8734
8734
  const name = node.childForFieldName("name")?.text;
8735
- if (name) push("function", join(nsCtx, name), node);
8735
+ if (name) push("function", join2(nsCtx, name), node);
8736
8736
  walkBody(node, nsCtx, void 0);
8737
8737
  return;
8738
8738
  }
@@ -8740,7 +8740,7 @@ function collectPhpSymbolDefs(root) {
8740
8740
  case "trait_declaration":
8741
8741
  case "interface_declaration": {
8742
8742
  const name = node.childForFieldName("name")?.text;
8743
- const full = name ? join(nsCtx, name) : classCtx;
8743
+ const full = name ? join2(nsCtx, name) : classCtx;
8744
8744
  if (name) push("class", full, node);
8745
8745
  walkBody(node, nsCtx, full);
8746
8746
  return;
@@ -8749,7 +8749,7 @@ function collectPhpSymbolDefs(root) {
8749
8749
  const name = node.childForFieldName("name")?.text;
8750
8750
  if (name) {
8751
8751
  const kind = name === "__construct" ? "constructor" : "method";
8752
- push(kind, join(classCtx ?? nsCtx, name), node);
8752
+ push(kind, join2(classCtx ?? nsCtx, name), node);
8753
8753
  }
8754
8754
  return;
8755
8755
  }
@@ -8775,7 +8775,7 @@ function collectCsharpSymbolDefs(root) {
8775
8775
  endLine: node.endPosition.row + 1
8776
8776
  });
8777
8777
  };
8778
- const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
8778
+ const join2 = (prefix, name) => prefix ? `${prefix}.${name}` : name;
8779
8779
  const walkChildren = (container, nsCtx, classCtx) => {
8780
8780
  let ns = nsCtx;
8781
8781
  for (let i = 0; i < container.namedChildCount; i++) {
@@ -8783,7 +8783,7 @@ function collectCsharpSymbolDefs(root) {
8783
8783
  if (!child) continue;
8784
8784
  if (child.type === "file_scoped_namespace_declaration") {
8785
8785
  const nameNode = child.childForFieldName("name");
8786
- if (nameNode) ns = join(nsCtx, nameNode.text);
8786
+ if (nameNode) ns = join2(nsCtx, nameNode.text);
8787
8787
  continue;
8788
8788
  }
8789
8789
  visit(child, ns, classCtx);
@@ -8797,7 +8797,7 @@ function collectCsharpSymbolDefs(root) {
8797
8797
  switch (node.type) {
8798
8798
  case "namespace_declaration": {
8799
8799
  const nameNode = node.childForFieldName("name");
8800
- const ns = nameNode ? join(nsCtx, nameNode.text) : nsCtx;
8800
+ const ns = nameNode ? join2(nsCtx, nameNode.text) : nsCtx;
8801
8801
  walkBody(node, ns, classCtx);
8802
8802
  return;
8803
8803
  }
@@ -8806,19 +8806,19 @@ function collectCsharpSymbolDefs(root) {
8806
8806
  case "struct_declaration":
8807
8807
  case "record_declaration": {
8808
8808
  const name = node.childForFieldName("name")?.text;
8809
- const full = name ? join(classCtx ?? nsCtx, name) : classCtx;
8809
+ const full = name ? join2(classCtx ?? nsCtx, name) : classCtx;
8810
8810
  if (name) push("class", full, node);
8811
8811
  walkBody(node, nsCtx, full);
8812
8812
  return;
8813
8813
  }
8814
8814
  case "method_declaration": {
8815
8815
  const name = node.childForFieldName("name")?.text;
8816
- if (name) push("method", join(classCtx ?? nsCtx, name), node);
8816
+ if (name) push("method", join2(classCtx ?? nsCtx, name), node);
8817
8817
  return;
8818
8818
  }
8819
8819
  case "constructor_declaration": {
8820
8820
  const name = node.childForFieldName("name")?.text;
8821
- if (name) push("constructor", join(classCtx ?? nsCtx, name), node);
8821
+ if (name) push("constructor", join2(classCtx ?? nsCtx, name), node);
8822
8822
  return;
8823
8823
  }
8824
8824
  }
@@ -8837,7 +8837,7 @@ function collectJavaSymbolDefs(root) {
8837
8837
  endLine: node.endPosition.row + 1
8838
8838
  });
8839
8839
  };
8840
- const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
8840
+ const join2 = (prefix, name) => prefix ? `${prefix}.${name}` : name;
8841
8841
  let pkg;
8842
8842
  for (let i = 0; i < root.namedChildCount; i++) {
8843
8843
  const child = root.namedChild(i);
@@ -8863,19 +8863,19 @@ function collectJavaSymbolDefs(root) {
8863
8863
  case "enum_declaration":
8864
8864
  case "record_declaration": {
8865
8865
  const name = node.childForFieldName("name")?.text;
8866
- const full = name ? join(classCtx ?? pkg, name) : classCtx;
8866
+ const full = name ? join2(classCtx ?? pkg, name) : classCtx;
8867
8867
  if (name) push("class", full, node);
8868
8868
  walkBody(node, full);
8869
8869
  return;
8870
8870
  }
8871
8871
  case "method_declaration": {
8872
8872
  const name = node.childForFieldName("name")?.text;
8873
- if (name) push("method", join(classCtx ?? pkg, name), node);
8873
+ if (name) push("method", join2(classCtx ?? pkg, name), node);
8874
8874
  return;
8875
8875
  }
8876
8876
  case "constructor_declaration": {
8877
8877
  const name = node.childForFieldName("name")?.text;
8878
- if (name) push("constructor", join(classCtx ?? pkg, name), node);
8878
+ if (name) push("constructor", join2(classCtx ?? pkg, name), node);
8879
8879
  return;
8880
8880
  }
8881
8881
  }
@@ -8894,7 +8894,7 @@ function collectKotlinSymbolDefs(root) {
8894
8894
  endLine: node.endPosition.row + 1
8895
8895
  });
8896
8896
  };
8897
- const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
8897
+ const join2 = (prefix, name) => prefix ? `${prefix}.${name}` : name;
8898
8898
  const firstChildOfType2 = (node, types) => {
8899
8899
  for (let i = 0; i < node.namedChildCount; i++) {
8900
8900
  const child = node.namedChild(i);
@@ -8923,7 +8923,7 @@ function collectKotlinSymbolDefs(root) {
8923
8923
  case "class_declaration":
8924
8924
  case "object_declaration": {
8925
8925
  const name = nameOf(node, "type_identifier");
8926
- const full = name ? join(classCtx ?? pkg, name) : classCtx;
8926
+ const full = name ? join2(classCtx ?? pkg, name) : classCtx;
8927
8927
  if (name) push("class", full, node);
8928
8928
  const body = bodyOf(node);
8929
8929
  if (body) walkChildren(body, full);
@@ -8931,20 +8931,20 @@ function collectKotlinSymbolDefs(root) {
8931
8931
  }
8932
8932
  case "companion_object": {
8933
8933
  const name = nameOf(node, "type_identifier");
8934
- const full = name ? join(classCtx ?? pkg, name) : classCtx ?? pkg;
8934
+ const full = name ? join2(classCtx ?? pkg, name) : classCtx ?? pkg;
8935
8935
  const body = bodyOf(node);
8936
8936
  if (body) walkChildren(body, full);
8937
8937
  return;
8938
8938
  }
8939
8939
  case "function_declaration": {
8940
8940
  const name = nameOf(node, "simple_identifier");
8941
- if (name) push(classCtx ? "method" : "function", join(classCtx ?? pkg, name), node);
8941
+ if (name) push(classCtx ? "method" : "function", join2(classCtx ?? pkg, name), node);
8942
8942
  return;
8943
8943
  }
8944
8944
  case "secondary_constructor": {
8945
8945
  if (classCtx) {
8946
8946
  const typeName = classCtx.slice(classCtx.lastIndexOf(".") + 1);
8947
- push("constructor", join(classCtx, typeName), node);
8947
+ push("constructor", join2(classCtx, typeName), node);
8948
8948
  }
8949
8949
  return;
8950
8950
  }
@@ -8964,14 +8964,14 @@ function collectRustSymbolDefs(root) {
8964
8964
  endLine: node.endPosition.row + 1
8965
8965
  });
8966
8966
  };
8967
- const join = (prefix, name) => prefix ? `${prefix}::${name}` : name;
8967
+ const join2 = (prefix, name) => prefix ? `${prefix}::${name}` : name;
8968
8968
  const walkTypeBody = (body, typeCtx) => {
8969
8969
  for (let i = 0; i < body.namedChildCount; i++) {
8970
8970
  const child = body.namedChild(i);
8971
8971
  if (!child) continue;
8972
8972
  if (child.type === "function_item" || child.type === "function_signature_item") {
8973
8973
  const name = child.childForFieldName("name")?.text;
8974
- if (name) push("method", join(typeCtx, name), child);
8974
+ if (name) push("method", join2(typeCtx, name), child);
8975
8975
  }
8976
8976
  }
8977
8977
  };
@@ -8985,7 +8985,7 @@ function collectRustSymbolDefs(root) {
8985
8985
  switch (node.type) {
8986
8986
  case "mod_item": {
8987
8987
  const name = node.childForFieldName("name")?.text;
8988
- const full = name ? join(modCtx, name) : modCtx;
8988
+ const full = name ? join2(modCtx, name) : modCtx;
8989
8989
  const body = node.childForFieldName("body");
8990
8990
  if (body) walkChildren(body, full);
8991
8991
  return;
@@ -8993,12 +8993,12 @@ function collectRustSymbolDefs(root) {
8993
8993
  case "struct_item":
8994
8994
  case "enum_item": {
8995
8995
  const name = node.childForFieldName("name")?.text;
8996
- if (name) push("class", join(modCtx, name), node);
8996
+ if (name) push("class", join2(modCtx, name), node);
8997
8997
  return;
8998
8998
  }
8999
8999
  case "trait_item": {
9000
9000
  const name = node.childForFieldName("name")?.text;
9001
- const full = name ? join(modCtx, name) : modCtx;
9001
+ const full = name ? join2(modCtx, name) : modCtx;
9002
9002
  if (name) push("class", full, node);
9003
9003
  const body = node.childForFieldName("body");
9004
9004
  if (body && full) walkTypeBody(body, full);
@@ -9006,14 +9006,14 @@ function collectRustSymbolDefs(root) {
9006
9006
  }
9007
9007
  case "impl_item": {
9008
9008
  const typeName = node.childForFieldName("type")?.text;
9009
- const full = typeName ? join(modCtx, typeName) : modCtx;
9009
+ const full = typeName ? join2(modCtx, typeName) : modCtx;
9010
9010
  const body = node.childForFieldName("body");
9011
9011
  if (body && full) walkTypeBody(body, full);
9012
9012
  return;
9013
9013
  }
9014
9014
  case "function_item": {
9015
9015
  const name = node.childForFieldName("name")?.text;
9016
- if (name) push("function", join(modCtx, name), node);
9016
+ if (name) push("function", join2(modCtx, name), node);
9017
9017
  return;
9018
9018
  }
9019
9019
  }
@@ -9032,7 +9032,7 @@ function collectCppSymbolDefs(root) {
9032
9032
  endLine: node.endPosition.row + 1
9033
9033
  });
9034
9034
  };
9035
- const join = (prefix, name) => prefix ? `${prefix}::${name}` : name;
9035
+ const join2 = (prefix, name) => prefix ? `${prefix}::${name}` : name;
9036
9036
  const DECLARATOR_WRAPPERS = /* @__PURE__ */ new Set([
9037
9037
  "function_declarator",
9038
9038
  "pointer_declarator",
@@ -9058,7 +9058,7 @@ function collectCppSymbolDefs(root) {
9058
9058
  return void 0;
9059
9059
  };
9060
9060
  const classify = (nameText, prefix, enclosingClass) => {
9061
- const qualname = join(prefix, nameText);
9061
+ const qualname = join2(prefix, nameText);
9062
9062
  let className;
9063
9063
  let memberName;
9064
9064
  const idx = nameText.lastIndexOf("::");
@@ -9087,7 +9087,7 @@ function collectCppSymbolDefs(root) {
9087
9087
  switch (node.type) {
9088
9088
  case "namespace_definition": {
9089
9089
  const name = node.childForFieldName("name")?.text;
9090
- const newPrefix = name ? join(prefix, name) : prefix;
9090
+ const newPrefix = name ? join2(prefix, name) : prefix;
9091
9091
  const body = node.childForFieldName("body");
9092
9092
  if (body) walkChildren(body, newPrefix, void 0);
9093
9093
  return;
@@ -9095,7 +9095,7 @@ function collectCppSymbolDefs(root) {
9095
9095
  case "class_specifier":
9096
9096
  case "struct_specifier": {
9097
9097
  const name = node.childForFieldName("name")?.text;
9098
- const full = name ? join(prefix, name) : prefix;
9098
+ const full = name ? join2(prefix, name) : prefix;
9099
9099
  if (name) push("class", full, node);
9100
9100
  const body = node.childForFieldName("body");
9101
9101
  if (body && name) walkChildren(body, full, name);
@@ -16471,7 +16471,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
16471
16471
  init_cjs_shims();
16472
16472
  var import_fastify2 = __toESM(require("fastify"), 1);
16473
16473
  var import_cors = __toESM(require("@fastify/cors"), 1);
16474
- var import_types100 = require("@neat.is/types");
16474
+ var import_types101 = require("@neat.is/types");
16475
16475
 
16476
16476
  // src/extend/index.ts
16477
16477
  init_cjs_shims();
@@ -17701,7 +17701,7 @@ var INTENT_RULES = [
17701
17701
  },
17702
17702
  {
17703
17703
  intent: "blast-radius",
17704
- 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/
17704
+ 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/
17705
17705
  },
17706
17706
  {
17707
17707
  intent: "divergence",
@@ -17713,11 +17713,11 @@ var INTENT_RULES = [
17713
17713
  },
17714
17714
  {
17715
17715
  intent: "observed",
17716
- test: /\b(at\s+runtime|in\s+prod(?:uction)?|actually\s+call\w*|really\s+call\w*|observed|runtime\s+traffic)\b/
17716
+ test: /\b(at\s+runtime|in\s+prod(?:uction)?|actually|really\s+call\w*|observed|runtime\s+traffic|slow|latency|p95|timing)\b/
17717
17717
  },
17718
17718
  {
17719
17719
  intent: "dependencies",
17720
- test: /\b(depend\w*|calls?|uses?|imports?|relies\s+on|needs?)\b/
17720
+ 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/
17721
17721
  }
17722
17722
  ];
17723
17723
  function classifyIntent(question) {
@@ -17884,7 +17884,25 @@ var INTENT_WORDS = /* @__PURE__ */ new Set([
17884
17884
  "rely",
17885
17885
  "needs",
17886
17886
  "need",
17887
- "sync"
17887
+ "sync",
17888
+ "talk",
17889
+ "talks",
17890
+ "connect",
17891
+ "connects",
17892
+ "hit",
17893
+ "hits",
17894
+ "read",
17895
+ "reads",
17896
+ "write",
17897
+ "writes",
17898
+ "consumer",
17899
+ "consumers",
17900
+ "caller",
17901
+ "callers",
17902
+ "slow",
17903
+ "latency",
17904
+ "p95",
17905
+ "timing"
17888
17906
  ]);
17889
17907
  function tokens(input) {
17890
17908
  const spaced = input.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
@@ -17908,6 +17926,13 @@ function idBody(id) {
17908
17926
  const colon = id.indexOf(":");
17909
17927
  return colon >= 0 ? id.slice(colon + 1) : id;
17910
17928
  }
17929
+ var TYPE_NOUNS = [
17930
+ { nouns: ["database", "db", "table"], type: import_types62.NodeType.DatabaseNode },
17931
+ { nouns: ["service", "api", "backend"], type: import_types62.NodeType.ServiceNode },
17932
+ { nouns: ["route", "endpoint"], type: import_types62.NodeType.RouteNode },
17933
+ { nouns: ["file"], type: import_types62.NodeType.FileNode },
17934
+ { nouns: ["config"], type: import_types62.NodeType.ConfigNode }
17935
+ ];
17911
17936
  var EMBED_MIN_SCORE = 0.35;
17912
17937
  async function resolveEntities(graph, question, searchIndex, maxNodes) {
17913
17938
  const qTokens = queryTokens(question);
@@ -17915,6 +17940,7 @@ async function resolveEntities(graph, question, searchIndex, maxNodes) {
17915
17940
  const best = /* @__PURE__ */ new Map();
17916
17941
  const consider = (cand) => {
17917
17942
  const cur = best.get(cand.nodeId);
17943
+ if (cur?.via === "id" && cand.via === "embedding") return;
17918
17944
  if (!cur || cand.score > cur.score) best.set(cand.nodeId, cand);
17919
17945
  };
17920
17946
  graph.forEachNode((id, attrs) => {
@@ -17924,12 +17950,12 @@ async function resolveEntities(graph, question, searchIndex, maxNodes) {
17924
17950
  const body = idBody(id);
17925
17951
  const labelTokens = /* @__PURE__ */ new Set([...tokens(body), ...tokens(name)]);
17926
17952
  if (labelTokens.size === 0) return;
17927
- let matched = 0;
17928
- for (const t of qTokens) if (labelTokens.has(t)) matched += 1;
17953
+ let matched2 = 0;
17954
+ for (const t of qTokens) if (labelTokens.has(t)) matched2 += 1;
17929
17955
  const named2 = body.length >= 2 && normalized.includes(body.toLowerCase()) || name.length >= 2 && normalized.includes(name.toLowerCase());
17930
- if (matched === 0 && !named2) return;
17931
- const coverage = qTokens.length > 0 ? matched / qTokens.length : 0;
17932
- const via = named2 ? "id" : matched > 0 && tokens(name).some((t) => qTokens.includes(t)) ? "label" : "token";
17956
+ if (matched2 === 0 && !named2) return;
17957
+ const coverage = qTokens.length > 0 ? matched2 / qTokens.length : 0;
17958
+ const via = named2 ? "id" : matched2 > 0 && tokens(name).some((t) => qTokens.includes(t)) ? "label" : "token";
17933
17959
  const score = named2 ? Math.max(0.9, coverage) : Math.min(0.85, 0.3 + 0.7 * coverage);
17934
17960
  consider({ nodeId: id, label: name, via, score: Math.min(1, score) });
17935
17961
  });
@@ -17939,6 +17965,7 @@ async function resolveEntities(graph, question, searchIndex, maxNodes) {
17939
17965
  if (res.provider !== "substring") {
17940
17966
  for (const m of res.matches) {
17941
17967
  if (m.node.type === import_types62.NodeType.FrontierNode) continue;
17968
+ if (!graph.hasNode(m.node.id)) continue;
17942
17969
  const already = best.get(m.node.id);
17943
17970
  if (!already && m.score < EMBED_MIN_SCORE) continue;
17944
17971
  consider({
@@ -17952,10 +17979,31 @@ async function resolveEntities(graph, question, searchIndex, maxNodes) {
17952
17979
  } catch {
17953
17980
  }
17954
17981
  }
17955
- const viaRank = { id: 3, label: 2, token: 1, embedding: 0 };
17956
- return [...best.values()].sort(
17957
- (a, b) => b.score - a.score || viaRank[b.via] - viaRank[a.via] || a.nodeId.localeCompare(b.nodeId)
17982
+ if (best.size === 0) {
17983
+ const words = new Set(tokens(question));
17984
+ const kind = TYPE_NOUNS.find((entry) => entry.nouns.some((noun) => words.has(noun)));
17985
+ if (kind) {
17986
+ const ids = graph.filterNodes((_id, attrs) => attrs.type === kind.type).sort();
17987
+ if (ids.length > 1) {
17988
+ return { matched: [], ambiguousType: { noun: kind.nouns.find((noun) => words.has(noun)), ids } };
17989
+ }
17990
+ if (ids.length === 1) {
17991
+ const id = ids[0];
17992
+ return { matched: [{ nodeId: id, label: nodeName(graph.getNodeAttributes(id)), via: "type", score: 0.6 }].slice(0, maxNodes) };
17993
+ }
17994
+ return { matched: [] };
17995
+ }
17996
+ }
17997
+ const viaRank = { id: 4, label: 3, token: 2, type: 1, embedding: 0 };
17998
+ const ranked = [...best.values()].sort(
17999
+ (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)
18000
+ );
18001
+ const primary = ranked[0];
18002
+ const primaryType = primary && graph.getNodeAttributes(primary.nodeId).type;
18003
+ const matched = ranked.filter(
18004
+ (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
17958
18005
  ).slice(0, maxNodes).map((s) => ({ nodeId: s.nodeId, label: s.label, via: s.via, score: Number(s.score.toFixed(3)) }));
18006
+ return { matched };
17959
18007
  }
17960
18008
  function incidentsForNode(nodeId, incidents) {
17961
18009
  if (!incidents || incidents.length === 0) return [];
@@ -18047,6 +18095,23 @@ function buildBlastSection(graph, node) {
18047
18095
  facts
18048
18096
  };
18049
18097
  }
18098
+ function buildDatabaseCallersSection(graph, node) {
18099
+ const callers = getBlastRadius(graph, node).affectedNodes.filter((item) => {
18100
+ if (graph.getNodeAttributes(item.nodeId).type !== import_types62.NodeType.ServiceNode) return false;
18101
+ return item.path.slice(1, -1).every((id) => {
18102
+ const type = graph.getNodeAttributes(id).type;
18103
+ return type === import_types62.NodeType.FileNode || type === import_types62.NodeType.SymbolNode;
18104
+ });
18105
+ });
18106
+ return {
18107
+ heading: `Services calling ${node} \u2014 ${callers.length}`,
18108
+ facts: callers.length > 0 ? callers.slice(0, MAX_FACTS_PER_SECTION).map((item) => ({
18109
+ text: `${item.nodeId} (distance ${item.distance})`,
18110
+ provenance: item.edgeProvenance,
18111
+ confidence: item.confidence
18112
+ })) : [{ text: `No inbound service callers found for ${node}.` }]
18113
+ };
18114
+ }
18050
18115
  function buildIncidentsSection(node, incidents) {
18051
18116
  const relevant = incidentsForNode(node, incidents);
18052
18117
  if (relevant.length === 0) return null;
@@ -18073,6 +18138,29 @@ function divergenceLine(d) {
18073
18138
  }
18074
18139
  return `[${d.type}] ${d.source} \u2192 ${d.target} \u2014 ${d.reason}`;
18075
18140
  }
18141
+ function runtimeEdgeState(graph) {
18142
+ let observed = false;
18143
+ let stale = false;
18144
+ graph.forEachEdge((_id, attrs) => {
18145
+ if (attrs.provenance === import_types62.Provenance.OBSERVED) observed = true;
18146
+ if (attrs.provenance === import_types62.Provenance.STALE) stale = true;
18147
+ });
18148
+ return { observed, stale };
18149
+ }
18150
+ function pendingRuntimeMessage(graph, divergences) {
18151
+ const count = divergences.filter((d) => d.type === "missing-observed").length;
18152
+ if (count === 0) return void 0;
18153
+ const { observed, stale } = runtimeEdgeState(graph);
18154
+ if (observed) return void 0;
18155
+ const prefix = stale ? "No current runtime observations \u2014 earlier runtime evidence is stale" : "No runtime observed yet";
18156
+ 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.`;
18157
+ }
18158
+ function noDivergenceMessage(graph) {
18159
+ if (graph.order === 0) return "Graph is empty \u2014 no declared or observed dependencies to compare.";
18160
+ const { observed, stale } = runtimeEdgeState(graph);
18161
+ 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.";
18162
+ return "None \u2014 declared code and observed runtime agree across the graph.";
18163
+ }
18076
18164
  function buildDivergenceSection(graph, node, incidents) {
18077
18165
  const result = computeDivergences(graph, { node, ...incidents ? { incidents } : {} });
18078
18166
  if (result.totalAffected === 0) return null;
@@ -18089,19 +18177,28 @@ function buildDivergenceSection(graph, node, incidents) {
18089
18177
  }
18090
18178
  function buildGlobalDivergenceSection(graph, incidents) {
18091
18179
  const result = computeDivergences(graph, incidents ? { incidents } : {});
18180
+ const pending = pendingRuntimeMessage(graph, result.divergences);
18181
+ const visible = pending ? result.divergences.filter((d) => d.type !== "missing-observed") : result.divergences;
18182
+ if (visible.length === 0 && pending) {
18183
+ return {
18184
+ heading: "Divergences (EXTRACTED vs OBSERVED)",
18185
+ facts: [{ text: pending }]
18186
+ };
18187
+ }
18092
18188
  if (result.totalAffected === 0) {
18093
18189
  return {
18094
18190
  heading: "Divergences (EXTRACTED vs OBSERVED)",
18095
- facts: [{ text: "None \u2014 declared code and observed runtime agree across the graph." }]
18191
+ facts: [{ text: noDivergenceMessage(graph) }]
18096
18192
  };
18097
18193
  }
18098
- const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
18194
+ const facts = visible.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
18099
18195
  text: divergenceLine(d),
18100
18196
  confidence: d.confidence
18101
18197
  // Composite by construction (EXTRACTED vs OBSERVED), so no single provenance.
18102
18198
  }));
18199
+ if (pending && facts.length < MAX_FACTS_PER_SECTION) facts.push({ text: pending });
18103
18200
  return {
18104
- heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
18201
+ heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${visible.length}`,
18105
18202
  facts
18106
18203
  };
18107
18204
  }
@@ -18187,13 +18284,12 @@ function buildOverviewSections(graph, incidents) {
18187
18284
  });
18188
18285
  }
18189
18286
  const div = computeDivergences(graph, incidents ? { incidents } : {});
18287
+ const pending = pendingRuntimeMessage(graph, div.divergences);
18288
+ const visibleCount = pending ? div.divergences.filter((d) => d.type !== "missing-observed").length : div.totalAffected;
18289
+ 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);
18190
18290
  sections.push({
18191
18291
  heading: "Divergences",
18192
- facts: [
18193
- {
18194
- 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.`
18195
- }
18196
- ]
18292
+ facts: [{ text: divergenceText }]
18197
18293
  });
18198
18294
  return sections;
18199
18295
  }
@@ -18238,6 +18334,8 @@ function summarizeGlobal(intent, sections) {
18238
18334
  const lead = sections[0];
18239
18335
  switch (intent) {
18240
18336
  case "divergence": {
18337
+ const pending = lead?.facts[0]?.text;
18338
+ if (pending?.startsWith("No runtime") || pending?.startsWith("No current runtime") || pending?.startsWith("Graph is empty")) return pending;
18241
18339
  const none = lead?.facts[0]?.text.startsWith("None") ?? true;
18242
18340
  return none ? "No divergences across the graph \u2014 declared code and observed runtime agree." : `${lead.heading} across the graph, highest-confidence first below.`;
18243
18341
  }
@@ -18250,11 +18348,16 @@ function summarizeGlobal(intent, sections) {
18250
18348
  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.`;
18251
18349
  }
18252
18350
  }
18253
- function summarize(question, intent, matched, primary, sections, scope) {
18351
+ function summarize(question, intent, matched, primary, sections, scope, ambiguousType) {
18254
18352
  if (scope === "global") {
18255
18353
  return summarizeGlobal(intent, sections);
18256
18354
  }
18257
18355
  if (!primary) {
18356
+ if (ambiguousType) {
18357
+ const shown = ambiguousType.ids.slice(0, 10);
18358
+ const more = ambiguousType.ids.length - shown.length;
18359
+ return `"${ambiguousType.noun}" matches ${ambiguousType.ids.length} nodes. Name one by ID: ${shown.join(", ")}${more > 0 ? `, and ${more} more` : ""}.`;
18360
+ }
18258
18361
  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.`;
18259
18362
  }
18260
18363
  const lead = sections[0];
@@ -18277,10 +18380,13 @@ function summarize(question, intent, matched, primary, sections, scope) {
18277
18380
  core = lead ? `${lead.heading} of ${primary}.` : `${primary} has no dependents \u2014 nothing else would break if it failed.`;
18278
18381
  break;
18279
18382
  case "dependencies":
18280
- core = lead ? `${primary}: ${lead.heading.toLowerCase()} listed below.` : `${primary} has no declared dependencies in the graph.`;
18383
+ core = lead?.heading.startsWith("Services calling") ? `${lead.heading}.` : lead ? `${primary}: ${lead.heading.toLowerCase()} listed below.` : `${primary} has no declared dependencies in the graph.`;
18281
18384
  break;
18282
18385
  case "observed":
18283
- core = lead ? `${primary} at runtime: ${lead.facts.length} OBSERVED fact${lead.facts.length === 1 ? "" : "s"}.` : `No runtime traffic OBSERVED for ${primary}.`;
18386
+ {
18387
+ const observed = sections.find((s) => s.heading === "Runtime dependencies (OBSERVED)");
18388
+ core = observed ? `${primary} at runtime: ${observed.facts.length} OBSERVED fact${observed.facts.length === 1 ? "" : "s"}.` : `No runtime traffic OBSERVED for ${primary}.`;
18389
+ }
18284
18390
  break;
18285
18391
  case "incidents":
18286
18392
  core = lead ? `${primary}: ${lead.heading.toLowerCase()}.` : `No incidents recorded against ${primary}.`;
@@ -18297,24 +18403,28 @@ async function askGraph(graph, question, opts = {}) {
18297
18403
  const now = opts.now ?? Date.now();
18298
18404
  const maxNodes = opts.maxNodes ?? DEFAULT_MAX_NODES;
18299
18405
  const intent = classifyIntent(question);
18300
- const matched = await resolveEntities(graph, question, opts.searchIndex, maxNodes);
18406
+ const resolution = await resolveEntities(graph, question, opts.searchIndex, maxNodes);
18407
+ const matched = resolution.matched;
18301
18408
  const primary = matched[0]?.nodeId;
18302
18409
  const sections = [];
18303
18410
  let scope;
18304
18411
  if (primary) {
18305
18412
  scope = "node";
18413
+ 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);
18414
+ if (callersQuestion) sections.push(buildDatabaseCallersSection(graph, primary));
18306
18415
  for (const kind of SECTION_ORDER[intent]) {
18416
+ if (callersQuestion && kind === "blast") continue;
18307
18417
  const s = buildSection(kind, graph, primary, opts.incidents, now);
18308
18418
  if (s && s.facts.length > 0) sections.push(s);
18309
18419
  }
18310
18420
  } else {
18311
- const global = buildGlobalSections(intent, graph, opts.incidents);
18421
+ const global = resolution.ambiguousType ? null : buildGlobalSections(intent, graph, opts.incidents);
18312
18422
  if (global) {
18313
18423
  scope = "global";
18314
18424
  for (const s of global) if (s.facts.length > 0) sections.push(s);
18315
18425
  }
18316
18426
  }
18317
- const answer = summarize(question, intent, matched, primary, sections, scope);
18427
+ const answer = summarize(question, intent, matched, primary, sections, scope, resolution.ambiguousType);
18318
18428
  const provSet = /* @__PURE__ */ new Set();
18319
18429
  for (const s of sections) for (const f of s.facts) if (f.provenance) provSet.add(f.provenance);
18320
18430
  const confidence = sections[0]?.facts[0]?.confidence;
@@ -20252,9 +20362,9 @@ var RESOURCE_TYPES = [
20252
20362
  function isFirebaseResourceType(value) {
20253
20363
  return RESOURCE_TYPES.includes(value);
20254
20364
  }
20255
- function buildEntriesFilter(sinceIso) {
20365
+ function buildEntriesFilter(sinceIso, resourceTypes = RESOURCE_TYPES) {
20256
20366
  return [
20257
- 'resource.type = ("cloud_function" OR "cloud_run_revision" OR "firebase_domain")',
20367
+ `resource.type = (${resourceTypes.map((t) => `"${t}"`).join(" OR ")})`,
20258
20368
  "httpRequest:*",
20259
20369
  `timestamp >= "${sinceIso}"`
20260
20370
  ].join(" AND ");
@@ -20263,8 +20373,8 @@ var DEFAULT_LOOKBACK_MS = 24 * 60 * 60 * 1e3;
20263
20373
  var ENTRIES_LIST_URL = "https://logging.googleapis.com/v2/entries:list";
20264
20374
  var PAGE_SIZE = 1e3;
20265
20375
  var MAX_PAGES = 20;
20266
- async function fetchHttpRequestLogEntries(creds, sinceIso) {
20267
- const filter = buildEntriesFilter(sinceIso);
20376
+ async function fetchHttpRequestLogEntries(creds, sinceIso, resourceTypes) {
20377
+ const filter = buildEntriesFilter(sinceIso, resourceTypes);
20268
20378
  const out = [];
20269
20379
  let pageToken;
20270
20380
  for (let page = 0; page < MAX_PAGES; page++) {
@@ -20379,7 +20489,38 @@ function mapLogEntriesToSignals(entries) {
20379
20489
 
20380
20490
  // src/connectors/firebase/resolve.ts
20381
20491
  init_cjs_shims();
20492
+ var import_types72 = require("@neat.is/types");
20493
+
20494
+ // src/connectors/infer-service.ts
20495
+ init_cjs_shims();
20382
20496
  var import_types71 = require("@neat.is/types");
20497
+ function normalizeName(name) {
20498
+ return name.toLowerCase().replace(/[^a-z0-9]/g, "");
20499
+ }
20500
+ function inferServiceName(graph, resourceName, method, path79) {
20501
+ const wantedMethod = method.toUpperCase();
20502
+ const wantedPath = normalizePathTemplate(path79);
20503
+ const services = [];
20504
+ const owners = /* @__PURE__ */ new Set();
20505
+ graph.forEachNode((_id, attrs) => {
20506
+ const node = attrs;
20507
+ if (node.type === import_types71.NodeType.ServiceNode && typeof node.name === "string") {
20508
+ services.push(node.name);
20509
+ return;
20510
+ }
20511
+ if (node.type !== import_types71.NodeType.RouteNode) return;
20512
+ const route = attrs;
20513
+ if (!route.pathTemplate || normalizePathTemplate(route.pathTemplate) !== wantedPath) return;
20514
+ const routeMethod = route.method.toUpperCase();
20515
+ if (routeMethod === "ALL" || routeMethod === wantedMethod) owners.add(route.service);
20516
+ });
20517
+ const wanted = normalizeName(resourceName);
20518
+ const byName = services.find((s) => normalizeName(s) === wanted);
20519
+ if (byName) return byName;
20520
+ return owners.size === 1 ? [...owners][0] : null;
20521
+ }
20522
+
20523
+ // src/connectors/firebase/resolve.ts
20383
20524
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
20384
20525
  switch (resourceType) {
20385
20526
  case "cloud_function":
@@ -20394,7 +20535,7 @@ function routeEntriesFor(graph, serviceName) {
20394
20535
  const entries = [];
20395
20536
  graph.forEachNode((_id, attrs) => {
20396
20537
  const node = attrs;
20397
- if (node.type !== import_types71.NodeType.RouteNode) return;
20538
+ if (node.type !== import_types72.NodeType.RouteNode) return;
20398
20539
  const route = attrs;
20399
20540
  if (route.service !== serviceName) return;
20400
20541
  entries.push({
@@ -20418,32 +20559,38 @@ function createFirebaseResolveTarget(graph, serviceMap) {
20418
20559
  }
20419
20560
  const identity = parseFirebaseTargetName(signal.targetName);
20420
20561
  if (!identity) return null;
20421
- const serviceName = neatServiceNameFor(resourceType, identity.resourceName, serviceMap);
20422
- if (!serviceName) return null;
20423
20562
  const normalizedPath = normalizePathTemplate(identity.path);
20563
+ const serviceName = neatServiceNameFor(resourceType, identity.resourceName, serviceMap) ?? (serviceMap.inferServices ? inferServiceName(graph, identity.resourceName, identity.method, identity.path) : null);
20564
+ if (!serviceName) return null;
20424
20565
  const match = findRoute2(routeEntriesFor(graph, serviceName), identity.method, normalizedPath);
20425
20566
  if (!match) return null;
20426
20567
  return {
20427
20568
  targetNodeId: match.routeNodeId,
20428
20569
  serviceName,
20429
- edgeType: import_types71.EdgeType.CALLS
20570
+ edgeType: import_types72.EdgeType.CALLS
20430
20571
  };
20431
20572
  };
20432
20573
  }
20433
20574
 
20434
20575
  // src/connectors/firebase/index.ts
20435
20576
  var FirebaseConnector = class {
20577
+ constructor(opts = {}) {
20578
+ this.opts = opts;
20579
+ }
20580
+ opts;
20436
20581
  provider = "firebase";
20437
20582
  async poll(ctx) {
20438
20583
  const creds = readFirebaseCredentials(ctx.credentials);
20439
20584
  const sinceIso = ctx.since ?? new Date(Date.now() - DEFAULT_LOOKBACK_MS).toISOString();
20440
- const entries = await fetchHttpRequestLogEntries(creds, sinceIso);
20585
+ const entries = await fetchHttpRequestLogEntries(creds, sinceIso, this.opts.resourceTypes);
20441
20586
  return mapLogEntriesToSignals(entries);
20442
20587
  }
20443
20588
  };
20444
20589
  function createFirebaseConnector(graph, serviceMap) {
20445
20590
  return {
20446
- connector: new FirebaseConnector(),
20591
+ connector: new FirebaseConnector(
20592
+ serviceMap.excludeCloudRun ? { resourceTypes: ["cloud_function", "firebase_domain"] } : {}
20593
+ ),
20447
20594
  resolveTarget: createFirebaseResolveTarget(graph, serviceMap)
20448
20595
  };
20449
20596
  }
@@ -20453,7 +20600,7 @@ init_cjs_shims();
20453
20600
 
20454
20601
  // src/connectors/cloudflare/connector.ts
20455
20602
  init_cjs_shims();
20456
- var import_types73 = require("@neat.is/types");
20603
+ var import_types74 = require("@neat.is/types");
20457
20604
 
20458
20605
  // src/connectors/cloudflare/client.ts
20459
20606
  init_cjs_shims();
@@ -20617,7 +20764,7 @@ function findTaggedWorkerFileNode(graph, workerName) {
20617
20764
  graph.forEachNode((id, attrs) => {
20618
20765
  if (found) return;
20619
20766
  const a = attrs;
20620
- if (a.type === import_types73.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
20767
+ if (a.type === import_types74.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
20621
20768
  found = id;
20622
20769
  }
20623
20770
  });
@@ -20629,7 +20776,7 @@ function findMatchingRouteNode(graph, serviceName, method, path79) {
20629
20776
  graph.forEachNode((id, attrs) => {
20630
20777
  if (found) return;
20631
20778
  const a = attrs;
20632
- if (a.type !== import_types73.NodeType.RouteNode || a.service !== serviceName) return;
20779
+ if (a.type !== import_types74.NodeType.RouteNode || a.service !== serviceName) return;
20633
20780
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
20634
20781
  const routeMethod = (a.method ?? "").toUpperCase();
20635
20782
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -20648,11 +20795,11 @@ function createCloudflareResolveTarget(config, graph) {
20648
20795
  };
20649
20796
  const mapping = config.workers?.[scriptName];
20650
20797
  if (mapping) {
20651
- const wholeFileId = (0, import_types73.fileId)(mapping.service, mapping.entryFile);
20798
+ const wholeFileId = (0, import_types74.fileId)(mapping.service, mapping.entryFile);
20652
20799
  return {
20653
20800
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
20654
20801
  serviceName: mapping.service,
20655
- edgeType: import_types73.EdgeType.CALLS
20802
+ edgeType: import_types74.EdgeType.CALLS
20656
20803
  };
20657
20804
  }
20658
20805
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -20661,13 +20808,13 @@ function createCloudflareResolveTarget(config, graph) {
20661
20808
  return {
20662
20809
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
20663
20810
  serviceName: fileNode.service,
20664
- edgeType: import_types73.EdgeType.CALLS
20811
+ edgeType: import_types74.EdgeType.CALLS
20665
20812
  };
20666
20813
  }
20667
20814
  return {
20668
- targetNodeId: (0, import_types73.infraId)("cloudflare-worker", scriptName),
20815
+ targetNodeId: (0, import_types74.infraId)("cloudflare-worker", scriptName),
20669
20816
  serviceName: scriptName,
20670
- edgeType: import_types73.EdgeType.CALLS,
20817
+ edgeType: import_types74.EdgeType.CALLS,
20671
20818
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
20672
20819
  };
20673
20820
  };
@@ -20863,14 +21010,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
20863
21010
 
20864
21011
  // src/connectors/neon/resolve.ts
20865
21012
  init_cjs_shims();
20866
- var import_types77 = require("@neat.is/types");
21013
+ var import_types78 = require("@neat.is/types");
20867
21014
  function createNeonResolveTarget(config) {
20868
21015
  return (signal) => {
20869
21016
  if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
20870
21017
  return {
20871
- targetNodeId: (0, import_types77.infraId)("sql-table", signal.targetName),
21018
+ targetNodeId: (0, import_types78.infraId)("sql-table", signal.targetName),
20872
21019
  serviceName: config.serviceName,
20873
- edgeType: import_types77.EdgeType.CALLS,
21020
+ edgeType: import_types78.EdgeType.CALLS,
20874
21021
  ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
20875
21022
  };
20876
21023
  };
@@ -21051,14 +21198,14 @@ function mapLogEntriesToSignals2(entries) {
21051
21198
 
21052
21199
  // src/connectors/cloud-run/resolve.ts
21053
21200
  init_cjs_shims();
21054
- var import_types81 = require("@neat.is/types");
21201
+ var import_types82 = require("@neat.is/types");
21055
21202
  var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
21056
21203
  function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
21057
21204
  let found = null;
21058
21205
  graph.forEachNode((_id, attrs) => {
21059
21206
  if (found) return;
21060
21207
  const node = attrs;
21061
- if (node.type !== import_types81.NodeType.RouteNode) return;
21208
+ if (node.type !== import_types82.NodeType.RouteNode) return;
21062
21209
  const route = attrs;
21063
21210
  if (route.service !== serviceName || !route.pathTemplate) return;
21064
21211
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -21074,7 +21221,7 @@ function createCloudRunResolveTarget(graph, config) {
21074
21221
  const identity = parseCloudRunTargetName(signal.targetName);
21075
21222
  if (!identity) return null;
21076
21223
  const { serviceName: gcpServiceName, method, path: path79 } = identity;
21077
- const mappedService = config.serviceMap?.[gcpServiceName];
21224
+ const mappedService = config.serviceMap?.[gcpServiceName] ?? (config.inferServices ? inferServiceName(graph, gcpServiceName, method, path79) ?? void 0 : void 0);
21078
21225
  if (mappedService) {
21079
21226
  const routeNodeId = findMatchingRouteNode2(
21080
21227
  graph,
@@ -21083,13 +21230,13 @@ function createCloudRunResolveTarget(graph, config) {
21083
21230
  normalizePathTemplate(path79)
21084
21231
  );
21085
21232
  if (routeNodeId) {
21086
- return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types81.EdgeType.CALLS };
21233
+ return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types82.EdgeType.CALLS };
21087
21234
  }
21088
21235
  }
21089
21236
  return {
21090
- targetNodeId: (0, import_types81.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
21237
+ targetNodeId: (0, import_types82.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
21091
21238
  serviceName: mappedService ?? gcpServiceName,
21092
- edgeType: import_types81.EdgeType.CALLS,
21239
+ edgeType: import_types82.EdgeType.CALLS,
21093
21240
  ensureInfraNode: {
21094
21241
  kind: CLOUD_RUN_SERVICE_INFRA_KIND,
21095
21242
  name: gcpServiceName,
@@ -21271,14 +21418,14 @@ function mapLogEntriesToSignals3(entries) {
21271
21418
 
21272
21419
  // src/connectors/gcp-lb/resolve.ts
21273
21420
  init_cjs_shims();
21274
- var import_types85 = require("@neat.is/types");
21421
+ var import_types86 = require("@neat.is/types");
21275
21422
  var GCP_LB_BACKEND_INFRA_KIND = "gcp-lb-backend";
21276
21423
  function findMatchingRouteNode3(graph, serviceName, method, normalizedPath) {
21277
21424
  let found = null;
21278
21425
  graph.forEachNode((_id, attrs) => {
21279
21426
  if (found) return;
21280
21427
  const node = attrs;
21281
- if (node.type !== import_types85.NodeType.RouteNode) return;
21428
+ if (node.type !== import_types86.NodeType.RouteNode) return;
21282
21429
  const route = attrs;
21283
21430
  if (route.service !== serviceName || !route.pathTemplate) return;
21284
21431
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -21294,7 +21441,7 @@ function createGcpLbResolveTarget(graph, config) {
21294
21441
  const identity = parseGcpLbTargetName(signal.targetName);
21295
21442
  if (!identity) return null;
21296
21443
  const { backendServiceName, method, path: path79 } = identity;
21297
- const mappedService = config.backendServiceMap?.[backendServiceName];
21444
+ const mappedService = config.backendServiceMap?.[backendServiceName] ?? (config.inferServices ? inferServiceName(graph, backendServiceName, method, path79) ?? void 0 : void 0);
21298
21445
  if (mappedService) {
21299
21446
  const routeNodeId = findMatchingRouteNode3(
21300
21447
  graph,
@@ -21303,13 +21450,13 @@ function createGcpLbResolveTarget(graph, config) {
21303
21450
  normalizePathTemplate(path79)
21304
21451
  );
21305
21452
  if (routeNodeId) {
21306
- return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types85.EdgeType.CALLS };
21453
+ return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types86.EdgeType.CALLS };
21307
21454
  }
21308
21455
  }
21309
21456
  return {
21310
- targetNodeId: (0, import_types85.infraId)(GCP_LB_BACKEND_INFRA_KIND, backendServiceName),
21457
+ targetNodeId: (0, import_types86.infraId)(GCP_LB_BACKEND_INFRA_KIND, backendServiceName),
21311
21458
  serviceName: mappedService ?? backendServiceName,
21312
- edgeType: import_types85.EdgeType.CALLS,
21459
+ edgeType: import_types86.EdgeType.CALLS,
21313
21460
  ensureInfraNode: {
21314
21461
  kind: GCP_LB_BACKEND_INFRA_KIND,
21315
21462
  name: backendServiceName,
@@ -21350,7 +21497,7 @@ function createGcpLbConnector(graph, config = {}) {
21350
21497
 
21351
21498
  // src/connectors/render/index.ts
21352
21499
  init_cjs_shims();
21353
- var import_types88 = require("@neat.is/types");
21500
+ var import_types89 = require("@neat.is/types");
21354
21501
 
21355
21502
  // src/connectors/render/types.ts
21356
21503
  init_cjs_shims();
@@ -21428,7 +21575,7 @@ function buildRenderRouteIndex(graph, serviceName) {
21428
21575
  const out = [];
21429
21576
  graph.forEachNode((_id, attrs) => {
21430
21577
  const node = attrs;
21431
- if (node.type !== import_types88.NodeType.RouteNode) return;
21578
+ if (node.type !== import_types89.NodeType.RouteNode) return;
21432
21579
  const route = attrs;
21433
21580
  if (route.service !== serviceName) return;
21434
21581
  out.push({
@@ -21513,7 +21660,7 @@ function mapRenderRequestLogsToSignals(entries, routeIndex) {
21513
21660
  function createRenderResolveTarget(config) {
21514
21661
  return (signal) => {
21515
21662
  if (signal.targetKind === ROUTE_TARGET_KIND2) {
21516
- return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types88.EdgeType.CALLS };
21663
+ return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types89.EdgeType.CALLS };
21517
21664
  }
21518
21665
  return null;
21519
21666
  };
@@ -21651,21 +21798,21 @@ function mapInsightsToSignals(rows, observedAtIso) {
21651
21798
 
21652
21799
  // src/connectors/planetscale/resolve.ts
21653
21800
  init_cjs_shims();
21654
- var import_types92 = require("@neat.is/types");
21801
+ var import_types93 = require("@neat.is/types");
21655
21802
  var PLANETSCALE_DATABASE_KIND = "planetscale-database";
21656
21803
  function createPlanetscaleResolveTarget(graph, config) {
21657
21804
  const databaseName = `${config.organization}/${config.database}`;
21658
21805
  return (signal, _ctx) => {
21659
21806
  if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
21660
- const tableId = (0, import_types92.infraId)("sql-table", signal.targetName);
21807
+ const tableId = (0, import_types93.infraId)("sql-table", signal.targetName);
21661
21808
  if (graph.hasNode(tableId)) {
21662
- return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types92.EdgeType.CALLS };
21809
+ return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types93.EdgeType.CALLS };
21663
21810
  }
21664
- const providerId = (0, import_types92.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
21811
+ const providerId = (0, import_types93.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
21665
21812
  return {
21666
21813
  targetNodeId: providerId,
21667
21814
  serviceName: config.serviceName,
21668
- edgeType: import_types92.EdgeType.CALLS,
21815
+ edgeType: import_types93.EdgeType.CALLS,
21669
21816
  ensureInfraNode: { kind: PLANETSCALE_DATABASE_KIND, name: databaseName, provider: "planetscale" }
21670
21817
  };
21671
21818
  };
@@ -21930,7 +22077,7 @@ function mapBuildsToSignals(builds, serviceName) {
21930
22077
 
21931
22078
  // src/connectors/eas/resolve.ts
21932
22079
  init_cjs_shims();
21933
- var import_types97 = require("@neat.is/types");
22080
+ var import_types98 = require("@neat.is/types");
21934
22081
  var NO_ENV2 = "unknown";
21935
22082
  var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
21936
22083
  var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
@@ -21946,8 +22093,8 @@ function configBasenamesForPhase(phase) {
21946
22093
  function configNodeService(graph, configNodeId) {
21947
22094
  for (const edgeId of graph.inboundEdges(configNodeId)) {
21948
22095
  const edge = graph.getEdgeAttributes(edgeId);
21949
- if (edge.type !== import_types97.EdgeType.CONFIGURED_BY) continue;
21950
- const parsed = (0, import_types97.parseFileId)(edge.source);
22096
+ if (edge.type !== import_types98.EdgeType.CONFIGURED_BY) continue;
22097
+ const parsed = (0, import_types98.parseFileId)(edge.source);
21951
22098
  if (parsed) return parsed.service;
21952
22099
  }
21953
22100
  return null;
@@ -21958,7 +22105,7 @@ function findConfigNode(graph, basenames, serviceName) {
21958
22105
  graph.forEachNode((id, attrs) => {
21959
22106
  if (scoped) return;
21960
22107
  const node = attrs;
21961
- if (node.type !== import_types97.NodeType.ConfigNode) return;
22108
+ if (node.type !== import_types98.NodeType.ConfigNode) return;
21962
22109
  if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
21963
22110
  if (anyMatch === null) anyMatch = id;
21964
22111
  if (configNodeService(graph, id) === serviceName) scoped = id;
@@ -21975,13 +22122,13 @@ function createEasResolveTarget(graph) {
21975
22122
  if (basenames.length > 0) {
21976
22123
  const configNodeId = findConfigNode(graph, basenames, serviceName);
21977
22124
  if (configNodeId) {
21978
- return { targetNodeId: configNodeId, serviceName, edgeType: import_types97.EdgeType.CALLS };
22125
+ return { targetNodeId: configNodeId, serviceName, edgeType: import_types98.EdgeType.CALLS };
21979
22126
  }
21980
22127
  }
21981
22128
  return {
21982
22129
  targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV2),
21983
22130
  serviceName,
21984
- edgeType: import_types97.EdgeType.CALLS
22131
+ edgeType: import_types98.EdgeType.CALLS
21985
22132
  };
21986
22133
  };
21987
22134
  }
@@ -22469,6 +22616,9 @@ var PUSH_PROVIDER_DISPATCH = {
22469
22616
  }
22470
22617
  }
22471
22618
  };
22619
+ function getPushProviderDispatch(provider) {
22620
+ return PUSH_PROVIDER_DISPATCH[provider];
22621
+ }
22472
22622
  function isPushProvider(provider) {
22473
22623
  return provider in PUSH_PROVIDER_DISPATCH;
22474
22624
  }
@@ -22762,11 +22912,11 @@ function registerRoutes(scope, ctx) {
22762
22912
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
22763
22913
  const parsed = [];
22764
22914
  for (const c of candidates) {
22765
- const r = import_types100.DivergenceTypeSchema.safeParse(c);
22915
+ const r = import_types101.DivergenceTypeSchema.safeParse(c);
22766
22916
  if (!r.success) {
22767
22917
  return reply.code(400).send({
22768
22918
  error: `unknown divergence type "${c}"`,
22769
- allowed: import_types100.DivergenceTypeSchema.options
22919
+ allowed: import_types101.DivergenceTypeSchema.options
22770
22920
  });
22771
22921
  }
22772
22922
  parsed.push(r.data);
@@ -23162,7 +23312,7 @@ function registerRoutes(scope, ctx) {
23162
23312
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
23163
23313
  let violations = await log.readAll();
23164
23314
  if (req.query.severity) {
23165
- const sev = import_types100.PolicySeveritySchema.safeParse(req.query.severity);
23315
+ const sev = import_types101.PolicySeveritySchema.safeParse(req.query.severity);
23166
23316
  if (!sev.success) {
23167
23317
  return reply.code(400).send({
23168
23318
  error: "invalid severity",
@@ -23201,7 +23351,7 @@ function registerRoutes(scope, ctx) {
23201
23351
  scope.post("/policies/check", async (req, reply) => {
23202
23352
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
23203
23353
  if (!proj) return;
23204
- const parsed = import_types100.PoliciesCheckBodySchema.safeParse(req.body ?? {});
23354
+ const parsed = import_types101.PoliciesCheckBodySchema.safeParse(req.body ?? {});
23205
23355
  if (!parsed.success) {
23206
23356
  return reply.code(400).send({
23207
23357
  error: "invalid /policies/check body",
@@ -23523,13 +23673,13 @@ init_otel_grpc();
23523
23673
  // src/daemon.ts
23524
23674
  init_cjs_shims();
23525
23675
  var import_node_fs41 = require("fs");
23526
- var import_node_path78 = __toESM(require("path"), 1);
23676
+ var import_node_path79 = __toESM(require("path"), 1);
23527
23677
  var import_node_module = require("module");
23528
23678
  init_otel();
23529
23679
 
23530
23680
  // src/hang-sensor.ts
23531
23681
  init_cjs_shims();
23532
- var import_types101 = require("@neat.is/types");
23682
+ var import_types102 = require("@neat.is/types");
23533
23683
  var RECONCILE_INCIDENT_LIMIT = 500;
23534
23684
  function stageHangSurfaces(graph, incidents) {
23535
23685
  const boundaries = /* @__PURE__ */ new Set();
@@ -23542,7 +23692,7 @@ function stageHangSurfaces(graph, incidents) {
23542
23692
  if (!isBoundaryTimeoutSymptom(ctx)) continue;
23543
23693
  const hop = resolveHangHop(graph, boundary, incidents);
23544
23694
  if (!hop) continue;
23545
- if (stageFrontierEdge(graph, hop.source, hop.target, import_types101.EdgeType.CALLS)) staged++;
23695
+ if (stageFrontierEdge(graph, hop.source, hop.target, import_types102.EdgeType.CALLS)) staged++;
23546
23696
  }
23547
23697
  return staged;
23548
23698
  }
@@ -23559,6 +23709,8 @@ async function reconcileFrontierSurfaces(graph, errorsPath) {
23559
23709
 
23560
23710
  // src/connectors/hosted.ts
23561
23711
  init_cjs_shims();
23712
+ var import_promises = require("fs/promises");
23713
+ var import_node_path75 = require("path");
23562
23714
 
23563
23715
  // src/connectors/railway/target-ref.ts
23564
23716
  init_cjs_shims();
@@ -23605,11 +23757,14 @@ function credentialRecord(provider, cred) {
23605
23757
  switch (provider) {
23606
23758
  case "supabase":
23607
23759
  return { managementToken: cred.accessToken };
23760
+ case "gcp":
23761
+ if (!cred.projectRef) throw new Error("gcp credential delivered without a project ref");
23762
+ return { projectId: cred.projectRef, accessToken: cred.accessToken };
23608
23763
  default:
23609
23764
  return { token: cred.accessToken };
23610
23765
  }
23611
23766
  }
23612
- function hostedOptions(provider, summary, serviceName) {
23767
+ function hostedOptions(provider, summary, serviceName, firebaseServiceMap) {
23613
23768
  switch (provider) {
23614
23769
  case "supabase": {
23615
23770
  const ref = summary.projectRef;
@@ -23625,10 +23780,125 @@ function hostedOptions(provider, summary, serviceName) {
23625
23780
  serviceNameById: { [target.serviceId]: serviceName }
23626
23781
  };
23627
23782
  }
23783
+ case "firebase": {
23784
+ return { inferServices: true, excludeCloudRun: true, ...firebaseServiceMap ?? {} };
23785
+ }
23786
+ case "cloud-run":
23787
+ case "gcp-lb":
23788
+ return { inferServices: true };
23628
23789
  default:
23629
23790
  return null;
23630
23791
  }
23631
23792
  }
23793
+ function parseFirebaseServiceMap(raw) {
23794
+ if (!raw) return void 0;
23795
+ try {
23796
+ const parsed = JSON.parse(raw);
23797
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
23798
+ const out = {};
23799
+ for (const key of ["functions", "cloudRun", "hosting"]) {
23800
+ const group = parsed[key];
23801
+ if (group === void 0) continue;
23802
+ if (!group || typeof group !== "object" || Array.isArray(group)) return void 0;
23803
+ const entries = Object.entries(group);
23804
+ if (entries.some(([, v]) => typeof v !== "string" || v.length === 0)) return void 0;
23805
+ out[key] = Object.fromEntries(entries);
23806
+ }
23807
+ return Object.keys(out).length > 0 ? out : void 0;
23808
+ } catch {
23809
+ return void 0;
23810
+ }
23811
+ }
23812
+ function pushHandlesPath(projectDir) {
23813
+ return (0, import_node_path75.join)(projectDir, "neat-out", "connectors-hosted.json");
23814
+ }
23815
+ async function readPushHandles(projectDir) {
23816
+ try {
23817
+ const parsed = JSON.parse(await (0, import_promises.readFile)(pushHandlesPath(projectDir), "utf8"));
23818
+ return parsed && typeof parsed === "object" ? parsed : {};
23819
+ } catch {
23820
+ return {};
23821
+ }
23822
+ }
23823
+ async function writePushHandle(projectDir, provider, handle) {
23824
+ const path79 = pushHandlesPath(projectDir);
23825
+ const handles = await readPushHandles(projectDir);
23826
+ handles[provider] = handle;
23827
+ await (0, import_promises.mkdir)((0, import_node_path75.dirname)(path79), { recursive: true });
23828
+ const tmp = `${path79}.${process.pid}.tmp`;
23829
+ await (0, import_promises.writeFile)(tmp, `${JSON.stringify(handles, null, 2)}
23830
+ `, "utf8");
23831
+ await (0, import_promises.rename)(tmp, path79);
23832
+ }
23833
+ function pushCredentialRecord(provider, cred, deps) {
23834
+ switch (provider) {
23835
+ case "vercel":
23836
+ return { token: cred.accessToken, otelToken: deps.otelToken ?? deps.daemonToken };
23837
+ default:
23838
+ return { token: cred.accessToken };
23839
+ }
23840
+ }
23841
+ function hostedPushOptions(provider, summary, deps) {
23842
+ switch (provider) {
23843
+ case "vercel": {
23844
+ if (!deps.publicUrl) {
23845
+ return { skip: "no public URL for this daemon (NEAT_PUBLIC_URL) \u2014 a drain has nowhere to deliver" };
23846
+ }
23847
+ if (!summary.projectRef) return { skip: "no Vercel team selected yet \u2014 drains are team-scoped" };
23848
+ const endpoint2 = `${deps.publicUrl.replace(/\/+$/, "")}/v1/traces`;
23849
+ return { options: { teamId: summary.projectRef, endpoint: endpoint2 }, endpoint: endpoint2 };
23850
+ }
23851
+ default:
23852
+ return { skip: "no hosted option mapping for this push provider" };
23853
+ }
23854
+ }
23855
+ async function provisionHostedPushProvider(summary, dispatch, input) {
23856
+ const { deps, projectDir, onSkip, onProvisioned } = input;
23857
+ const provider = summary.provider;
23858
+ const mapped = hostedPushOptions(provider, summary, deps);
23859
+ if ("skip" in mapped) {
23860
+ onSkip?.(provider, mapped.skip);
23861
+ return;
23862
+ }
23863
+ let cred;
23864
+ try {
23865
+ cred = await cpGet(
23866
+ `/internal/projects/${deps.projectId}/connections/${provider}/credential`,
23867
+ deps
23868
+ );
23869
+ } catch (err) {
23870
+ onSkip?.(provider, `credential delivery failed \u2014 ${err.message}`);
23871
+ return;
23872
+ }
23873
+ const credentials = pushCredentialRecord(provider, cred, deps);
23874
+ const existing = (await readPushHandles(projectDir))[provider];
23875
+ const alreadyProvisioned = existing !== void 0 && existing.endpoint === mapped.endpoint;
23876
+ const options = alreadyProvisioned ? { ...mapped.options, ...existing.options } : mapped.options;
23877
+ const seam = deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {};
23878
+ const validation = await dispatch.validate({ credentials, options, ...seam });
23879
+ if (!validation.ok) {
23880
+ onSkip?.(provider, `drain delivery test failed \u2014 ${validation.reason}`);
23881
+ return;
23882
+ }
23883
+ if (alreadyProvisioned) {
23884
+ onProvisioned?.(provider, "drain already provisioned \u2014 delivery re-validated");
23885
+ return;
23886
+ }
23887
+ const result = await dispatch.provision({ credentials, options, ...seam });
23888
+ if (!result.ok) {
23889
+ onSkip?.(provider, `drain provisioning failed \u2014 ${result.reason}`);
23890
+ return;
23891
+ }
23892
+ await writePushHandle(projectDir, provider, {
23893
+ endpoint: mapped.endpoint,
23894
+ options: result.options ?? {},
23895
+ provisionedAt: (/* @__PURE__ */ new Date()).toISOString()
23896
+ });
23897
+ onProvisioned?.(provider, result.note);
23898
+ }
23899
+ var HOSTED_CONNECTORS_FOR = {
23900
+ gcp: ["firebase", "cloud-run", "gcp-lb"]
23901
+ };
23632
23902
  function createHostedCredentialSource(provider, deps) {
23633
23903
  let cached = null;
23634
23904
  return async () => {
@@ -23657,40 +23927,50 @@ async function startHostedConnectors(input) {
23657
23927
  if (!Array.isArray(connections)) return () => {
23658
23928
  };
23659
23929
  const stops = [];
23930
+ const lookupPush = input.pushDispatch ?? getPushProviderDispatch;
23660
23931
  for (const c of connections) {
23661
- const dispatch = PROVIDER_DISPATCH[c.provider];
23662
- if (!dispatch) {
23663
- onSkip?.(c.provider, "no pull connector for this provider");
23664
- continue;
23665
- }
23666
- if (c.needsProjectSelection || !c.projectRef) {
23667
- onSkip?.(c.provider, "no project selected yet \u2014 not pullable");
23668
- continue;
23669
- }
23670
- const options = hostedOptions(c.provider, c, project);
23671
- if (!options) {
23672
- onSkip?.(c.provider, "no hosted option mapping for this provider");
23932
+ const push = lookupPush(c.provider);
23933
+ if (push) {
23934
+ await provisionHostedPushProvider(c, push, input);
23673
23935
  continue;
23674
23936
  }
23675
- let built;
23676
- try {
23677
- built = dispatch.build(graph, options);
23678
- } catch (err) {
23679
- onSkip?.(c.provider, err.message);
23680
- continue;
23937
+ const credentialSource = createHostedCredentialSource(c.provider, deps);
23938
+ for (const name of HOSTED_CONNECTORS_FOR[c.provider] ?? [c.provider]) {
23939
+ const dispatch = PROVIDER_DISPATCH[name];
23940
+ if (!dispatch) {
23941
+ onSkip?.(name, "no pull connector for this provider");
23942
+ continue;
23943
+ }
23944
+ if (c.needsProjectSelection || !c.projectRef) {
23945
+ onSkip?.(name, "no project selected yet \u2014 not pullable");
23946
+ continue;
23947
+ }
23948
+ const options = hostedOptions(name, c, project, input.firebaseServiceMap);
23949
+ if (!options) {
23950
+ onSkip?.(name, "no hosted option mapping for this provider");
23951
+ continue;
23952
+ }
23953
+ let built;
23954
+ try {
23955
+ built = dispatch.build(graph, options);
23956
+ } catch (err) {
23957
+ onSkip?.(name, err.message);
23958
+ continue;
23959
+ }
23960
+ stops.push(
23961
+ startLoop(
23962
+ built.connector,
23963
+ { projectDir, project, credentials: {}, ...errorsPath ? { errorsPath } : {} },
23964
+ graph,
23965
+ built.resolveTarget,
23966
+ {
23967
+ connectorId: `hosted:${name}`,
23968
+ // The credential is fetched by the CONNECTION's provider (`gcp`), not the connector's name.
23969
+ refreshCredentials: credentialSource
23970
+ }
23971
+ )
23972
+ );
23681
23973
  }
23682
- stops.push(
23683
- startLoop(
23684
- built.connector,
23685
- { projectDir, project, credentials: {}, ...errorsPath ? { errorsPath } : {} },
23686
- graph,
23687
- built.resolveTarget,
23688
- {
23689
- connectorId: `hosted:${c.provider}`,
23690
- refreshCredentials: createHostedCredentialSource(c.provider, deps)
23691
- }
23692
- )
23693
- );
23694
23974
  }
23695
23975
  return () => {
23696
23976
  for (const stop of stops) stop();
@@ -23704,10 +23984,18 @@ async function maybeStartHostedConnectors(input) {
23704
23984
  if (!cpUrl || !projectId || !daemonToken) return () => {
23705
23985
  };
23706
23986
  return startHostedConnectors({
23707
- deps: { cpUrl, projectId, daemonToken, ...input.fetchImpl ? { fetchImpl: input.fetchImpl } : {} },
23987
+ deps: {
23988
+ cpUrl,
23989
+ projectId,
23990
+ daemonToken,
23991
+ ...env.NEAT_PUBLIC_URL ? { publicUrl: env.NEAT_PUBLIC_URL } : {},
23992
+ ...env.NEAT_OTEL_TOKEN ? { otelToken: env.NEAT_OTEL_TOKEN } : {},
23993
+ ...input.fetchImpl ? { fetchImpl: input.fetchImpl } : {}
23994
+ },
23708
23995
  graph: input.graph,
23709
23996
  projectDir: input.projectDir,
23710
23997
  project: input.project,
23998
+ ...parseFirebaseServiceMap(env.NEAT_FIREBASE_SERVICE_MAP) ? { firebaseServiceMap: parseFirebaseServiceMap(env.NEAT_FIREBASE_SERVICE_MAP) } : {},
23711
23999
  ...input.errorsPath ? { errorsPath: input.errorsPath } : {},
23712
24000
  ...input.onSkip ? { onSkip: input.onSkip } : {}
23713
24001
  });
@@ -23716,8 +24004,8 @@ async function maybeStartHostedConnectors(input) {
23716
24004
  // src/connectors/hosted-repos.ts
23717
24005
  init_cjs_shims();
23718
24006
  var import_node_os5 = __toESM(require("os"), 1);
23719
- var import_node_path75 = __toESM(require("path"), 1);
23720
- var import_promises = require("fs/promises");
24007
+ var import_node_path76 = __toESM(require("path"), 1);
24008
+ var import_promises2 = require("fs/promises");
23721
24009
  var CP_REQUEST_TIMEOUT_MS2 = 1e4;
23722
24010
  var DEFAULT_SYNC_INTERVAL_MS = 5 * 6e4;
23723
24011
  var CLONE_TIMEOUT_MS = 5 * 6e4;
@@ -23787,7 +24075,9 @@ async function syncOneRepo(r, input) {
23787
24075
  const tmpRoot = input.tmpRoot ?? import_node_os5.default.tmpdir();
23788
24076
  let dir;
23789
24077
  try {
23790
- dir = await (0, import_promises.mkdtemp)(import_node_path75.default.join(tmpRoot, "neat-repo-"));
24078
+ dir = await (0, import_promises2.mkdtemp)(import_node_path76.default.join(tmpRoot, "neat-repo-"));
24079
+ await cpPostStatus(deps, r.owner, r.name, { syncStatus: "syncing", detail: "cloning" }).catch(() => {
24080
+ });
23791
24081
  await cloneRepo(r.cloneUrl, r.defaultBranch, dir);
23792
24082
  const extracted = await extract(graph, dir);
23793
24083
  const nodes = extracted?.nodesAdded ?? 0;
@@ -23805,7 +24095,7 @@ async function syncOneRepo(r, input) {
23805
24095
  }).catch(() => {
23806
24096
  });
23807
24097
  } finally {
23808
- if (dir) await (0, import_promises.rm)(dir, { recursive: true, force: true }).catch(() => {
24098
+ if (dir) await (0, import_promises2.rm)(dir, { recursive: true, force: true }).catch(() => {
23809
24099
  });
23810
24100
  }
23811
24101
  }
@@ -23815,23 +24105,26 @@ async function runRepoSyncPass(input) {
23815
24105
  repos = await cpGet2(`/internal/projects/${input.deps.projectId}/repos`, input.deps);
23816
24106
  } catch (err) {
23817
24107
  input.onSkip?.("(all)", `control plane repo list unreadable \u2014 ${err.message}`);
23818
- return;
24108
+ return false;
23819
24109
  }
23820
- if (!Array.isArray(repos)) return;
24110
+ if (!Array.isArray(repos)) return false;
23821
24111
  for (const r of repos) {
23822
- if (!needsSync(r)) continue;
24112
+ if (!input.forceResync && !needsSync(r)) continue;
23823
24113
  await syncOneRepo(r, input);
23824
24114
  }
24115
+ return true;
23825
24116
  }
23826
24117
  async function startRepoSync(input) {
23827
24118
  const intervalMs = input.intervalMs ?? DEFAULT_SYNC_INTERVAL_MS;
23828
24119
  let stopped = false;
23829
24120
  let running = false;
24121
+ let bootResyncDone = false;
23830
24122
  const tick = async () => {
23831
24123
  if (stopped || running) return;
23832
24124
  running = true;
23833
24125
  try {
23834
- await runRepoSyncPass(input);
24126
+ const listed = await runRepoSyncPass({ ...input, forceResync: !bootResyncDone });
24127
+ if (listed) bootResyncDone = true;
23835
24128
  } finally {
23836
24129
  running = false;
23837
24130
  }
@@ -24215,7 +24508,7 @@ function mapWorkloadsToSignals(deployments, pods, config) {
24215
24508
 
24216
24509
  // src/connectors/kubernetes/resolve.ts
24217
24510
  init_cjs_shims();
24218
- var import_types103 = require("@neat.is/types");
24511
+ var import_types104 = require("@neat.is/types");
24219
24512
  var NO_ENV3 = "unknown";
24220
24513
  function createK8sResolveTarget(graph) {
24221
24514
  return (signal) => {
@@ -24226,28 +24519,28 @@ function createK8sResolveTarget(graph) {
24226
24519
  return {
24227
24520
  targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV3),
24228
24521
  serviceName,
24229
- edgeType: import_types103.EdgeType.CALLS
24522
+ edgeType: import_types104.EdgeType.CALLS
24230
24523
  };
24231
24524
  };
24232
24525
  }
24233
24526
 
24234
24527
  // src/connectors/kubernetes/substrate.ts
24235
24528
  init_cjs_shims();
24236
- var import_promises2 = require("fs/promises");
24529
+ var import_promises3 = require("fs/promises");
24237
24530
  var import_node_os6 = __toESM(require("os"), 1);
24238
- var import_node_path76 = __toESM(require("path"), 1);
24531
+ var import_node_path77 = __toESM(require("path"), 1);
24239
24532
  function defaultHome() {
24240
24533
  const override = process.env.NEAT_HOME;
24241
- if (override && override.length > 0) return import_node_path76.default.resolve(override);
24242
- return import_node_path76.default.join(import_node_os6.default.homedir(), ".neat");
24534
+ if (override && override.length > 0) return import_node_path77.default.resolve(override);
24535
+ return import_node_path77.default.join(import_node_os6.default.homedir(), ".neat");
24243
24536
  }
24244
24537
  function k8sSubstrateConfigPath(home = defaultHome()) {
24245
- return import_node_path76.default.join(home, "k8s.json");
24538
+ return import_node_path77.default.join(home, "k8s.json");
24246
24539
  }
24247
24540
  async function readK8sSubstrateConfig(home = defaultHome()) {
24248
24541
  let raw;
24249
24542
  try {
24250
- raw = await (0, import_promises2.readFile)(k8sSubstrateConfigPath(home), "utf8");
24543
+ raw = await (0, import_promises3.readFile)(k8sSubstrateConfigPath(home), "utf8");
24251
24544
  } catch {
24252
24545
  return { version: 1, deployments: [] };
24253
24546
  }
@@ -24342,7 +24635,7 @@ init_auth();
24342
24635
  // src/unrouted.ts
24343
24636
  init_cjs_shims();
24344
24637
  var import_node_fs40 = require("fs");
24345
- var import_node_path77 = __toESM(require("path"), 1);
24638
+ var import_node_path78 = __toESM(require("path"), 1);
24346
24639
  function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
24347
24640
  return {
24348
24641
  timestamp: now.toISOString(),
@@ -24352,34 +24645,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
24352
24645
  };
24353
24646
  }
24354
24647
  async function appendUnroutedSpan(neatHome3, record) {
24355
- const target = import_node_path77.default.join(neatHome3, "errors.ndjson");
24648
+ const target = import_node_path78.default.join(neatHome3, "errors.ndjson");
24356
24649
  await import_node_fs40.promises.mkdir(neatHome3, { recursive: true });
24357
24650
  await import_node_fs40.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
24358
24651
  }
24359
24652
  function unroutedErrorsPath(neatHome3) {
24360
- return import_node_path77.default.join(neatHome3, "errors.ndjson");
24653
+ return import_node_path78.default.join(neatHome3, "errors.ndjson");
24361
24654
  }
24362
24655
 
24363
24656
  // src/daemon.ts
24364
- var import_types106 = require("@neat.is/types");
24657
+ var import_types107 = require("@neat.is/types");
24365
24658
  function daemonJsonPath(scanPath) {
24366
- return import_node_path78.default.join(scanPath, "neat-out", "daemon.json");
24659
+ return import_node_path79.default.join(scanPath, "neat-out", "daemon.json");
24367
24660
  }
24368
24661
  function daemonsDiscoveryDir(home) {
24369
24662
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
24370
- return import_node_path78.default.join(base, "daemons");
24663
+ return import_node_path79.default.join(base, "daemons");
24371
24664
  }
24372
24665
  function daemonDiscoveryPath(project, home) {
24373
- return import_node_path78.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
24666
+ return import_node_path79.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
24374
24667
  }
24375
24668
  function sanitizeDiscoveryName(project) {
24376
24669
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
24377
24670
  }
24378
24671
  function neatHomeFromEnv() {
24379
24672
  const env = process.env.NEAT_HOME;
24380
- if (env && env.length > 0) return import_node_path78.default.resolve(env);
24673
+ if (env && env.length > 0) return import_node_path79.default.resolve(env);
24381
24674
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
24382
- return import_node_path78.default.join(home, ".neat");
24675
+ return import_node_path79.default.join(home, ".neat");
24383
24676
  }
24384
24677
  function resolveNeatVersion() {
24385
24678
  if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
@@ -24446,11 +24739,11 @@ function teardownSlot(slot) {
24446
24739
  }
24447
24740
  }
24448
24741
  function neatHomeFor(opts) {
24449
- if (opts.neatHome && opts.neatHome.length > 0) return import_node_path78.default.resolve(opts.neatHome);
24742
+ if (opts.neatHome && opts.neatHome.length > 0) return import_node_path79.default.resolve(opts.neatHome);
24450
24743
  const env = process.env.NEAT_HOME;
24451
- if (env && env.length > 0) return import_node_path78.default.resolve(env);
24744
+ if (env && env.length > 0) return import_node_path79.default.resolve(env);
24452
24745
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
24453
- return import_node_path78.default.join(home, ".neat");
24746
+ return import_node_path79.default.join(home, ".neat");
24454
24747
  }
24455
24748
  function routeSpanToProject(serviceName, projects) {
24456
24749
  if (!serviceName) return DEFAULT_PROJECT;
@@ -24498,11 +24791,11 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
24498
24791
  if (!serviceName) return true;
24499
24792
  if (serviceNameMatchesProject(serviceName, project)) return true;
24500
24793
  return graph.someNode(
24501
- (_id, attrs) => attrs.type === import_types106.NodeType.ServiceNode && attrs.name === serviceName
24794
+ (_id, attrs) => attrs.type === import_types107.NodeType.ServiceNode && attrs.name === serviceName
24502
24795
  );
24503
24796
  }
24504
24797
  async function bootstrapProject(entry, connectors = [], neatHome3) {
24505
- const paths = pathsForProject(entry.name, import_node_path78.default.join(entry.path, "neat-out"));
24798
+ const paths = pathsForProject(entry.name, import_node_path79.default.join(entry.path, "neat-out"));
24506
24799
  try {
24507
24800
  const stat = await import_node_fs41.promises.stat(entry.path);
24508
24801
  if (!stat.isDirectory()) {
@@ -24656,7 +24949,7 @@ async function startDaemon(opts = {}) {
24656
24949
  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;
24657
24950
  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;
24658
24951
  const singleProject = projectArg;
24659
- const singleProjectPath = singleProject && projectPathArg ? import_node_path78.default.resolve(projectPathArg) : null;
24952
+ const singleProjectPath = singleProject && projectPathArg ? import_node_path79.default.resolve(projectPathArg) : null;
24660
24953
  if (singleProject && !singleProjectPath) {
24661
24954
  throw new Error(
24662
24955
  `neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
@@ -24671,7 +24964,7 @@ async function startDaemon(opts = {}) {
24671
24964
  );
24672
24965
  }
24673
24966
  }
24674
- const pidPath = import_node_path78.default.join(home, "neatd.pid");
24967
+ const pidPath = import_node_path79.default.join(home, "neatd.pid");
24675
24968
  await writeAtomically(pidPath, `${process.pid}
24676
24969
  `);
24677
24970
  const slots = /* @__PURE__ */ new Map();
@@ -25143,8 +25436,8 @@ async function startDaemon(opts = {}) {
25143
25436
  let registryWatcher = null;
25144
25437
  let reloadTimer = null;
25145
25438
  if (!singleProject) try {
25146
- const regDir = import_node_path78.default.dirname(regPath);
25147
- const regBase = import_node_path78.default.basename(regPath);
25439
+ const regDir = import_node_path79.default.dirname(regPath);
25440
+ const regBase = import_node_path79.default.basename(regPath);
25148
25441
  registryWatcher = (0, import_node_fs41.watch)(regDir, (_eventType, filename) => {
25149
25442
  if (filename !== null && filename !== regBase) return;
25150
25443
  if (reloadTimer) clearTimeout(reloadTimer);