@neat.is/core 0.9.2-dev.20260821 → 0.9.2

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
@@ -1392,7 +1392,7 @@ function databaseRootCauseShape(graph, origin, walk10) {
1392
1392
  for (const id of walk10.path) {
1393
1393
  const owner = resolveOwningService(graph, id);
1394
1394
  if (!owner) continue;
1395
- const { id: serviceId15, svc } = owner;
1395
+ const { id: serviceId16, svc } = owner;
1396
1396
  const deps = svc.dependencies ?? {};
1397
1397
  for (const pair of candidatePairs) {
1398
1398
  const declared = deps[pair.driver];
@@ -1405,7 +1405,7 @@ function databaseRootCauseShape(graph, origin, walk10) {
1405
1405
  );
1406
1406
  if (!result.compatible) {
1407
1407
  return {
1408
- rootCauseNode: serviceId15,
1408
+ rootCauseNode: serviceId16,
1409
1409
  rootCauseReason: result.reason ?? "incompatible driver",
1410
1410
  ...result.minDriverVersion ? {
1411
1411
  fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
@@ -1420,7 +1420,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
1420
1420
  for (const id of walk10.path) {
1421
1421
  const owner = resolveOwningService(graph, id);
1422
1422
  if (!owner) continue;
1423
- const { id: serviceId15, svc } = owner;
1423
+ const { id: serviceId16, svc } = owner;
1424
1424
  const deps = svc.dependencies ?? {};
1425
1425
  const serviceNodeEngine = svc.nodeEngine;
1426
1426
  for (const constraint of nodeEngineConstraints()) {
@@ -1429,7 +1429,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
1429
1429
  const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
1430
1430
  if (!result.compatible && result.reason) {
1431
1431
  return {
1432
- rootCauseNode: serviceId15,
1432
+ rootCauseNode: serviceId16,
1433
1433
  rootCauseReason: result.reason,
1434
1434
  ...result.requiredNodeVersion ? {
1435
1435
  fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
@@ -1444,7 +1444,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
1444
1444
  const result = checkPackageConflict(conflict, declared, requiredDeclared);
1445
1445
  if (!result.compatible && result.reason) {
1446
1446
  return {
1447
- rootCauseNode: serviceId15,
1447
+ rootCauseNode: serviceId16,
1448
1448
  rootCauseReason: result.reason,
1449
1449
  fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
1450
1450
  };
@@ -1549,9 +1549,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
1549
1549
  function isFailingCallEdge(e) {
1550
1550
  return e.type === import_types.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
1551
1551
  }
1552
- function callSourcesForService(graph, serviceId15) {
1553
- const ids = [serviceId15];
1554
- for (const edgeId of graph.outboundEdges(serviceId15)) {
1552
+ function callSourcesForService(graph, serviceId16) {
1553
+ const ids = [serviceId16];
1554
+ for (const edgeId of graph.outboundEdges(serviceId16)) {
1555
1555
  const e = graph.getEdgeAttributes(edgeId);
1556
1556
  if (e.type !== import_types.EdgeType.CONTAINS) continue;
1557
1557
  const tgt = graph.getNodeAttributes(e.target);
@@ -1575,9 +1575,9 @@ function failingCallDominates(e, id, curEdge, curId) {
1575
1575
  }
1576
1576
  return id < curId;
1577
1577
  }
1578
- function dominantFailingCall(graph, serviceId15, visited) {
1578
+ function dominantFailingCall(graph, serviceId16, visited) {
1579
1579
  let best = null;
1580
- for (const src of callSourcesForService(graph, serviceId15)) {
1580
+ for (const src of callSourcesForService(graph, serviceId16)) {
1581
1581
  for (const edgeId of graph.outboundEdges(src)) {
1582
1582
  const e = graph.getEdgeAttributes(edgeId);
1583
1583
  if (!isFailingCallEdge(e)) continue;
@@ -1616,9 +1616,9 @@ function staleCallDominates(e, id, curEdge, curId) {
1616
1616
  if (ev !== cv) return ev > cv;
1617
1617
  return id < curId;
1618
1618
  }
1619
- function dominantStaleCall(graph, serviceId15, visited) {
1619
+ function dominantStaleCall(graph, serviceId16, visited) {
1620
1620
  const bestByCallee = /* @__PURE__ */ new Map();
1621
- for (const src of callSourcesForService(graph, serviceId15)) {
1621
+ for (const src of callSourcesForService(graph, serviceId16)) {
1622
1622
  for (const edgeId of graph.outboundEdges(src)) {
1623
1623
  const e = graph.getEdgeAttributes(edgeId);
1624
1624
  if (e.type !== import_types.EdgeType.CALLS) continue;
@@ -4817,11 +4817,11 @@ function actixAttributeRoute(attrItem, out) {
4817
4817
  if (!nameNode) return;
4818
4818
  const macro = nameNode.type === "identifier" ? nameNode.text : nameNode.type === "scoped_identifier" ? nameNode.childForFieldName("name")?.text ?? null : null;
4819
4819
  if (!macro) return;
4820
- const tokens = attr.childForFieldName("arguments");
4821
- if (!tokens || tokens.type !== "token_tree") return;
4820
+ const tokens2 = attr.childForFieldName("arguments");
4821
+ if (!tokens2 || tokens2.type !== "token_tree") return;
4822
4822
  const strings = [];
4823
- for (let i = 0; i < tokens.namedChildCount; i++) {
4824
- const s = rustStringContent(tokens.namedChild(i));
4823
+ for (let i = 0; i < tokens2.namedChildCount; i++) {
4824
+ const s = rustStringContent(tokens2.namedChild(i));
4825
4825
  if (s !== null) strings.push(s);
4826
4826
  }
4827
4827
  const pathStr = strings[0];
@@ -6715,29 +6715,29 @@ function promoteFrontierNodes(graph, opts = {}) {
6715
6715
  toPromote.push({ frontierId: id, serviceId: target });
6716
6716
  });
6717
6717
  let promoted = 0;
6718
- for (const { frontierId: frontierId2, serviceId: serviceId15 } of toPromote) {
6718
+ for (const { frontierId: frontierId2, serviceId: serviceId16 } of toPromote) {
6719
6719
  if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
6720
6720
  const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
6721
6721
  if (!gate.allowed) {
6722
6722
  continue;
6723
6723
  }
6724
6724
  }
6725
- rewireFrontierEdges(graph, frontierId2, serviceId15);
6725
+ rewireFrontierEdges(graph, frontierId2, serviceId16);
6726
6726
  graph.dropNode(frontierId2);
6727
6727
  promoted++;
6728
6728
  }
6729
6729
  return promoted;
6730
6730
  }
6731
- function rewireFrontierEdges(graph, frontierId2, serviceId15) {
6731
+ function rewireFrontierEdges(graph, frontierId2, serviceId16) {
6732
6732
  const inbound = [...graph.inboundEdges(frontierId2)];
6733
6733
  const outbound = [...graph.outboundEdges(frontierId2)];
6734
6734
  for (const edgeId of inbound) {
6735
6735
  const edge = graph.getEdgeAttributes(edgeId);
6736
- rebuildEdge(graph, edge, edge.source, serviceId15, edgeId);
6736
+ rebuildEdge(graph, edge, edge.source, serviceId16, edgeId);
6737
6737
  }
6738
6738
  for (const edgeId of outbound) {
6739
6739
  const edge = graph.getEdgeAttributes(edgeId);
6740
- rebuildEdge(graph, edge, serviceId15, edge.target, edgeId);
6740
+ rebuildEdge(graph, edge, serviceId16, edge.target, edgeId);
6741
6741
  }
6742
6742
  }
6743
6743
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
@@ -7852,9 +7852,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
7852
7852
  "StatefulSet",
7853
7853
  "DaemonSet"
7854
7854
  ]);
7855
- function addAliases(graph, serviceId15, candidates) {
7856
- if (!graph.hasNode(serviceId15)) return;
7857
- const node = graph.getNodeAttributes(serviceId15);
7855
+ function addAliases(graph, serviceId16, candidates) {
7856
+ if (!graph.hasNode(serviceId16)) return;
7857
+ const node = graph.getNodeAttributes(serviceId16);
7858
7858
  if (node.type !== import_types19.NodeType.ServiceNode) return;
7859
7859
  const set = new Set(node.aliases ?? []);
7860
7860
  for (const c of candidates) {
@@ -7864,7 +7864,7 @@ function addAliases(graph, serviceId15, candidates) {
7864
7864
  }
7865
7865
  if (set.size === 0) return;
7866
7866
  const updated = { ...node, aliases: [...set].sort() };
7867
- graph.replaceNodeAttributes(serviceId15, updated);
7867
+ graph.replaceNodeAttributes(serviceId16, updated);
7868
7868
  }
7869
7869
  function indexServicesByName(services) {
7870
7870
  const map = /* @__PURE__ */ new Map();
@@ -7897,12 +7897,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
7897
7897
  }
7898
7898
  if (!compose?.services) return;
7899
7899
  for (const [composeName, svc] of Object.entries(compose.services)) {
7900
- const serviceId15 = serviceIndex.get(composeName);
7901
- if (!serviceId15) continue;
7900
+ const serviceId16 = serviceIndex.get(composeName);
7901
+ if (!serviceId16) continue;
7902
7902
  const aliases = /* @__PURE__ */ new Set([composeName]);
7903
7903
  if (svc.container_name) aliases.add(svc.container_name);
7904
7904
  if (svc.hostname) aliases.add(svc.hostname);
7905
- addAliases(graph, serviceId15, aliases);
7905
+ addAliases(graph, serviceId16, aliases);
7906
7906
  }
7907
7907
  }
7908
7908
  var LABEL_KEYS = /* @__PURE__ */ new Set([
@@ -15839,7 +15839,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
15839
15839
  init_cjs_shims();
15840
15840
  var import_fastify2 = __toESM(require("fastify"), 1);
15841
15841
  var import_cors = __toESM(require("@fastify/cors"), 1);
15842
- var import_types92 = require("@neat.is/types");
15842
+ var import_types93 = require("@neat.is/types");
15843
15843
 
15844
15844
  // src/extend/index.ts
15845
15845
  init_cjs_shims();
@@ -16562,6 +16562,639 @@ function queryLogEntries(opts) {
16562
16562
  return merged;
16563
16563
  }
16564
16564
 
16565
+ // src/ask.ts
16566
+ init_cjs_shims();
16567
+ var import_types59 = require("@neat.is/types");
16568
+ var DEFAULT_MAX_NODES = 3;
16569
+ var MAX_FACTS_PER_SECTION = 6;
16570
+ var INTENT_RULES = [
16571
+ {
16572
+ intent: "root-cause",
16573
+ test: /\b(why|root[\s-]?cause|failing|fail(?:s|ed)?|breaking|broke(?:n)?|crash(?:ing|ed)?|throw(?:ing|s)?|culprit|5\d\d)\b/
16574
+ },
16575
+ {
16576
+ intent: "blast-radius",
16577
+ 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/
16578
+ },
16579
+ {
16580
+ intent: "divergence",
16581
+ test: /\b(diverg\w*|weird|mismatch|drift|out\s+of\s+sync|inconsistent|disagree\w*|declared\s+vs|anything\s+wrong)\b/
16582
+ },
16583
+ {
16584
+ intent: "incidents",
16585
+ test: /\b(incidents?|recent\s+(?:errors?|failures?)|error\s+history|failure\s+history)\b/
16586
+ },
16587
+ {
16588
+ intent: "observed",
16589
+ test: /\b(at\s+runtime|in\s+prod(?:uction)?|actually\s+call\w*|really\s+call\w*|observed|runtime\s+traffic)\b/
16590
+ },
16591
+ {
16592
+ intent: "dependencies",
16593
+ test: /\b(depend\w*|calls?|uses?|imports?|relies\s+on|needs?)\b/
16594
+ }
16595
+ ];
16596
+ function classifyIntent(question) {
16597
+ const q = question.toLowerCase();
16598
+ for (const rule of INTENT_RULES) {
16599
+ if (rule.test.test(q)) return rule.intent;
16600
+ }
16601
+ return "overview";
16602
+ }
16603
+ var STOPWORDS = /* @__PURE__ */ new Set([
16604
+ "the",
16605
+ "a",
16606
+ "an",
16607
+ "is",
16608
+ "are",
16609
+ "was",
16610
+ "were",
16611
+ "be",
16612
+ "been",
16613
+ "being",
16614
+ "of",
16615
+ "to",
16616
+ "in",
16617
+ "on",
16618
+ "at",
16619
+ "by",
16620
+ "for",
16621
+ "with",
16622
+ "and",
16623
+ "or",
16624
+ "but",
16625
+ "if",
16626
+ "as",
16627
+ "this",
16628
+ "that",
16629
+ "these",
16630
+ "those",
16631
+ "it",
16632
+ "its",
16633
+ "do",
16634
+ "does",
16635
+ "did",
16636
+ "has",
16637
+ "have",
16638
+ "had",
16639
+ "what",
16640
+ "which",
16641
+ "who",
16642
+ "whom",
16643
+ "how",
16644
+ "when",
16645
+ "where",
16646
+ "my",
16647
+ "me",
16648
+ "i",
16649
+ "we",
16650
+ "you",
16651
+ "they",
16652
+ "them",
16653
+ "from",
16654
+ "into",
16655
+ "about",
16656
+ "would",
16657
+ "will",
16658
+ "can",
16659
+ "could",
16660
+ "should",
16661
+ "anything",
16662
+ "everything",
16663
+ "something",
16664
+ "all",
16665
+ "any",
16666
+ "some",
16667
+ "no",
16668
+ "not",
16669
+ "so",
16670
+ "up",
16671
+ "out",
16672
+ "over",
16673
+ "get",
16674
+ "got",
16675
+ "show",
16676
+ "tell",
16677
+ "explain",
16678
+ "find",
16679
+ "give",
16680
+ "happen",
16681
+ "happens",
16682
+ "happening",
16683
+ "here",
16684
+ "there",
16685
+ "system",
16686
+ "code",
16687
+ "service",
16688
+ "node",
16689
+ "graph"
16690
+ ]);
16691
+ var INTENT_WORDS = /* @__PURE__ */ new Set([
16692
+ "why",
16693
+ "root",
16694
+ "cause",
16695
+ "failing",
16696
+ "fail",
16697
+ "fails",
16698
+ "failed",
16699
+ "breaking",
16700
+ "broke",
16701
+ "broken",
16702
+ "crash",
16703
+ "crashing",
16704
+ "crashed",
16705
+ "throw",
16706
+ "throwing",
16707
+ "throws",
16708
+ "culprit",
16709
+ "blast",
16710
+ "radius",
16711
+ "break",
16712
+ "breaks",
16713
+ "impact",
16714
+ "downstream",
16715
+ "dependent",
16716
+ "dependents",
16717
+ "redeploy",
16718
+ "affect",
16719
+ "affects",
16720
+ "affected",
16721
+ "diverge",
16722
+ "divergence",
16723
+ "divergences",
16724
+ "weird",
16725
+ "mismatch",
16726
+ "drift",
16727
+ "inconsistent",
16728
+ "disagree",
16729
+ "declared",
16730
+ "observed",
16731
+ "incident",
16732
+ "incidents",
16733
+ "recent",
16734
+ "errors",
16735
+ "error",
16736
+ "failures",
16737
+ "failure",
16738
+ "history",
16739
+ "runtime",
16740
+ "production",
16741
+ "prod",
16742
+ "traffic",
16743
+ "actually",
16744
+ "really",
16745
+ "depend",
16746
+ "depends",
16747
+ "dependency",
16748
+ "dependencies",
16749
+ "call",
16750
+ "calls",
16751
+ "use",
16752
+ "uses",
16753
+ "using",
16754
+ "import",
16755
+ "imports",
16756
+ "relies",
16757
+ "rely",
16758
+ "needs",
16759
+ "need",
16760
+ "sync"
16761
+ ]);
16762
+ function tokens(input) {
16763
+ const spaced = input.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
16764
+ return (spaced.toLowerCase().match(/[a-z0-9]+/g) ?? []).filter((t) => t.length >= 2);
16765
+ }
16766
+ function queryTokens(question) {
16767
+ const seen = /* @__PURE__ */ new Set();
16768
+ const out = [];
16769
+ for (const t of tokens(question)) {
16770
+ if (STOPWORDS.has(t) || INTENT_WORDS.has(t)) continue;
16771
+ if (seen.has(t)) continue;
16772
+ seen.add(t);
16773
+ out.push(t);
16774
+ }
16775
+ return out;
16776
+ }
16777
+ function nodeName(node) {
16778
+ return node.name ?? node.id;
16779
+ }
16780
+ function idBody(id) {
16781
+ const colon = id.indexOf(":");
16782
+ return colon >= 0 ? id.slice(colon + 1) : id;
16783
+ }
16784
+ var EMBED_MIN_SCORE = 0.35;
16785
+ async function resolveEntities(graph, question, searchIndex, maxNodes) {
16786
+ const qTokens = queryTokens(question);
16787
+ const normalized = question.toLowerCase();
16788
+ const best = /* @__PURE__ */ new Map();
16789
+ const consider = (cand) => {
16790
+ const cur = best.get(cand.nodeId);
16791
+ if (!cur || cand.score > cur.score) best.set(cand.nodeId, cand);
16792
+ };
16793
+ graph.forEachNode((id, attrs) => {
16794
+ const node = attrs;
16795
+ if (node.type === import_types59.NodeType.FrontierNode) return;
16796
+ const name = nodeName(node);
16797
+ const body = idBody(id);
16798
+ const labelTokens = /* @__PURE__ */ new Set([...tokens(body), ...tokens(name)]);
16799
+ if (labelTokens.size === 0) return;
16800
+ let matched = 0;
16801
+ for (const t of qTokens) if (labelTokens.has(t)) matched += 1;
16802
+ const named = body.length >= 2 && normalized.includes(body.toLowerCase()) || name.length >= 2 && normalized.includes(name.toLowerCase());
16803
+ if (matched === 0 && !named) return;
16804
+ const coverage = qTokens.length > 0 ? matched / qTokens.length : 0;
16805
+ const via = named ? "id" : matched > 0 && tokens(name).some((t) => qTokens.includes(t)) ? "label" : "token";
16806
+ const score = named ? Math.max(0.9, coverage) : Math.min(0.85, 0.3 + 0.7 * coverage);
16807
+ consider({ nodeId: id, label: name, via, score: Math.min(1, score) });
16808
+ });
16809
+ if (searchIndex) {
16810
+ try {
16811
+ const res = await searchIndex.search(question, 10);
16812
+ if (res.provider !== "substring") {
16813
+ for (const m of res.matches) {
16814
+ if (m.node.type === import_types59.NodeType.FrontierNode) continue;
16815
+ const already = best.get(m.node.id);
16816
+ if (!already && m.score < EMBED_MIN_SCORE) continue;
16817
+ consider({
16818
+ nodeId: m.node.id,
16819
+ label: nodeName(m.node),
16820
+ via: "embedding",
16821
+ score: Math.max(0, Math.min(1, m.score))
16822
+ });
16823
+ }
16824
+ }
16825
+ } catch {
16826
+ }
16827
+ }
16828
+ const viaRank = { id: 3, label: 2, token: 1, embedding: 0 };
16829
+ return [...best.values()].sort(
16830
+ (a, b) => b.score - a.score || viaRank[b.via] - viaRank[a.via] || a.nodeId.localeCompare(b.nodeId)
16831
+ ).slice(0, maxNodes).map((s) => ({ nodeId: s.nodeId, label: s.label, via: s.via, score: Number(s.score.toFixed(3)) }));
16832
+ }
16833
+ function incidentsForNode(nodeId, incidents) {
16834
+ if (!incidents || incidents.length === 0) return [];
16835
+ const svc = nodeId.replace(/^service:/, "");
16836
+ return incidents.filter((e) => e.affectedNode === nodeId || e.service === svc);
16837
+ }
16838
+ function edgeSignalNote(e) {
16839
+ const bits = [];
16840
+ if (e.signal) {
16841
+ bits.push(`${e.signal.spanCount} spans`);
16842
+ if (e.signal.errorCount > 0) bits.push(`${e.signal.errorCount} errors`);
16843
+ if (e.signal.latencyMs?.p95 !== void 0) bits.push(`p95 ${Math.round(e.signal.latencyMs.p95)}ms`);
16844
+ } else if (e.callCount !== void 0) {
16845
+ bits.push(`${e.callCount} calls`);
16846
+ }
16847
+ return bits.length ? ` (${bits.join(", ")})` : "";
16848
+ }
16849
+ function buildRootCauseSection(graph, node, incidents, now) {
16850
+ const result = getRootCause(graph, node, void 0, incidents, { now });
16851
+ if (!result) return null;
16852
+ const lastProv = result.edgeProvenances[result.edgeProvenances.length - 1] ?? import_types59.Provenance.OBSERVED;
16853
+ const facts = [
16854
+ {
16855
+ text: `Root cause: ${result.rootCauseNode} \u2014 ${result.rootCauseReason}`,
16856
+ provenance: lastProv,
16857
+ confidence: result.confidence
16858
+ }
16859
+ ];
16860
+ const candidates = result.candidates ?? [];
16861
+ for (const c of candidates.slice(0, 3)) {
16862
+ facts.push({
16863
+ text: `Candidate ${c.node} \u2014 ${c.classification}: ${c.reason}`,
16864
+ ...c.provenance ? { provenance: c.provenance } : {},
16865
+ confidence: c.confidence
16866
+ });
16867
+ }
16868
+ if (result.traversalPath.length > 1) {
16869
+ facts.push({ text: `Traversal: ${result.traversalPath.join(" \u2190 ")}` });
16870
+ }
16871
+ if (result.fixRecommendation) {
16872
+ facts.push({ text: `Recommended fix: ${result.fixRecommendation}` });
16873
+ }
16874
+ return { heading: "Root cause (navigation)", facts: facts.slice(0, MAX_FACTS_PER_SECTION) };
16875
+ }
16876
+ function buildDependenciesSection(graph, node) {
16877
+ const result = getTransitiveDependencies(graph, node, 2);
16878
+ if (result.total === 0) return null;
16879
+ const facts = result.dependencies.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
16880
+ text: `${d.nodeId} \u2014 ${d.edgeType} (distance ${d.distance})`,
16881
+ // Per-fact provenance: the transitive walk mixes EXTRACTED and OBSERVED
16882
+ // edges, so the tag is on the fact, not asserted for the whole section.
16883
+ provenance: d.provenance
16884
+ }));
16885
+ return { heading: "Dependencies", facts };
16886
+ }
16887
+ function buildObservedSection(graph, node) {
16888
+ const result = getObservedDependencies(graph, node);
16889
+ const facts = [];
16890
+ for (const e of result.dependencies.slice(0, MAX_FACTS_PER_SECTION)) {
16891
+ const via = e.source !== node ? ` via ${e.source}` : "";
16892
+ facts.push({
16893
+ text: `calls ${e.target}${via}${edgeSignalNote(e)}`,
16894
+ provenance: e.provenance,
16895
+ confidence: confidenceForEdge(e)
16896
+ });
16897
+ }
16898
+ if (facts.length === 0) {
16899
+ if (result.observed && result.inboundObservedCount > 0) {
16900
+ facts.push({
16901
+ text: `no outbound runtime calls, but OTel observed ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 a pure receiver`,
16902
+ provenance: import_types59.Provenance.OBSERVED
16903
+ });
16904
+ } else {
16905
+ return null;
16906
+ }
16907
+ }
16908
+ return { heading: "Runtime dependencies (OBSERVED)", facts };
16909
+ }
16910
+ function buildBlastSection(graph, node) {
16911
+ const result = getBlastRadius(graph, node);
16912
+ if (result.totalAffected === 0) return null;
16913
+ const facts = result.affectedNodes.slice(0, MAX_FACTS_PER_SECTION).map((n) => ({
16914
+ text: `${n.nodeId} (distance ${n.distance})`,
16915
+ provenance: n.edgeProvenance,
16916
+ confidence: n.confidence
16917
+ }));
16918
+ return {
16919
+ heading: `Blast radius \u2014 ${result.totalAffected} dependent${result.totalAffected === 1 ? "" : "s"}`,
16920
+ facts
16921
+ };
16922
+ }
16923
+ function buildIncidentsSection(node, incidents) {
16924
+ const relevant = incidentsForNode(node, incidents);
16925
+ if (relevant.length === 0) return null;
16926
+ const ordered = [...relevant].sort((a, b) => b.timestamp.localeCompare(a.timestamp)).slice(0, 4);
16927
+ const facts = ordered.map((ev) => ({
16928
+ text: `${ev.timestamp} \u2014 ${ev.service}: ${ev.errorMessage}`,
16929
+ // ErrorEvents are observation records — OBSERVED by definition.
16930
+ provenance: import_types59.Provenance.OBSERVED
16931
+ }));
16932
+ return { heading: `Recent incidents (OBSERVED) \u2014 ${relevant.length} recorded`, facts };
16933
+ }
16934
+ function divergenceLine(d) {
16935
+ if ((d.type === "missing-observed" || d.type === "missing-extracted") && d.column) {
16936
+ return `[${d.type}] ${d.table ?? d.source} column ${d.column} \u2014 ${d.reason}`;
16937
+ }
16938
+ return `[${d.type}] ${d.source} \u2192 ${d.target} \u2014 ${d.reason}`;
16939
+ }
16940
+ function buildDivergenceSection(graph, node) {
16941
+ const result = computeDivergences(graph, { node });
16942
+ if (result.totalAffected === 0) return null;
16943
+ const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
16944
+ text: divergenceLine(d),
16945
+ confidence: d.confidence
16946
+ // A divergence is composite by construction (EXTRACTED vs OBSERVED), so it
16947
+ // carries no single provenance — the fact leaves it unset.
16948
+ }));
16949
+ return {
16950
+ heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
16951
+ facts
16952
+ };
16953
+ }
16954
+ function buildGlobalDivergenceSection(graph) {
16955
+ const result = computeDivergences(graph);
16956
+ if (result.totalAffected === 0) {
16957
+ return {
16958
+ heading: "Divergences (EXTRACTED vs OBSERVED)",
16959
+ facts: [{ text: "None \u2014 declared code and observed runtime agree across the graph." }]
16960
+ };
16961
+ }
16962
+ const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
16963
+ text: divergenceLine(d),
16964
+ confidence: d.confidence
16965
+ // Composite by construction (EXTRACTED vs OBSERVED), so no single provenance.
16966
+ }));
16967
+ return {
16968
+ heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
16969
+ facts
16970
+ };
16971
+ }
16972
+ function buildGlobalIncidentsSection(incidents) {
16973
+ if (!incidents || incidents.length === 0) {
16974
+ return {
16975
+ heading: "Incidents across the system",
16976
+ facts: [{ text: "None recorded \u2014 the OBSERVED incident store is empty." }]
16977
+ };
16978
+ }
16979
+ const byKey = /* @__PURE__ */ new Map();
16980
+ for (const ev of incidents) {
16981
+ const key = ev.affectedNode || (0, import_types59.serviceId)(ev.service);
16982
+ const cur = byKey.get(key);
16983
+ if (!cur) {
16984
+ byKey.set(key, { key, count: 1, latest: ev.timestamp, sampleMsg: ev.errorMessage });
16985
+ } else {
16986
+ cur.count += 1;
16987
+ if (ev.timestamp > cur.latest) {
16988
+ cur.latest = ev.timestamp;
16989
+ cur.sampleMsg = ev.errorMessage;
16990
+ }
16991
+ }
16992
+ }
16993
+ const rows = [...byKey.values()].sort((a, b) => b.count - a.count || b.latest.localeCompare(a.latest) || a.key.localeCompare(b.key)).slice(0, MAX_FACTS_PER_SECTION);
16994
+ const facts = rows.map((r) => ({
16995
+ text: `${r.key} \u2014 ${r.count} incident${r.count === 1 ? "" : "s"}, latest ${r.latest}: ${r.sampleMsg}`,
16996
+ provenance: import_types59.Provenance.OBSERVED
16997
+ }));
16998
+ return {
16999
+ heading: `Incidents across the system \u2014 ${incidents.length} recorded`,
17000
+ facts
17001
+ };
17002
+ }
17003
+ function buildOverviewSections(graph, incidents) {
17004
+ const nodeByType = /* @__PURE__ */ new Map();
17005
+ const services = [];
17006
+ graph.forEachNode((_id, attrs) => {
17007
+ const node = attrs;
17008
+ nodeByType.set(node.type, (nodeByType.get(node.type) ?? 0) + 1);
17009
+ if (node.type === import_types59.NodeType.ServiceNode) services.push(node.id);
17010
+ });
17011
+ const edgeByProv = /* @__PURE__ */ new Map();
17012
+ graph.forEachEdge((_id, attrs) => {
17013
+ const p = attrs.provenance;
17014
+ edgeByProv.set(p, (edgeByProv.get(p) ?? 0) + 1);
17015
+ });
17016
+ const sections = [];
17017
+ const count = (t) => nodeByType.get(t) ?? 0;
17018
+ const shapeFacts = [
17019
+ { text: `${graph.order} nodes, ${graph.size} edges` },
17020
+ {
17021
+ text: `${count(import_types59.NodeType.ServiceNode)} services, ${count(import_types59.NodeType.FileNode)} files, ${count(import_types59.NodeType.SymbolNode)} symbols, ${count(import_types59.NodeType.DatabaseNode)} databases`
17022
+ }
17023
+ ];
17024
+ for (const p of [import_types59.Provenance.EXTRACTED, import_types59.Provenance.OBSERVED, import_types59.Provenance.INFERRED, import_types59.Provenance.STALE]) {
17025
+ const n = edgeByProv.get(p) ?? 0;
17026
+ if (n > 0) shapeFacts.push({ text: `${n} ${p} edge${n === 1 ? "" : "s"}`, provenance: p });
17027
+ }
17028
+ sections.push({ heading: "System shape", facts: shapeFacts });
17029
+ const depCounts = services.map((s) => ({ s, n: getTransitiveDependencies(graph, s, 1).total })).filter((r) => r.n > 0).sort((a, b) => b.n - a.n || a.s.localeCompare(b.s)).slice(0, MAX_FACTS_PER_SECTION);
17030
+ if (depCounts.length > 0) {
17031
+ sections.push({
17032
+ heading: "Busiest services (by direct dependencies)",
17033
+ facts: depCounts.map((r) => ({
17034
+ text: `${r.s} \u2014 ${r.n} direct dependenc${r.n === 1 ? "y" : "ies"}`
17035
+ }))
17036
+ });
17037
+ }
17038
+ if (incidents && incidents.length > 0) {
17039
+ const incCount = /* @__PURE__ */ new Map();
17040
+ for (const ev of incidents) {
17041
+ const key = ev.affectedNode || (0, import_types59.serviceId)(ev.service);
17042
+ incCount.set(key, (incCount.get(key) ?? 0) + 1);
17043
+ }
17044
+ const top = [...incCount.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, MAX_FACTS_PER_SECTION);
17045
+ sections.push({
17046
+ heading: `Nodes with incidents \u2014 ${incidents.length} recorded`,
17047
+ facts: top.map(([k, n]) => ({
17048
+ text: `${k} \u2014 ${n} incident${n === 1 ? "" : "s"}`,
17049
+ provenance: import_types59.Provenance.OBSERVED
17050
+ }))
17051
+ });
17052
+ }
17053
+ const div = computeDivergences(graph);
17054
+ sections.push({
17055
+ heading: "Divergences",
17056
+ facts: [
17057
+ {
17058
+ 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.`
17059
+ }
17060
+ ]
17061
+ });
17062
+ return sections;
17063
+ }
17064
+ function buildGlobalSections(intent, graph, incidents) {
17065
+ switch (intent) {
17066
+ case "divergence":
17067
+ return [buildGlobalDivergenceSection(graph)];
17068
+ case "incidents":
17069
+ return [buildGlobalIncidentsSection(incidents)];
17070
+ case "overview":
17071
+ return buildOverviewSections(graph, incidents);
17072
+ default:
17073
+ return null;
17074
+ }
17075
+ }
17076
+ var SECTION_ORDER = {
17077
+ "root-cause": ["root-cause", "incidents", "blast", "observed", "divergence"],
17078
+ "blast-radius": ["blast", "dependencies", "observed", "incidents"],
17079
+ dependencies: ["dependencies", "observed", "blast"],
17080
+ observed: ["observed", "dependencies", "incidents"],
17081
+ incidents: ["incidents", "root-cause", "observed"],
17082
+ divergence: ["divergence", "observed", "dependencies", "incidents"],
17083
+ overview: ["dependencies", "observed", "blast", "incidents", "divergence"]
17084
+ };
17085
+ function buildSection(kind, graph, node, incidents, now) {
17086
+ switch (kind) {
17087
+ case "root-cause":
17088
+ return buildRootCauseSection(graph, node, incidents, now);
17089
+ case "dependencies":
17090
+ return buildDependenciesSection(graph, node);
17091
+ case "observed":
17092
+ return buildObservedSection(graph, node);
17093
+ case "blast":
17094
+ return buildBlastSection(graph, node);
17095
+ case "incidents":
17096
+ return buildIncidentsSection(node, incidents);
17097
+ case "divergence":
17098
+ return buildDivergenceSection(graph, node);
17099
+ }
17100
+ }
17101
+ function summarizeGlobal(intent, sections) {
17102
+ const lead = sections[0];
17103
+ switch (intent) {
17104
+ case "divergence": {
17105
+ const none = lead?.facts[0]?.text.startsWith("None") ?? true;
17106
+ return none ? "No divergences across the graph \u2014 declared code and observed runtime agree." : `${lead.heading} across the graph, highest-confidence first below.`;
17107
+ }
17108
+ case "incidents": {
17109
+ const none = lead?.facts[0]?.text.startsWith("None") ?? true;
17110
+ return none ? "No incidents recorded across the system \u2014 the OBSERVED incident store is empty." : `${lead.heading}, aggregated by node below.`;
17111
+ }
17112
+ case "overview":
17113
+ default:
17114
+ 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.`;
17115
+ }
17116
+ }
17117
+ function summarize(question, intent, matched, primary, sections, scope) {
17118
+ if (scope === "global") {
17119
+ return summarizeGlobal(intent, sections);
17120
+ }
17121
+ if (!primary) {
17122
+ 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.`;
17123
+ }
17124
+ const lead = sections[0];
17125
+ const others = matched.slice(1);
17126
+ const alsoNote = others.length ? ` Also matched: ${others.map((m) => m.nodeId).join(", ")}.` : "";
17127
+ let core;
17128
+ switch (intent) {
17129
+ case "root-cause": {
17130
+ const rc = sections.find((s) => s.heading === "Root cause (navigation)");
17131
+ if (rc) {
17132
+ core = rc.facts[0]?.text ?? "";
17133
+ } else if (lead) {
17134
+ core = `No root cause surfaced for ${primary} \u2014 it may be healthy, or the failure isn't recorded. Nearest context: ${lead.heading.toLowerCase()}.`;
17135
+ } else {
17136
+ core = `No root cause surfaced for ${primary} \u2014 it may be healthy.`;
17137
+ }
17138
+ break;
17139
+ }
17140
+ case "blast-radius":
17141
+ core = lead ? `${lead.heading} of ${primary}.` : `${primary} has no dependents \u2014 nothing else would break if it failed.`;
17142
+ break;
17143
+ case "dependencies":
17144
+ core = lead ? `${primary}: ${lead.heading.toLowerCase()} listed below.` : `${primary} has no declared dependencies in the graph.`;
17145
+ break;
17146
+ case "observed":
17147
+ core = lead ? `${primary} at runtime: ${lead.facts.length} OBSERVED fact${lead.facts.length === 1 ? "" : "s"}.` : `No runtime traffic OBSERVED for ${primary}.`;
17148
+ break;
17149
+ case "incidents":
17150
+ core = lead ? `${primary}: ${lead.heading.toLowerCase()}.` : `No incidents recorded against ${primary}.`;
17151
+ break;
17152
+ case "divergence":
17153
+ core = lead ? `${lead.heading} involving ${primary}.` : `No divergences involve ${primary} \u2014 declared and observed agree here.`;
17154
+ break;
17155
+ default:
17156
+ core = `${primary}: ${sections.length} view${sections.length === 1 ? "" : "s"} of its fused local context below.`;
17157
+ }
17158
+ return `${core}${alsoNote}`;
17159
+ }
17160
+ async function askGraph(graph, question, opts = {}) {
17161
+ const now = opts.now ?? Date.now();
17162
+ const maxNodes = opts.maxNodes ?? DEFAULT_MAX_NODES;
17163
+ const intent = classifyIntent(question);
17164
+ const matched = await resolveEntities(graph, question, opts.searchIndex, maxNodes);
17165
+ const primary = matched[0]?.nodeId;
17166
+ const sections = [];
17167
+ let scope;
17168
+ if (primary) {
17169
+ scope = "node";
17170
+ for (const kind of SECTION_ORDER[intent]) {
17171
+ const s = buildSection(kind, graph, primary, opts.incidents, now);
17172
+ if (s && s.facts.length > 0) sections.push(s);
17173
+ }
17174
+ } else {
17175
+ const global = buildGlobalSections(intent, graph, opts.incidents);
17176
+ if (global) {
17177
+ scope = "global";
17178
+ for (const s of global) if (s.facts.length > 0) sections.push(s);
17179
+ }
17180
+ }
17181
+ const answer = summarize(question, intent, matched, primary, sections, scope);
17182
+ const provSet = /* @__PURE__ */ new Set();
17183
+ for (const s of sections) for (const f of s.facts) if (f.provenance) provSet.add(f.provenance);
17184
+ const confidence = sections[0]?.facts[0]?.confidence;
17185
+ return import_types59.AskResultSchema.parse({
17186
+ question,
17187
+ intent,
17188
+ matched,
17189
+ ...primary ? { primaryNode: primary } : {},
17190
+ ...scope ? { scope } : {},
17191
+ sections,
17192
+ answer,
17193
+ ...confidence !== void 0 ? { confidence } : {},
17194
+ provenance: [...provSet]
17195
+ });
17196
+ }
17197
+
16565
17198
  // src/diff.ts
16566
17199
  init_cjs_shims();
16567
17200
  var import_node_fs36 = require("fs");
@@ -16696,7 +17329,7 @@ init_cjs_shims();
16696
17329
  var import_node_fs37 = require("fs");
16697
17330
  var import_node_os3 = __toESM(require("os"), 1);
16698
17331
  var import_node_path72 = __toESM(require("path"), 1);
16699
- var import_types59 = require("@neat.is/types");
17332
+ var import_types60 = require("@neat.is/types");
16700
17333
  var LOCK_TIMEOUT_MS = 5e3;
16701
17334
  var LOCK_RETRY_MS = 50;
16702
17335
  function neatHome() {
@@ -16906,10 +17539,10 @@ async function readRegistry() {
16906
17539
  throw err;
16907
17540
  }
16908
17541
  const parsed = JSON.parse(raw);
16909
- return import_types59.RegistryFileSchema.parse(parsed);
17542
+ return import_types60.RegistryFileSchema.parse(parsed);
16910
17543
  }
16911
17544
  async function writeRegistry(reg) {
16912
- const validated = import_types59.RegistryFileSchema.parse(reg);
17545
+ const validated = import_types60.RegistryFileSchema.parse(reg);
16913
17546
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
16914
17547
  }
16915
17548
  var ProjectNameCollisionError = class extends Error {
@@ -17310,15 +17943,15 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
17310
17943
 
17311
17944
  // src/connectors/index.ts
17312
17945
  init_cjs_shims();
17313
- var import_types60 = require("@neat.is/types");
17946
+ var import_types61 = require("@neat.is/types");
17314
17947
  var NO_ENV = "unknown";
17315
17948
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
17316
17949
  if (!graph.hasNode(targetNodeId)) return void 0;
17317
17950
  const sites = [];
17318
17951
  for (const edgeId of graph.inboundEdges(targetNodeId)) {
17319
17952
  const edge = graph.getEdgeAttributes(edgeId);
17320
- if (edge.provenance !== import_types60.Provenance.EXTRACTED) continue;
17321
- const parsed = (0, import_types60.parseFileId)(edge.source);
17953
+ if (edge.provenance !== import_types61.Provenance.EXTRACTED) continue;
17954
+ const parsed = (0, import_types61.parseFileId)(edge.source);
17322
17955
  if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
17323
17956
  const site = { relPath: edge.evidence.file };
17324
17957
  if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
@@ -17329,7 +17962,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
17329
17962
  function routeCallSiteFor(graph, targetNodeId) {
17330
17963
  if (!graph.hasNode(targetNodeId)) return void 0;
17331
17964
  const attrs = graph.getNodeAttributes(targetNodeId);
17332
- if (attrs.type !== import_types60.NodeType.RouteNode || !attrs.path) return void 0;
17965
+ if (attrs.type !== import_types61.NodeType.RouteNode || !attrs.path) return void 0;
17333
17966
  const site = { relPath: attrs.path };
17334
17967
  if (attrs.line !== void 0) site.line = attrs.line;
17335
17968
  return site;
@@ -17941,23 +18574,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
17941
18574
 
17942
18575
  // src/connectors/supabase/resolve.ts
17943
18576
  init_cjs_shims();
17944
- var import_types62 = require("@neat.is/types");
18577
+ var import_types63 = require("@neat.is/types");
17945
18578
  function createSupabaseResolveTarget(graph, config) {
17946
18579
  return (signal, _ctx) => {
17947
18580
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
17948
18581
  return null;
17949
18582
  }
17950
- const subResourceId = (0, import_types62.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
18583
+ const subResourceId = (0, import_types63.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
17951
18584
  if (graph.hasNode(subResourceId)) {
17952
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types62.EdgeType.CALLS };
18585
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
17953
18586
  }
17954
- const bareResourceId = (0, import_types62.infraId)(signal.targetKind, signal.targetName);
18587
+ const bareResourceId = (0, import_types63.infraId)(signal.targetKind, signal.targetName);
17955
18588
  if (graph.hasNode(bareResourceId)) {
17956
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types62.EdgeType.CALLS };
18589
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
17957
18590
  }
17958
- const projectLevelId = (0, import_types62.infraId)("supabase", config.nodeRef);
18591
+ const projectLevelId = (0, import_types63.infraId)("supabase", config.nodeRef);
17959
18592
  if (graph.hasNode(projectLevelId)) {
17960
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types62.EdgeType.CALLS };
18593
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
17961
18594
  }
17962
18595
  return null;
17963
18596
  };
@@ -18050,7 +18683,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
18050
18683
 
18051
18684
  // src/connectors/railway/index.ts
18052
18685
  init_cjs_shims();
18053
- var import_types66 = require("@neat.is/types");
18686
+ var import_types67 = require("@neat.is/types");
18054
18687
 
18055
18688
  // src/connectors/railway/client.ts
18056
18689
  init_cjs_shims();
@@ -18201,7 +18834,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
18201
18834
  const out = [];
18202
18835
  graph.forEachNode((_id, attrs) => {
18203
18836
  const node = attrs;
18204
- if (node.type !== import_types66.NodeType.RouteNode) return;
18837
+ if (node.type !== import_types67.NodeType.RouteNode) return;
18205
18838
  const route = attrs;
18206
18839
  if (route.service !== serviceName) return;
18207
18840
  out.push({
@@ -18305,12 +18938,12 @@ function createRailwayResolveTarget(config) {
18305
18938
  const serviceName = config.serviceNameById[config.serviceId];
18306
18939
  if (!serviceName) return null;
18307
18940
  if (signal.targetKind === ROUTE_TARGET_KIND) {
18308
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types66.EdgeType.CALLS };
18941
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types67.EdgeType.CALLS };
18309
18942
  }
18310
18943
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
18311
18944
  const peerName = config.serviceNameById[signal.targetName];
18312
18945
  if (!peerName) return null;
18313
- return { targetNodeId: (0, import_types66.serviceId)(peerName), serviceName, edgeType: import_types66.EdgeType.CONNECTS_TO };
18946
+ return { targetNodeId: (0, import_types67.serviceId)(peerName), serviceName, edgeType: import_types67.EdgeType.CONNECTS_TO };
18314
18947
  }
18315
18948
  return null;
18316
18949
  };
@@ -18498,7 +19131,7 @@ function mapLogEntriesToSignals(entries) {
18498
19131
 
18499
19132
  // src/connectors/firebase/resolve.ts
18500
19133
  init_cjs_shims();
18501
- var import_types67 = require("@neat.is/types");
19134
+ var import_types68 = require("@neat.is/types");
18502
19135
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
18503
19136
  switch (resourceType) {
18504
19137
  case "cloud_function":
@@ -18513,7 +19146,7 @@ function routeEntriesFor(graph, serviceName) {
18513
19146
  const entries = [];
18514
19147
  graph.forEachNode((_id, attrs) => {
18515
19148
  const node = attrs;
18516
- if (node.type !== import_types67.NodeType.RouteNode) return;
19149
+ if (node.type !== import_types68.NodeType.RouteNode) return;
18517
19150
  const route = attrs;
18518
19151
  if (route.service !== serviceName) return;
18519
19152
  entries.push({
@@ -18545,7 +19178,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
18545
19178
  return {
18546
19179
  targetNodeId: match.routeNodeId,
18547
19180
  serviceName,
18548
- edgeType: import_types67.EdgeType.CALLS
19181
+ edgeType: import_types68.EdgeType.CALLS
18549
19182
  };
18550
19183
  };
18551
19184
  }
@@ -18572,7 +19205,7 @@ init_cjs_shims();
18572
19205
 
18573
19206
  // src/connectors/cloudflare/connector.ts
18574
19207
  init_cjs_shims();
18575
- var import_types69 = require("@neat.is/types");
19208
+ var import_types70 = require("@neat.is/types");
18576
19209
 
18577
19210
  // src/connectors/cloudflare/client.ts
18578
19211
  init_cjs_shims();
@@ -18736,7 +19369,7 @@ function findTaggedWorkerFileNode(graph, workerName) {
18736
19369
  graph.forEachNode((id, attrs) => {
18737
19370
  if (found) return;
18738
19371
  const a = attrs;
18739
- if (a.type === import_types69.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
19372
+ if (a.type === import_types70.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
18740
19373
  found = id;
18741
19374
  }
18742
19375
  });
@@ -18748,7 +19381,7 @@ function findMatchingRouteNode(graph, serviceName, method, path76) {
18748
19381
  graph.forEachNode((id, attrs) => {
18749
19382
  if (found) return;
18750
19383
  const a = attrs;
18751
- if (a.type !== import_types69.NodeType.RouteNode || a.service !== serviceName) return;
19384
+ if (a.type !== import_types70.NodeType.RouteNode || a.service !== serviceName) return;
18752
19385
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
18753
19386
  const routeMethod = (a.method ?? "").toUpperCase();
18754
19387
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -18767,11 +19400,11 @@ function createCloudflareResolveTarget(config, graph) {
18767
19400
  };
18768
19401
  const mapping = config.workers?.[scriptName];
18769
19402
  if (mapping) {
18770
- const wholeFileId = (0, import_types69.fileId)(mapping.service, mapping.entryFile);
19403
+ const wholeFileId = (0, import_types70.fileId)(mapping.service, mapping.entryFile);
18771
19404
  return {
18772
19405
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
18773
19406
  serviceName: mapping.service,
18774
- edgeType: import_types69.EdgeType.CALLS
19407
+ edgeType: import_types70.EdgeType.CALLS
18775
19408
  };
18776
19409
  }
18777
19410
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -18780,13 +19413,13 @@ function createCloudflareResolveTarget(config, graph) {
18780
19413
  return {
18781
19414
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
18782
19415
  serviceName: fileNode.service,
18783
- edgeType: import_types69.EdgeType.CALLS
19416
+ edgeType: import_types70.EdgeType.CALLS
18784
19417
  };
18785
19418
  }
18786
19419
  return {
18787
- targetNodeId: (0, import_types69.infraId)("cloudflare-worker", scriptName),
19420
+ targetNodeId: (0, import_types70.infraId)("cloudflare-worker", scriptName),
18788
19421
  serviceName: scriptName,
18789
- edgeType: import_types69.EdgeType.CALLS,
19422
+ edgeType: import_types70.EdgeType.CALLS,
18790
19423
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
18791
19424
  };
18792
19425
  };
@@ -18982,14 +19615,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
18982
19615
 
18983
19616
  // src/connectors/neon/resolve.ts
18984
19617
  init_cjs_shims();
18985
- var import_types73 = require("@neat.is/types");
19618
+ var import_types74 = require("@neat.is/types");
18986
19619
  function createNeonResolveTarget(config) {
18987
19620
  return (signal) => {
18988
19621
  if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
18989
19622
  return {
18990
- targetNodeId: (0, import_types73.infraId)("sql-table", signal.targetName),
19623
+ targetNodeId: (0, import_types74.infraId)("sql-table", signal.targetName),
18991
19624
  serviceName: config.serviceName,
18992
- edgeType: import_types73.EdgeType.CALLS,
19625
+ edgeType: import_types74.EdgeType.CALLS,
18993
19626
  ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
18994
19627
  };
18995
19628
  };
@@ -19170,14 +19803,14 @@ function mapLogEntriesToSignals2(entries) {
19170
19803
 
19171
19804
  // src/connectors/cloud-run/resolve.ts
19172
19805
  init_cjs_shims();
19173
- var import_types77 = require("@neat.is/types");
19806
+ var import_types78 = require("@neat.is/types");
19174
19807
  var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
19175
19808
  function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
19176
19809
  let found = null;
19177
19810
  graph.forEachNode((_id, attrs) => {
19178
19811
  if (found) return;
19179
19812
  const node = attrs;
19180
- if (node.type !== import_types77.NodeType.RouteNode) return;
19813
+ if (node.type !== import_types78.NodeType.RouteNode) return;
19181
19814
  const route = attrs;
19182
19815
  if (route.service !== serviceName || !route.pathTemplate) return;
19183
19816
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -19202,13 +19835,13 @@ function createCloudRunResolveTarget(graph, config) {
19202
19835
  normalizePathTemplate(path76)
19203
19836
  );
19204
19837
  if (routeNodeId) {
19205
- return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types77.EdgeType.CALLS };
19838
+ return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types78.EdgeType.CALLS };
19206
19839
  }
19207
19840
  }
19208
19841
  return {
19209
- targetNodeId: (0, import_types77.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
19842
+ targetNodeId: (0, import_types78.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
19210
19843
  serviceName: mappedService ?? gcpServiceName,
19211
- edgeType: import_types77.EdgeType.CALLS,
19844
+ edgeType: import_types78.EdgeType.CALLS,
19212
19845
  ensureInfraNode: {
19213
19846
  kind: CLOUD_RUN_SERVICE_INFRA_KIND,
19214
19847
  name: gcpServiceName,
@@ -19249,7 +19882,7 @@ function createCloudRunConnector(graph, config = {}) {
19249
19882
 
19250
19883
  // src/connectors/render/index.ts
19251
19884
  init_cjs_shims();
19252
- var import_types80 = require("@neat.is/types");
19885
+ var import_types81 = require("@neat.is/types");
19253
19886
 
19254
19887
  // src/connectors/render/types.ts
19255
19888
  init_cjs_shims();
@@ -19327,7 +19960,7 @@ function buildRenderRouteIndex(graph, serviceName) {
19327
19960
  const out = [];
19328
19961
  graph.forEachNode((_id, attrs) => {
19329
19962
  const node = attrs;
19330
- if (node.type !== import_types80.NodeType.RouteNode) return;
19963
+ if (node.type !== import_types81.NodeType.RouteNode) return;
19331
19964
  const route = attrs;
19332
19965
  if (route.service !== serviceName) return;
19333
19966
  out.push({
@@ -19412,7 +20045,7 @@ function mapRenderRequestLogsToSignals(entries, routeIndex) {
19412
20045
  function createRenderResolveTarget(config) {
19413
20046
  return (signal) => {
19414
20047
  if (signal.targetKind === ROUTE_TARGET_KIND2) {
19415
- return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types80.EdgeType.CALLS };
20048
+ return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types81.EdgeType.CALLS };
19416
20049
  }
19417
20050
  return null;
19418
20051
  };
@@ -19550,21 +20183,21 @@ function mapInsightsToSignals(rows, observedAtIso) {
19550
20183
 
19551
20184
  // src/connectors/planetscale/resolve.ts
19552
20185
  init_cjs_shims();
19553
- var import_types84 = require("@neat.is/types");
20186
+ var import_types85 = require("@neat.is/types");
19554
20187
  var PLANETSCALE_DATABASE_KIND = "planetscale-database";
19555
20188
  function createPlanetscaleResolveTarget(graph, config) {
19556
20189
  const databaseName = `${config.organization}/${config.database}`;
19557
20190
  return (signal, _ctx) => {
19558
20191
  if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
19559
- const tableId = (0, import_types84.infraId)("sql-table", signal.targetName);
20192
+ const tableId = (0, import_types85.infraId)("sql-table", signal.targetName);
19560
20193
  if (graph.hasNode(tableId)) {
19561
- return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types84.EdgeType.CALLS };
20194
+ return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types85.EdgeType.CALLS };
19562
20195
  }
19563
- const providerId = (0, import_types84.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
20196
+ const providerId = (0, import_types85.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
19564
20197
  return {
19565
20198
  targetNodeId: providerId,
19566
20199
  serviceName: config.serviceName,
19567
- edgeType: import_types84.EdgeType.CALLS,
20200
+ edgeType: import_types85.EdgeType.CALLS,
19568
20201
  ensureInfraNode: { kind: PLANETSCALE_DATABASE_KIND, name: databaseName, provider: "planetscale" }
19569
20202
  };
19570
20203
  };
@@ -19829,7 +20462,7 @@ function mapBuildsToSignals(builds, serviceName) {
19829
20462
 
19830
20463
  // src/connectors/eas/resolve.ts
19831
20464
  init_cjs_shims();
19832
- var import_types89 = require("@neat.is/types");
20465
+ var import_types90 = require("@neat.is/types");
19833
20466
  var NO_ENV2 = "unknown";
19834
20467
  var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
19835
20468
  var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
@@ -19845,8 +20478,8 @@ function configBasenamesForPhase(phase) {
19845
20478
  function configNodeService(graph, configNodeId) {
19846
20479
  for (const edgeId of graph.inboundEdges(configNodeId)) {
19847
20480
  const edge = graph.getEdgeAttributes(edgeId);
19848
- if (edge.type !== import_types89.EdgeType.CONFIGURED_BY) continue;
19849
- const parsed = (0, import_types89.parseFileId)(edge.source);
20481
+ if (edge.type !== import_types90.EdgeType.CONFIGURED_BY) continue;
20482
+ const parsed = (0, import_types90.parseFileId)(edge.source);
19850
20483
  if (parsed) return parsed.service;
19851
20484
  }
19852
20485
  return null;
@@ -19857,7 +20490,7 @@ function findConfigNode(graph, basenames, serviceName) {
19857
20490
  graph.forEachNode((id, attrs) => {
19858
20491
  if (scoped) return;
19859
20492
  const node = attrs;
19860
- if (node.type !== import_types89.NodeType.ConfigNode) return;
20493
+ if (node.type !== import_types90.NodeType.ConfigNode) return;
19861
20494
  if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
19862
20495
  if (anyMatch === null) anyMatch = id;
19863
20496
  if (configNodeService(graph, id) === serviceName) scoped = id;
@@ -19874,13 +20507,13 @@ function createEasResolveTarget(graph) {
19874
20507
  if (basenames.length > 0) {
19875
20508
  const configNodeId = findConfigNode(graph, basenames, serviceName);
19876
20509
  if (configNodeId) {
19877
- return { targetNodeId: configNodeId, serviceName, edgeType: import_types89.EdgeType.CALLS };
20510
+ return { targetNodeId: configNodeId, serviceName, edgeType: import_types90.EdgeType.CALLS };
19878
20511
  }
19879
20512
  }
19880
20513
  return {
19881
20514
  targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV2),
19882
20515
  serviceName,
19883
- edgeType: import_types89.EdgeType.CALLS
20516
+ edgeType: import_types90.EdgeType.CALLS
19884
20517
  };
19885
20518
  };
19886
20519
  }
@@ -20605,11 +21238,11 @@ function registerRoutes(scope, ctx) {
20605
21238
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
20606
21239
  const parsed = [];
20607
21240
  for (const c of candidates) {
20608
- const r = import_types92.DivergenceTypeSchema.safeParse(c);
21241
+ const r = import_types93.DivergenceTypeSchema.safeParse(c);
20609
21242
  if (!r.success) {
20610
21243
  return reply.code(400).send({
20611
21244
  error: `unknown divergence type "${c}"`,
20612
- allowed: import_types92.DivergenceTypeSchema.options
21245
+ allowed: import_types93.DivergenceTypeSchema.options
20613
21246
  });
20614
21247
  }
20615
21248
  parsed.push(r.data);
@@ -20851,6 +21484,18 @@ function registerRoutes(scope, ctx) {
20851
21484
  matches: matches.slice(0, safeLimit)
20852
21485
  };
20853
21486
  });
21487
+ scope.get("/graph/ask", async (req, reply) => {
21488
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
21489
+ if (!proj) return;
21490
+ const question = (req.query.q ?? "").trim();
21491
+ if (!question) return reply.code(400).send({ error: "query parameter `q` is required" });
21492
+ const epath = errorsPathFor(proj);
21493
+ const incidents = epath ? await readErrorEvents(epath) : [];
21494
+ return askGraph(proj.graph, question, {
21495
+ ...proj.searchIndex ? { searchIndex: proj.searchIndex } : {},
21496
+ incidents
21497
+ });
21498
+ });
20854
21499
  scope.get(
20855
21500
  "/graph/diff",
20856
21501
  async (req, reply) => {
@@ -20951,7 +21596,7 @@ function registerRoutes(scope, ctx) {
20951
21596
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
20952
21597
  let violations = await log.readAll();
20953
21598
  if (req.query.severity) {
20954
- const sev = import_types92.PolicySeveritySchema.safeParse(req.query.severity);
21599
+ const sev = import_types93.PolicySeveritySchema.safeParse(req.query.severity);
20955
21600
  if (!sev.success) {
20956
21601
  return reply.code(400).send({
20957
21602
  error: "invalid severity",
@@ -20990,7 +21635,7 @@ function registerRoutes(scope, ctx) {
20990
21635
  scope.post("/policies/check", async (req, reply) => {
20991
21636
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
20992
21637
  if (!proj) return;
20993
- const parsed = import_types92.PoliciesCheckBodySchema.safeParse(req.body ?? {});
21638
+ const parsed = import_types93.PoliciesCheckBodySchema.safeParse(req.body ?? {});
20994
21639
  if (!parsed.success) {
20995
21640
  return reply.code(400).send({
20996
21641
  error: "invalid /policies/check body",
@@ -21339,7 +21984,7 @@ function unroutedErrorsPath(neatHome3) {
21339
21984
  }
21340
21985
 
21341
21986
  // src/daemon.ts
21342
- var import_types93 = require("@neat.is/types");
21987
+ var import_types94 = require("@neat.is/types");
21343
21988
  function daemonJsonPath(scanPath) {
21344
21989
  return import_node_path75.default.join(scanPath, "neat-out", "daemon.json");
21345
21990
  }
@@ -21451,8 +22096,8 @@ function isTokenContained(needle, haystack) {
21451
22096
  const n = needle.toLowerCase();
21452
22097
  const h = haystack.toLowerCase();
21453
22098
  if (!h.includes(n)) return false;
21454
- const tokens = h.split(/[-_]/);
21455
- return tokens.includes(n);
22099
+ const tokens2 = h.split(/[-_]/);
22100
+ return tokens2.includes(n);
21456
22101
  }
21457
22102
  function serviceNameMatchesProject(serviceName, project) {
21458
22103
  if (serviceName.toLowerCase() === project.toLowerCase()) return true;
@@ -21464,7 +22109,7 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
21464
22109
  if (!serviceName) return true;
21465
22110
  if (serviceNameMatchesProject(serviceName, project)) return true;
21466
22111
  return graph.someNode(
21467
- (_id, attrs) => attrs.type === import_types93.NodeType.ServiceNode && attrs.name === serviceName
22112
+ (_id, attrs) => attrs.type === import_types94.NodeType.ServiceNode && attrs.name === serviceName
21468
22113
  );
21469
22114
  }
21470
22115
  async function bootstrapProject(entry, connectors = [], neatHome3) {