@neat.is/core 0.9.2-dev.20260822 → 0.9.3-dev.20260823

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([
@@ -9892,12 +9892,12 @@ async function resolveConfigs(literals, keys, serviceDir, looksLike, parse11) {
9892
9892
  const value = await interpolateEnvRefs(raw, serviceDir);
9893
9893
  if (!looksLike(value)) continue;
9894
9894
  const parsed = parse11(value);
9895
- if (parsed) out.push(parsed);
9895
+ if (parsed) out.push({ ...parsed, hostSource: "config" });
9896
9896
  }
9897
9897
  for (const lit of literals) {
9898
9898
  if (!looksLike(lit)) continue;
9899
9899
  const parsed = parse11(lit);
9900
- if (parsed) out.push(parsed);
9900
+ if (parsed) out.push({ ...parsed, hostSource: "literal" });
9901
9901
  }
9902
9902
  return out;
9903
9903
  }
@@ -10166,7 +10166,10 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
10166
10166
  type: import_types23.EdgeType.CONNECTS_TO,
10167
10167
  provenance: import_types23.Provenance.EXTRACTED,
10168
10168
  confidence: (0, import_types23.confidenceForExtracted)("structural"),
10169
- evidence: { file: evidenceFile }
10169
+ // Carry how the host was recovered (ADR-213) so the divergence ranker
10170
+ // can tell a real declared store from a hardcoded fault-injection /
10171
+ // flag-gated probe. Only set when the parser distinguished the two.
10172
+ evidence: config.hostSource ? { file: evidenceFile, hostSource: config.hostSource } : { file: evidenceFile }
10170
10173
  };
10171
10174
  if (!graph.hasEdge(edge.id)) {
10172
10175
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -15839,7 +15842,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
15839
15842
  init_cjs_shims();
15840
15843
  var import_fastify2 = __toESM(require("fastify"), 1);
15841
15844
  var import_cors = __toESM(require("@fastify/cors"), 1);
15842
- var import_types92 = require("@neat.is/types");
15845
+ var import_types93 = require("@neat.is/types");
15843
15846
 
15844
15847
  // src/extend/index.ts
15845
15848
  init_cjs_shims();
@@ -16443,6 +16446,53 @@ function detectColumnDrift(node) {
16443
16446
  function involvesNode(d, nodeId) {
16444
16447
  return d.source === nodeId || d.target === nodeId;
16445
16448
  }
16449
+ function datastoreEverObserved(graph, nodeId) {
16450
+ if (!graph.hasNode(nodeId)) return false;
16451
+ const n = graph.getNodeAttributes(nodeId);
16452
+ if (n.type === import_types58.NodeType.DatabaseNode) {
16453
+ const via = n.discoveredVia;
16454
+ if (via === "otel" || via === "merged") return true;
16455
+ }
16456
+ for (const edgeId of graph.inboundEdges(nodeId)) {
16457
+ const e = graph.getEdgeAttributes(edgeId);
16458
+ if (e.provenance === import_types58.Provenance.OBSERVED || e.provenance === import_types58.Provenance.STALE) return true;
16459
+ }
16460
+ return false;
16461
+ }
16462
+ function serviceHasObservedSameEngineStore(graph, buckets2, serviceId16, engine, excludeTarget) {
16463
+ for (const bucket of buckets2.values()) {
16464
+ if (bucket.type !== import_types58.EdgeType.CONNECTS_TO) continue;
16465
+ if (bucket.source !== serviceId16) continue;
16466
+ if (bucket.target === excludeTarget) continue;
16467
+ if (!graph.hasNode(bucket.target)) continue;
16468
+ const target = graph.getNodeAttributes(bucket.target);
16469
+ if (target.type !== import_types58.NodeType.DatabaseNode) continue;
16470
+ if (target.engine !== engine) continue;
16471
+ if (bucket.observed || datastoreEverObserved(graph, bucket.target)) return true;
16472
+ }
16473
+ return false;
16474
+ }
16475
+ var DEAD_CODE_PROBE_CONFIDENCE = 0.1;
16476
+ function dampenDeadCodeProbes(graph, buckets2, all) {
16477
+ return all.map((d) => {
16478
+ if (d.type !== "missing-observed") return d;
16479
+ if (!d.extracted || d.edgeType !== import_types58.EdgeType.CONNECTS_TO) return d;
16480
+ if (!graph.hasNode(d.target)) return d;
16481
+ const target = graph.getNodeAttributes(d.target);
16482
+ if (target.type !== import_types58.NodeType.DatabaseNode) return d;
16483
+ if (d.extracted.evidence?.hostSource !== "literal") return d;
16484
+ if (datastoreEverObserved(graph, d.target)) return d;
16485
+ const engine = target.engine;
16486
+ if (!serviceHasObservedSameEngineStore(graph, buckets2, d.source, engine, d.target)) return d;
16487
+ const host = target.host ?? target.name;
16488
+ return {
16489
+ ...d,
16490
+ confidence: Math.min(d.confidence, DEAD_CODE_PROBE_CONFIDENCE),
16491
+ reason: `${d.source} declares a ${engine} connection to a hardcoded-literal host (${host}) that production has never observed, while it does observe another ${engine} store \u2014 this reads as a flag-gated or dead-code declaration (e.g. a fault-injection probe), not a real declared-vs-observed gap.`,
16492
+ recommendation: "Confirm this is an intentional dead alternate \u2014 a fault-injection probe or a flag-gated branch. If it is meant to run in production, check the feature flag or conditional that gates it; if not, it can be ignored or removed."
16493
+ };
16494
+ });
16495
+ }
16446
16496
  function suppressHostMismatchHalves(all) {
16447
16497
  const observedHalf = /* @__PURE__ */ new Set();
16448
16498
  const declaredHalf = /* @__PURE__ */ new Set();
@@ -16479,7 +16529,8 @@ function computeDivergences(graph, opts = {}) {
16479
16529
  }
16480
16530
  });
16481
16531
  const reconciled = suppressHostMismatchHalves(all);
16482
- let filtered = reconciled;
16532
+ const dampened = dampenDeadCodeProbes(graph, buckets2, reconciled);
16533
+ let filtered = dampened;
16483
16534
  if (opts.type) {
16484
16535
  const allowed = opts.type;
16485
16536
  filtered = filtered.filter((d) => allowed.has(d.type));
@@ -16562,6 +16613,639 @@ function queryLogEntries(opts) {
16562
16613
  return merged;
16563
16614
  }
16564
16615
 
16616
+ // src/ask.ts
16617
+ init_cjs_shims();
16618
+ var import_types59 = require("@neat.is/types");
16619
+ var DEFAULT_MAX_NODES = 3;
16620
+ var MAX_FACTS_PER_SECTION = 6;
16621
+ var INTENT_RULES = [
16622
+ {
16623
+ intent: "root-cause",
16624
+ test: /\b(why|root[\s-]?cause|failing|fail(?:s|ed)?|breaking|broke(?:n)?|crash(?:ing|ed)?|throw(?:ing|s)?|culprit|5\d\d)\b/
16625
+ },
16626
+ {
16627
+ intent: "blast-radius",
16628
+ 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/
16629
+ },
16630
+ {
16631
+ intent: "divergence",
16632
+ test: /\b(diverg\w*|weird|mismatch|drift|out\s+of\s+sync|inconsistent|disagree\w*|declared\s+vs|anything\s+wrong)\b/
16633
+ },
16634
+ {
16635
+ intent: "incidents",
16636
+ test: /\b(incidents?|recent\s+(?:errors?|failures?)|error\s+history|failure\s+history)\b/
16637
+ },
16638
+ {
16639
+ intent: "observed",
16640
+ test: /\b(at\s+runtime|in\s+prod(?:uction)?|actually\s+call\w*|really\s+call\w*|observed|runtime\s+traffic)\b/
16641
+ },
16642
+ {
16643
+ intent: "dependencies",
16644
+ test: /\b(depend\w*|calls?|uses?|imports?|relies\s+on|needs?)\b/
16645
+ }
16646
+ ];
16647
+ function classifyIntent(question) {
16648
+ const q = question.toLowerCase();
16649
+ for (const rule of INTENT_RULES) {
16650
+ if (rule.test.test(q)) return rule.intent;
16651
+ }
16652
+ return "overview";
16653
+ }
16654
+ var STOPWORDS = /* @__PURE__ */ new Set([
16655
+ "the",
16656
+ "a",
16657
+ "an",
16658
+ "is",
16659
+ "are",
16660
+ "was",
16661
+ "were",
16662
+ "be",
16663
+ "been",
16664
+ "being",
16665
+ "of",
16666
+ "to",
16667
+ "in",
16668
+ "on",
16669
+ "at",
16670
+ "by",
16671
+ "for",
16672
+ "with",
16673
+ "and",
16674
+ "or",
16675
+ "but",
16676
+ "if",
16677
+ "as",
16678
+ "this",
16679
+ "that",
16680
+ "these",
16681
+ "those",
16682
+ "it",
16683
+ "its",
16684
+ "do",
16685
+ "does",
16686
+ "did",
16687
+ "has",
16688
+ "have",
16689
+ "had",
16690
+ "what",
16691
+ "which",
16692
+ "who",
16693
+ "whom",
16694
+ "how",
16695
+ "when",
16696
+ "where",
16697
+ "my",
16698
+ "me",
16699
+ "i",
16700
+ "we",
16701
+ "you",
16702
+ "they",
16703
+ "them",
16704
+ "from",
16705
+ "into",
16706
+ "about",
16707
+ "would",
16708
+ "will",
16709
+ "can",
16710
+ "could",
16711
+ "should",
16712
+ "anything",
16713
+ "everything",
16714
+ "something",
16715
+ "all",
16716
+ "any",
16717
+ "some",
16718
+ "no",
16719
+ "not",
16720
+ "so",
16721
+ "up",
16722
+ "out",
16723
+ "over",
16724
+ "get",
16725
+ "got",
16726
+ "show",
16727
+ "tell",
16728
+ "explain",
16729
+ "find",
16730
+ "give",
16731
+ "happen",
16732
+ "happens",
16733
+ "happening",
16734
+ "here",
16735
+ "there",
16736
+ "system",
16737
+ "code",
16738
+ "service",
16739
+ "node",
16740
+ "graph"
16741
+ ]);
16742
+ var INTENT_WORDS = /* @__PURE__ */ new Set([
16743
+ "why",
16744
+ "root",
16745
+ "cause",
16746
+ "failing",
16747
+ "fail",
16748
+ "fails",
16749
+ "failed",
16750
+ "breaking",
16751
+ "broke",
16752
+ "broken",
16753
+ "crash",
16754
+ "crashing",
16755
+ "crashed",
16756
+ "throw",
16757
+ "throwing",
16758
+ "throws",
16759
+ "culprit",
16760
+ "blast",
16761
+ "radius",
16762
+ "break",
16763
+ "breaks",
16764
+ "impact",
16765
+ "downstream",
16766
+ "dependent",
16767
+ "dependents",
16768
+ "redeploy",
16769
+ "affect",
16770
+ "affects",
16771
+ "affected",
16772
+ "diverge",
16773
+ "divergence",
16774
+ "divergences",
16775
+ "weird",
16776
+ "mismatch",
16777
+ "drift",
16778
+ "inconsistent",
16779
+ "disagree",
16780
+ "declared",
16781
+ "observed",
16782
+ "incident",
16783
+ "incidents",
16784
+ "recent",
16785
+ "errors",
16786
+ "error",
16787
+ "failures",
16788
+ "failure",
16789
+ "history",
16790
+ "runtime",
16791
+ "production",
16792
+ "prod",
16793
+ "traffic",
16794
+ "actually",
16795
+ "really",
16796
+ "depend",
16797
+ "depends",
16798
+ "dependency",
16799
+ "dependencies",
16800
+ "call",
16801
+ "calls",
16802
+ "use",
16803
+ "uses",
16804
+ "using",
16805
+ "import",
16806
+ "imports",
16807
+ "relies",
16808
+ "rely",
16809
+ "needs",
16810
+ "need",
16811
+ "sync"
16812
+ ]);
16813
+ function tokens(input) {
16814
+ const spaced = input.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
16815
+ return (spaced.toLowerCase().match(/[a-z0-9]+/g) ?? []).filter((t) => t.length >= 2);
16816
+ }
16817
+ function queryTokens(question) {
16818
+ const seen = /* @__PURE__ */ new Set();
16819
+ const out = [];
16820
+ for (const t of tokens(question)) {
16821
+ if (STOPWORDS.has(t) || INTENT_WORDS.has(t)) continue;
16822
+ if (seen.has(t)) continue;
16823
+ seen.add(t);
16824
+ out.push(t);
16825
+ }
16826
+ return out;
16827
+ }
16828
+ function nodeName(node) {
16829
+ return node.name ?? node.id;
16830
+ }
16831
+ function idBody(id) {
16832
+ const colon = id.indexOf(":");
16833
+ return colon >= 0 ? id.slice(colon + 1) : id;
16834
+ }
16835
+ var EMBED_MIN_SCORE = 0.35;
16836
+ async function resolveEntities(graph, question, searchIndex, maxNodes) {
16837
+ const qTokens = queryTokens(question);
16838
+ const normalized = question.toLowerCase();
16839
+ const best = /* @__PURE__ */ new Map();
16840
+ const consider = (cand) => {
16841
+ const cur = best.get(cand.nodeId);
16842
+ if (!cur || cand.score > cur.score) best.set(cand.nodeId, cand);
16843
+ };
16844
+ graph.forEachNode((id, attrs) => {
16845
+ const node = attrs;
16846
+ if (node.type === import_types59.NodeType.FrontierNode) return;
16847
+ const name = nodeName(node);
16848
+ const body = idBody(id);
16849
+ const labelTokens = /* @__PURE__ */ new Set([...tokens(body), ...tokens(name)]);
16850
+ if (labelTokens.size === 0) return;
16851
+ let matched = 0;
16852
+ for (const t of qTokens) if (labelTokens.has(t)) matched += 1;
16853
+ const named = body.length >= 2 && normalized.includes(body.toLowerCase()) || name.length >= 2 && normalized.includes(name.toLowerCase());
16854
+ if (matched === 0 && !named) return;
16855
+ const coverage = qTokens.length > 0 ? matched / qTokens.length : 0;
16856
+ const via = named ? "id" : matched > 0 && tokens(name).some((t) => qTokens.includes(t)) ? "label" : "token";
16857
+ const score = named ? Math.max(0.9, coverage) : Math.min(0.85, 0.3 + 0.7 * coverage);
16858
+ consider({ nodeId: id, label: name, via, score: Math.min(1, score) });
16859
+ });
16860
+ if (searchIndex) {
16861
+ try {
16862
+ const res = await searchIndex.search(question, 10);
16863
+ if (res.provider !== "substring") {
16864
+ for (const m of res.matches) {
16865
+ if (m.node.type === import_types59.NodeType.FrontierNode) continue;
16866
+ const already = best.get(m.node.id);
16867
+ if (!already && m.score < EMBED_MIN_SCORE) continue;
16868
+ consider({
16869
+ nodeId: m.node.id,
16870
+ label: nodeName(m.node),
16871
+ via: "embedding",
16872
+ score: Math.max(0, Math.min(1, m.score))
16873
+ });
16874
+ }
16875
+ }
16876
+ } catch {
16877
+ }
16878
+ }
16879
+ const viaRank = { id: 3, label: 2, token: 1, embedding: 0 };
16880
+ return [...best.values()].sort(
16881
+ (a, b) => b.score - a.score || viaRank[b.via] - viaRank[a.via] || a.nodeId.localeCompare(b.nodeId)
16882
+ ).slice(0, maxNodes).map((s) => ({ nodeId: s.nodeId, label: s.label, via: s.via, score: Number(s.score.toFixed(3)) }));
16883
+ }
16884
+ function incidentsForNode(nodeId, incidents) {
16885
+ if (!incidents || incidents.length === 0) return [];
16886
+ const svc = nodeId.replace(/^service:/, "");
16887
+ return incidents.filter((e) => e.affectedNode === nodeId || e.service === svc);
16888
+ }
16889
+ function edgeSignalNote(e) {
16890
+ const bits = [];
16891
+ if (e.signal) {
16892
+ bits.push(`${e.signal.spanCount} spans`);
16893
+ if (e.signal.errorCount > 0) bits.push(`${e.signal.errorCount} errors`);
16894
+ if (e.signal.latencyMs?.p95 !== void 0) bits.push(`p95 ${Math.round(e.signal.latencyMs.p95)}ms`);
16895
+ } else if (e.callCount !== void 0) {
16896
+ bits.push(`${e.callCount} calls`);
16897
+ }
16898
+ return bits.length ? ` (${bits.join(", ")})` : "";
16899
+ }
16900
+ function buildRootCauseSection(graph, node, incidents, now) {
16901
+ const result = getRootCause(graph, node, void 0, incidents, { now });
16902
+ if (!result) return null;
16903
+ const lastProv = result.edgeProvenances[result.edgeProvenances.length - 1] ?? import_types59.Provenance.OBSERVED;
16904
+ const facts = [
16905
+ {
16906
+ text: `Root cause: ${result.rootCauseNode} \u2014 ${result.rootCauseReason}`,
16907
+ provenance: lastProv,
16908
+ confidence: result.confidence
16909
+ }
16910
+ ];
16911
+ const candidates = result.candidates ?? [];
16912
+ for (const c of candidates.slice(0, 3)) {
16913
+ facts.push({
16914
+ text: `Candidate ${c.node} \u2014 ${c.classification}: ${c.reason}`,
16915
+ ...c.provenance ? { provenance: c.provenance } : {},
16916
+ confidence: c.confidence
16917
+ });
16918
+ }
16919
+ if (result.traversalPath.length > 1) {
16920
+ facts.push({ text: `Traversal: ${result.traversalPath.join(" \u2190 ")}` });
16921
+ }
16922
+ if (result.fixRecommendation) {
16923
+ facts.push({ text: `Recommended fix: ${result.fixRecommendation}` });
16924
+ }
16925
+ return { heading: "Root cause (navigation)", facts: facts.slice(0, MAX_FACTS_PER_SECTION) };
16926
+ }
16927
+ function buildDependenciesSection(graph, node) {
16928
+ const result = getTransitiveDependencies(graph, node, 2);
16929
+ if (result.total === 0) return null;
16930
+ const facts = result.dependencies.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
16931
+ text: `${d.nodeId} \u2014 ${d.edgeType} (distance ${d.distance})`,
16932
+ // Per-fact provenance: the transitive walk mixes EXTRACTED and OBSERVED
16933
+ // edges, so the tag is on the fact, not asserted for the whole section.
16934
+ provenance: d.provenance
16935
+ }));
16936
+ return { heading: "Dependencies", facts };
16937
+ }
16938
+ function buildObservedSection(graph, node) {
16939
+ const result = getObservedDependencies(graph, node);
16940
+ const facts = [];
16941
+ for (const e of result.dependencies.slice(0, MAX_FACTS_PER_SECTION)) {
16942
+ const via = e.source !== node ? ` via ${e.source}` : "";
16943
+ facts.push({
16944
+ text: `calls ${e.target}${via}${edgeSignalNote(e)}`,
16945
+ provenance: e.provenance,
16946
+ confidence: confidenceForEdge(e)
16947
+ });
16948
+ }
16949
+ if (facts.length === 0) {
16950
+ if (result.observed && result.inboundObservedCount > 0) {
16951
+ facts.push({
16952
+ text: `no outbound runtime calls, but OTel observed ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 a pure receiver`,
16953
+ provenance: import_types59.Provenance.OBSERVED
16954
+ });
16955
+ } else {
16956
+ return null;
16957
+ }
16958
+ }
16959
+ return { heading: "Runtime dependencies (OBSERVED)", facts };
16960
+ }
16961
+ function buildBlastSection(graph, node) {
16962
+ const result = getBlastRadius(graph, node);
16963
+ if (result.totalAffected === 0) return null;
16964
+ const facts = result.affectedNodes.slice(0, MAX_FACTS_PER_SECTION).map((n) => ({
16965
+ text: `${n.nodeId} (distance ${n.distance})`,
16966
+ provenance: n.edgeProvenance,
16967
+ confidence: n.confidence
16968
+ }));
16969
+ return {
16970
+ heading: `Blast radius \u2014 ${result.totalAffected} dependent${result.totalAffected === 1 ? "" : "s"}`,
16971
+ facts
16972
+ };
16973
+ }
16974
+ function buildIncidentsSection(node, incidents) {
16975
+ const relevant = incidentsForNode(node, incidents);
16976
+ if (relevant.length === 0) return null;
16977
+ const ordered = [...relevant].sort((a, b) => b.timestamp.localeCompare(a.timestamp)).slice(0, 4);
16978
+ const facts = ordered.map((ev) => ({
16979
+ text: `${ev.timestamp} \u2014 ${ev.service}: ${ev.errorMessage}`,
16980
+ // ErrorEvents are observation records — OBSERVED by definition.
16981
+ provenance: import_types59.Provenance.OBSERVED
16982
+ }));
16983
+ return { heading: `Recent incidents (OBSERVED) \u2014 ${relevant.length} recorded`, facts };
16984
+ }
16985
+ function divergenceLine(d) {
16986
+ if ((d.type === "missing-observed" || d.type === "missing-extracted") && d.column) {
16987
+ return `[${d.type}] ${d.table ?? d.source} column ${d.column} \u2014 ${d.reason}`;
16988
+ }
16989
+ return `[${d.type}] ${d.source} \u2192 ${d.target} \u2014 ${d.reason}`;
16990
+ }
16991
+ function buildDivergenceSection(graph, node) {
16992
+ const result = computeDivergences(graph, { node });
16993
+ if (result.totalAffected === 0) return null;
16994
+ const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
16995
+ text: divergenceLine(d),
16996
+ confidence: d.confidence
16997
+ // A divergence is composite by construction (EXTRACTED vs OBSERVED), so it
16998
+ // carries no single provenance — the fact leaves it unset.
16999
+ }));
17000
+ return {
17001
+ heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
17002
+ facts
17003
+ };
17004
+ }
17005
+ function buildGlobalDivergenceSection(graph) {
17006
+ const result = computeDivergences(graph);
17007
+ if (result.totalAffected === 0) {
17008
+ return {
17009
+ heading: "Divergences (EXTRACTED vs OBSERVED)",
17010
+ facts: [{ text: "None \u2014 declared code and observed runtime agree across the graph." }]
17011
+ };
17012
+ }
17013
+ const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
17014
+ text: divergenceLine(d),
17015
+ confidence: d.confidence
17016
+ // Composite by construction (EXTRACTED vs OBSERVED), so no single provenance.
17017
+ }));
17018
+ return {
17019
+ heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
17020
+ facts
17021
+ };
17022
+ }
17023
+ function buildGlobalIncidentsSection(incidents) {
17024
+ if (!incidents || incidents.length === 0) {
17025
+ return {
17026
+ heading: "Incidents across the system",
17027
+ facts: [{ text: "None recorded \u2014 the OBSERVED incident store is empty." }]
17028
+ };
17029
+ }
17030
+ const byKey = /* @__PURE__ */ new Map();
17031
+ for (const ev of incidents) {
17032
+ const key = ev.affectedNode || (0, import_types59.serviceId)(ev.service);
17033
+ const cur = byKey.get(key);
17034
+ if (!cur) {
17035
+ byKey.set(key, { key, count: 1, latest: ev.timestamp, sampleMsg: ev.errorMessage });
17036
+ } else {
17037
+ cur.count += 1;
17038
+ if (ev.timestamp > cur.latest) {
17039
+ cur.latest = ev.timestamp;
17040
+ cur.sampleMsg = ev.errorMessage;
17041
+ }
17042
+ }
17043
+ }
17044
+ 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);
17045
+ const facts = rows.map((r) => ({
17046
+ text: `${r.key} \u2014 ${r.count} incident${r.count === 1 ? "" : "s"}, latest ${r.latest}: ${r.sampleMsg}`,
17047
+ provenance: import_types59.Provenance.OBSERVED
17048
+ }));
17049
+ return {
17050
+ heading: `Incidents across the system \u2014 ${incidents.length} recorded`,
17051
+ facts
17052
+ };
17053
+ }
17054
+ function buildOverviewSections(graph, incidents) {
17055
+ const nodeByType = /* @__PURE__ */ new Map();
17056
+ const services = [];
17057
+ graph.forEachNode((_id, attrs) => {
17058
+ const node = attrs;
17059
+ nodeByType.set(node.type, (nodeByType.get(node.type) ?? 0) + 1);
17060
+ if (node.type === import_types59.NodeType.ServiceNode) services.push(node.id);
17061
+ });
17062
+ const edgeByProv = /* @__PURE__ */ new Map();
17063
+ graph.forEachEdge((_id, attrs) => {
17064
+ const p = attrs.provenance;
17065
+ edgeByProv.set(p, (edgeByProv.get(p) ?? 0) + 1);
17066
+ });
17067
+ const sections = [];
17068
+ const count = (t) => nodeByType.get(t) ?? 0;
17069
+ const shapeFacts = [
17070
+ { text: `${graph.order} nodes, ${graph.size} edges` },
17071
+ {
17072
+ 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`
17073
+ }
17074
+ ];
17075
+ for (const p of [import_types59.Provenance.EXTRACTED, import_types59.Provenance.OBSERVED, import_types59.Provenance.INFERRED, import_types59.Provenance.STALE]) {
17076
+ const n = edgeByProv.get(p) ?? 0;
17077
+ if (n > 0) shapeFacts.push({ text: `${n} ${p} edge${n === 1 ? "" : "s"}`, provenance: p });
17078
+ }
17079
+ sections.push({ heading: "System shape", facts: shapeFacts });
17080
+ 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);
17081
+ if (depCounts.length > 0) {
17082
+ sections.push({
17083
+ heading: "Busiest services (by direct dependencies)",
17084
+ facts: depCounts.map((r) => ({
17085
+ text: `${r.s} \u2014 ${r.n} direct dependenc${r.n === 1 ? "y" : "ies"}`
17086
+ }))
17087
+ });
17088
+ }
17089
+ if (incidents && incidents.length > 0) {
17090
+ const incCount = /* @__PURE__ */ new Map();
17091
+ for (const ev of incidents) {
17092
+ const key = ev.affectedNode || (0, import_types59.serviceId)(ev.service);
17093
+ incCount.set(key, (incCount.get(key) ?? 0) + 1);
17094
+ }
17095
+ const top = [...incCount.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, MAX_FACTS_PER_SECTION);
17096
+ sections.push({
17097
+ heading: `Nodes with incidents \u2014 ${incidents.length} recorded`,
17098
+ facts: top.map(([k, n]) => ({
17099
+ text: `${k} \u2014 ${n} incident${n === 1 ? "" : "s"}`,
17100
+ provenance: import_types59.Provenance.OBSERVED
17101
+ }))
17102
+ });
17103
+ }
17104
+ const div = computeDivergences(graph);
17105
+ sections.push({
17106
+ heading: "Divergences",
17107
+ facts: [
17108
+ {
17109
+ 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.`
17110
+ }
17111
+ ]
17112
+ });
17113
+ return sections;
17114
+ }
17115
+ function buildGlobalSections(intent, graph, incidents) {
17116
+ switch (intent) {
17117
+ case "divergence":
17118
+ return [buildGlobalDivergenceSection(graph)];
17119
+ case "incidents":
17120
+ return [buildGlobalIncidentsSection(incidents)];
17121
+ case "overview":
17122
+ return buildOverviewSections(graph, incidents);
17123
+ default:
17124
+ return null;
17125
+ }
17126
+ }
17127
+ var SECTION_ORDER = {
17128
+ "root-cause": ["root-cause", "incidents", "blast", "observed", "divergence"],
17129
+ "blast-radius": ["blast", "dependencies", "observed", "incidents"],
17130
+ dependencies: ["dependencies", "observed", "blast"],
17131
+ observed: ["observed", "dependencies", "incidents"],
17132
+ incidents: ["incidents", "root-cause", "observed"],
17133
+ divergence: ["divergence", "observed", "dependencies", "incidents"],
17134
+ overview: ["dependencies", "observed", "blast", "incidents", "divergence"]
17135
+ };
17136
+ function buildSection(kind, graph, node, incidents, now) {
17137
+ switch (kind) {
17138
+ case "root-cause":
17139
+ return buildRootCauseSection(graph, node, incidents, now);
17140
+ case "dependencies":
17141
+ return buildDependenciesSection(graph, node);
17142
+ case "observed":
17143
+ return buildObservedSection(graph, node);
17144
+ case "blast":
17145
+ return buildBlastSection(graph, node);
17146
+ case "incidents":
17147
+ return buildIncidentsSection(node, incidents);
17148
+ case "divergence":
17149
+ return buildDivergenceSection(graph, node);
17150
+ }
17151
+ }
17152
+ function summarizeGlobal(intent, sections) {
17153
+ const lead = sections[0];
17154
+ switch (intent) {
17155
+ case "divergence": {
17156
+ const none = lead?.facts[0]?.text.startsWith("None") ?? true;
17157
+ return none ? "No divergences across the graph \u2014 declared code and observed runtime agree." : `${lead.heading} across the graph, highest-confidence first below.`;
17158
+ }
17159
+ case "incidents": {
17160
+ const none = lead?.facts[0]?.text.startsWith("None") ?? true;
17161
+ return none ? "No incidents recorded across the system \u2014 the OBSERVED incident store is empty." : `${lead.heading}, aggregated by node below.`;
17162
+ }
17163
+ case "overview":
17164
+ default:
17165
+ 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.`;
17166
+ }
17167
+ }
17168
+ function summarize(question, intent, matched, primary, sections, scope) {
17169
+ if (scope === "global") {
17170
+ return summarizeGlobal(intent, sections);
17171
+ }
17172
+ if (!primary) {
17173
+ 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.`;
17174
+ }
17175
+ const lead = sections[0];
17176
+ const others = matched.slice(1);
17177
+ const alsoNote = others.length ? ` Also matched: ${others.map((m) => m.nodeId).join(", ")}.` : "";
17178
+ let core;
17179
+ switch (intent) {
17180
+ case "root-cause": {
17181
+ const rc = sections.find((s) => s.heading === "Root cause (navigation)");
17182
+ if (rc) {
17183
+ core = rc.facts[0]?.text ?? "";
17184
+ } else if (lead) {
17185
+ core = `No root cause surfaced for ${primary} \u2014 it may be healthy, or the failure isn't recorded. Nearest context: ${lead.heading.toLowerCase()}.`;
17186
+ } else {
17187
+ core = `No root cause surfaced for ${primary} \u2014 it may be healthy.`;
17188
+ }
17189
+ break;
17190
+ }
17191
+ case "blast-radius":
17192
+ core = lead ? `${lead.heading} of ${primary}.` : `${primary} has no dependents \u2014 nothing else would break if it failed.`;
17193
+ break;
17194
+ case "dependencies":
17195
+ core = lead ? `${primary}: ${lead.heading.toLowerCase()} listed below.` : `${primary} has no declared dependencies in the graph.`;
17196
+ break;
17197
+ case "observed":
17198
+ core = lead ? `${primary} at runtime: ${lead.facts.length} OBSERVED fact${lead.facts.length === 1 ? "" : "s"}.` : `No runtime traffic OBSERVED for ${primary}.`;
17199
+ break;
17200
+ case "incidents":
17201
+ core = lead ? `${primary}: ${lead.heading.toLowerCase()}.` : `No incidents recorded against ${primary}.`;
17202
+ break;
17203
+ case "divergence":
17204
+ core = lead ? `${lead.heading} involving ${primary}.` : `No divergences involve ${primary} \u2014 declared and observed agree here.`;
17205
+ break;
17206
+ default:
17207
+ core = `${primary}: ${sections.length} view${sections.length === 1 ? "" : "s"} of its fused local context below.`;
17208
+ }
17209
+ return `${core}${alsoNote}`;
17210
+ }
17211
+ async function askGraph(graph, question, opts = {}) {
17212
+ const now = opts.now ?? Date.now();
17213
+ const maxNodes = opts.maxNodes ?? DEFAULT_MAX_NODES;
17214
+ const intent = classifyIntent(question);
17215
+ const matched = await resolveEntities(graph, question, opts.searchIndex, maxNodes);
17216
+ const primary = matched[0]?.nodeId;
17217
+ const sections = [];
17218
+ let scope;
17219
+ if (primary) {
17220
+ scope = "node";
17221
+ for (const kind of SECTION_ORDER[intent]) {
17222
+ const s = buildSection(kind, graph, primary, opts.incidents, now);
17223
+ if (s && s.facts.length > 0) sections.push(s);
17224
+ }
17225
+ } else {
17226
+ const global = buildGlobalSections(intent, graph, opts.incidents);
17227
+ if (global) {
17228
+ scope = "global";
17229
+ for (const s of global) if (s.facts.length > 0) sections.push(s);
17230
+ }
17231
+ }
17232
+ const answer = summarize(question, intent, matched, primary, sections, scope);
17233
+ const provSet = /* @__PURE__ */ new Set();
17234
+ for (const s of sections) for (const f of s.facts) if (f.provenance) provSet.add(f.provenance);
17235
+ const confidence = sections[0]?.facts[0]?.confidence;
17236
+ return import_types59.AskResultSchema.parse({
17237
+ question,
17238
+ intent,
17239
+ matched,
17240
+ ...primary ? { primaryNode: primary } : {},
17241
+ ...scope ? { scope } : {},
17242
+ sections,
17243
+ answer,
17244
+ ...confidence !== void 0 ? { confidence } : {},
17245
+ provenance: [...provSet]
17246
+ });
17247
+ }
17248
+
16565
17249
  // src/diff.ts
16566
17250
  init_cjs_shims();
16567
17251
  var import_node_fs36 = require("fs");
@@ -16696,7 +17380,7 @@ init_cjs_shims();
16696
17380
  var import_node_fs37 = require("fs");
16697
17381
  var import_node_os3 = __toESM(require("os"), 1);
16698
17382
  var import_node_path72 = __toESM(require("path"), 1);
16699
- var import_types59 = require("@neat.is/types");
17383
+ var import_types60 = require("@neat.is/types");
16700
17384
  var LOCK_TIMEOUT_MS = 5e3;
16701
17385
  var LOCK_RETRY_MS = 50;
16702
17386
  function neatHome() {
@@ -16906,10 +17590,10 @@ async function readRegistry() {
16906
17590
  throw err;
16907
17591
  }
16908
17592
  const parsed = JSON.parse(raw);
16909
- return import_types59.RegistryFileSchema.parse(parsed);
17593
+ return import_types60.RegistryFileSchema.parse(parsed);
16910
17594
  }
16911
17595
  async function writeRegistry(reg) {
16912
- const validated = import_types59.RegistryFileSchema.parse(reg);
17596
+ const validated = import_types60.RegistryFileSchema.parse(reg);
16913
17597
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
16914
17598
  }
16915
17599
  var ProjectNameCollisionError = class extends Error {
@@ -17310,15 +17994,15 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
17310
17994
 
17311
17995
  // src/connectors/index.ts
17312
17996
  init_cjs_shims();
17313
- var import_types60 = require("@neat.is/types");
17997
+ var import_types61 = require("@neat.is/types");
17314
17998
  var NO_ENV = "unknown";
17315
17999
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
17316
18000
  if (!graph.hasNode(targetNodeId)) return void 0;
17317
18001
  const sites = [];
17318
18002
  for (const edgeId of graph.inboundEdges(targetNodeId)) {
17319
18003
  const edge = graph.getEdgeAttributes(edgeId);
17320
- if (edge.provenance !== import_types60.Provenance.EXTRACTED) continue;
17321
- const parsed = (0, import_types60.parseFileId)(edge.source);
18004
+ if (edge.provenance !== import_types61.Provenance.EXTRACTED) continue;
18005
+ const parsed = (0, import_types61.parseFileId)(edge.source);
17322
18006
  if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
17323
18007
  const site = { relPath: edge.evidence.file };
17324
18008
  if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
@@ -17329,7 +18013,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
17329
18013
  function routeCallSiteFor(graph, targetNodeId) {
17330
18014
  if (!graph.hasNode(targetNodeId)) return void 0;
17331
18015
  const attrs = graph.getNodeAttributes(targetNodeId);
17332
- if (attrs.type !== import_types60.NodeType.RouteNode || !attrs.path) return void 0;
18016
+ if (attrs.type !== import_types61.NodeType.RouteNode || !attrs.path) return void 0;
17333
18017
  const site = { relPath: attrs.path };
17334
18018
  if (attrs.line !== void 0) site.line = attrs.line;
17335
18019
  return site;
@@ -17941,23 +18625,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
17941
18625
 
17942
18626
  // src/connectors/supabase/resolve.ts
17943
18627
  init_cjs_shims();
17944
- var import_types62 = require("@neat.is/types");
18628
+ var import_types63 = require("@neat.is/types");
17945
18629
  function createSupabaseResolveTarget(graph, config) {
17946
18630
  return (signal, _ctx) => {
17947
18631
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
17948
18632
  return null;
17949
18633
  }
17950
- const subResourceId = (0, import_types62.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
18634
+ const subResourceId = (0, import_types63.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
17951
18635
  if (graph.hasNode(subResourceId)) {
17952
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types62.EdgeType.CALLS };
18636
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
17953
18637
  }
17954
- const bareResourceId = (0, import_types62.infraId)(signal.targetKind, signal.targetName);
18638
+ const bareResourceId = (0, import_types63.infraId)(signal.targetKind, signal.targetName);
17955
18639
  if (graph.hasNode(bareResourceId)) {
17956
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types62.EdgeType.CALLS };
18640
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
17957
18641
  }
17958
- const projectLevelId = (0, import_types62.infraId)("supabase", config.nodeRef);
18642
+ const projectLevelId = (0, import_types63.infraId)("supabase", config.nodeRef);
17959
18643
  if (graph.hasNode(projectLevelId)) {
17960
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types62.EdgeType.CALLS };
18644
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
17961
18645
  }
17962
18646
  return null;
17963
18647
  };
@@ -18050,7 +18734,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
18050
18734
 
18051
18735
  // src/connectors/railway/index.ts
18052
18736
  init_cjs_shims();
18053
- var import_types66 = require("@neat.is/types");
18737
+ var import_types67 = require("@neat.is/types");
18054
18738
 
18055
18739
  // src/connectors/railway/client.ts
18056
18740
  init_cjs_shims();
@@ -18201,7 +18885,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
18201
18885
  const out = [];
18202
18886
  graph.forEachNode((_id, attrs) => {
18203
18887
  const node = attrs;
18204
- if (node.type !== import_types66.NodeType.RouteNode) return;
18888
+ if (node.type !== import_types67.NodeType.RouteNode) return;
18205
18889
  const route = attrs;
18206
18890
  if (route.service !== serviceName) return;
18207
18891
  out.push({
@@ -18305,12 +18989,12 @@ function createRailwayResolveTarget(config) {
18305
18989
  const serviceName = config.serviceNameById[config.serviceId];
18306
18990
  if (!serviceName) return null;
18307
18991
  if (signal.targetKind === ROUTE_TARGET_KIND) {
18308
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types66.EdgeType.CALLS };
18992
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types67.EdgeType.CALLS };
18309
18993
  }
18310
18994
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
18311
18995
  const peerName = config.serviceNameById[signal.targetName];
18312
18996
  if (!peerName) return null;
18313
- return { targetNodeId: (0, import_types66.serviceId)(peerName), serviceName, edgeType: import_types66.EdgeType.CONNECTS_TO };
18997
+ return { targetNodeId: (0, import_types67.serviceId)(peerName), serviceName, edgeType: import_types67.EdgeType.CONNECTS_TO };
18314
18998
  }
18315
18999
  return null;
18316
19000
  };
@@ -18498,7 +19182,7 @@ function mapLogEntriesToSignals(entries) {
18498
19182
 
18499
19183
  // src/connectors/firebase/resolve.ts
18500
19184
  init_cjs_shims();
18501
- var import_types67 = require("@neat.is/types");
19185
+ var import_types68 = require("@neat.is/types");
18502
19186
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
18503
19187
  switch (resourceType) {
18504
19188
  case "cloud_function":
@@ -18513,7 +19197,7 @@ function routeEntriesFor(graph, serviceName) {
18513
19197
  const entries = [];
18514
19198
  graph.forEachNode((_id, attrs) => {
18515
19199
  const node = attrs;
18516
- if (node.type !== import_types67.NodeType.RouteNode) return;
19200
+ if (node.type !== import_types68.NodeType.RouteNode) return;
18517
19201
  const route = attrs;
18518
19202
  if (route.service !== serviceName) return;
18519
19203
  entries.push({
@@ -18545,7 +19229,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
18545
19229
  return {
18546
19230
  targetNodeId: match.routeNodeId,
18547
19231
  serviceName,
18548
- edgeType: import_types67.EdgeType.CALLS
19232
+ edgeType: import_types68.EdgeType.CALLS
18549
19233
  };
18550
19234
  };
18551
19235
  }
@@ -18572,7 +19256,7 @@ init_cjs_shims();
18572
19256
 
18573
19257
  // src/connectors/cloudflare/connector.ts
18574
19258
  init_cjs_shims();
18575
- var import_types69 = require("@neat.is/types");
19259
+ var import_types70 = require("@neat.is/types");
18576
19260
 
18577
19261
  // src/connectors/cloudflare/client.ts
18578
19262
  init_cjs_shims();
@@ -18736,7 +19420,7 @@ function findTaggedWorkerFileNode(graph, workerName) {
18736
19420
  graph.forEachNode((id, attrs) => {
18737
19421
  if (found) return;
18738
19422
  const a = attrs;
18739
- if (a.type === import_types69.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
19423
+ if (a.type === import_types70.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
18740
19424
  found = id;
18741
19425
  }
18742
19426
  });
@@ -18748,7 +19432,7 @@ function findMatchingRouteNode(graph, serviceName, method, path76) {
18748
19432
  graph.forEachNode((id, attrs) => {
18749
19433
  if (found) return;
18750
19434
  const a = attrs;
18751
- if (a.type !== import_types69.NodeType.RouteNode || a.service !== serviceName) return;
19435
+ if (a.type !== import_types70.NodeType.RouteNode || a.service !== serviceName) return;
18752
19436
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
18753
19437
  const routeMethod = (a.method ?? "").toUpperCase();
18754
19438
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -18767,11 +19451,11 @@ function createCloudflareResolveTarget(config, graph) {
18767
19451
  };
18768
19452
  const mapping = config.workers?.[scriptName];
18769
19453
  if (mapping) {
18770
- const wholeFileId = (0, import_types69.fileId)(mapping.service, mapping.entryFile);
19454
+ const wholeFileId = (0, import_types70.fileId)(mapping.service, mapping.entryFile);
18771
19455
  return {
18772
19456
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
18773
19457
  serviceName: mapping.service,
18774
- edgeType: import_types69.EdgeType.CALLS
19458
+ edgeType: import_types70.EdgeType.CALLS
18775
19459
  };
18776
19460
  }
18777
19461
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -18780,13 +19464,13 @@ function createCloudflareResolveTarget(config, graph) {
18780
19464
  return {
18781
19465
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
18782
19466
  serviceName: fileNode.service,
18783
- edgeType: import_types69.EdgeType.CALLS
19467
+ edgeType: import_types70.EdgeType.CALLS
18784
19468
  };
18785
19469
  }
18786
19470
  return {
18787
- targetNodeId: (0, import_types69.infraId)("cloudflare-worker", scriptName),
19471
+ targetNodeId: (0, import_types70.infraId)("cloudflare-worker", scriptName),
18788
19472
  serviceName: scriptName,
18789
- edgeType: import_types69.EdgeType.CALLS,
19473
+ edgeType: import_types70.EdgeType.CALLS,
18790
19474
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
18791
19475
  };
18792
19476
  };
@@ -18982,14 +19666,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
18982
19666
 
18983
19667
  // src/connectors/neon/resolve.ts
18984
19668
  init_cjs_shims();
18985
- var import_types73 = require("@neat.is/types");
19669
+ var import_types74 = require("@neat.is/types");
18986
19670
  function createNeonResolveTarget(config) {
18987
19671
  return (signal) => {
18988
19672
  if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
18989
19673
  return {
18990
- targetNodeId: (0, import_types73.infraId)("sql-table", signal.targetName),
19674
+ targetNodeId: (0, import_types74.infraId)("sql-table", signal.targetName),
18991
19675
  serviceName: config.serviceName,
18992
- edgeType: import_types73.EdgeType.CALLS,
19676
+ edgeType: import_types74.EdgeType.CALLS,
18993
19677
  ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
18994
19678
  };
18995
19679
  };
@@ -19170,14 +19854,14 @@ function mapLogEntriesToSignals2(entries) {
19170
19854
 
19171
19855
  // src/connectors/cloud-run/resolve.ts
19172
19856
  init_cjs_shims();
19173
- var import_types77 = require("@neat.is/types");
19857
+ var import_types78 = require("@neat.is/types");
19174
19858
  var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
19175
19859
  function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
19176
19860
  let found = null;
19177
19861
  graph.forEachNode((_id, attrs) => {
19178
19862
  if (found) return;
19179
19863
  const node = attrs;
19180
- if (node.type !== import_types77.NodeType.RouteNode) return;
19864
+ if (node.type !== import_types78.NodeType.RouteNode) return;
19181
19865
  const route = attrs;
19182
19866
  if (route.service !== serviceName || !route.pathTemplate) return;
19183
19867
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -19202,13 +19886,13 @@ function createCloudRunResolveTarget(graph, config) {
19202
19886
  normalizePathTemplate(path76)
19203
19887
  );
19204
19888
  if (routeNodeId) {
19205
- return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types77.EdgeType.CALLS };
19889
+ return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types78.EdgeType.CALLS };
19206
19890
  }
19207
19891
  }
19208
19892
  return {
19209
- targetNodeId: (0, import_types77.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
19893
+ targetNodeId: (0, import_types78.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
19210
19894
  serviceName: mappedService ?? gcpServiceName,
19211
- edgeType: import_types77.EdgeType.CALLS,
19895
+ edgeType: import_types78.EdgeType.CALLS,
19212
19896
  ensureInfraNode: {
19213
19897
  kind: CLOUD_RUN_SERVICE_INFRA_KIND,
19214
19898
  name: gcpServiceName,
@@ -19249,7 +19933,7 @@ function createCloudRunConnector(graph, config = {}) {
19249
19933
 
19250
19934
  // src/connectors/render/index.ts
19251
19935
  init_cjs_shims();
19252
- var import_types80 = require("@neat.is/types");
19936
+ var import_types81 = require("@neat.is/types");
19253
19937
 
19254
19938
  // src/connectors/render/types.ts
19255
19939
  init_cjs_shims();
@@ -19327,7 +20011,7 @@ function buildRenderRouteIndex(graph, serviceName) {
19327
20011
  const out = [];
19328
20012
  graph.forEachNode((_id, attrs) => {
19329
20013
  const node = attrs;
19330
- if (node.type !== import_types80.NodeType.RouteNode) return;
20014
+ if (node.type !== import_types81.NodeType.RouteNode) return;
19331
20015
  const route = attrs;
19332
20016
  if (route.service !== serviceName) return;
19333
20017
  out.push({
@@ -19412,7 +20096,7 @@ function mapRenderRequestLogsToSignals(entries, routeIndex) {
19412
20096
  function createRenderResolveTarget(config) {
19413
20097
  return (signal) => {
19414
20098
  if (signal.targetKind === ROUTE_TARGET_KIND2) {
19415
- return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types80.EdgeType.CALLS };
20099
+ return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types81.EdgeType.CALLS };
19416
20100
  }
19417
20101
  return null;
19418
20102
  };
@@ -19550,21 +20234,21 @@ function mapInsightsToSignals(rows, observedAtIso) {
19550
20234
 
19551
20235
  // src/connectors/planetscale/resolve.ts
19552
20236
  init_cjs_shims();
19553
- var import_types84 = require("@neat.is/types");
20237
+ var import_types85 = require("@neat.is/types");
19554
20238
  var PLANETSCALE_DATABASE_KIND = "planetscale-database";
19555
20239
  function createPlanetscaleResolveTarget(graph, config) {
19556
20240
  const databaseName = `${config.organization}/${config.database}`;
19557
20241
  return (signal, _ctx) => {
19558
20242
  if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
19559
- const tableId = (0, import_types84.infraId)("sql-table", signal.targetName);
20243
+ const tableId = (0, import_types85.infraId)("sql-table", signal.targetName);
19560
20244
  if (graph.hasNode(tableId)) {
19561
- return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types84.EdgeType.CALLS };
20245
+ return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types85.EdgeType.CALLS };
19562
20246
  }
19563
- const providerId = (0, import_types84.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
20247
+ const providerId = (0, import_types85.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
19564
20248
  return {
19565
20249
  targetNodeId: providerId,
19566
20250
  serviceName: config.serviceName,
19567
- edgeType: import_types84.EdgeType.CALLS,
20251
+ edgeType: import_types85.EdgeType.CALLS,
19568
20252
  ensureInfraNode: { kind: PLANETSCALE_DATABASE_KIND, name: databaseName, provider: "planetscale" }
19569
20253
  };
19570
20254
  };
@@ -19829,7 +20513,7 @@ function mapBuildsToSignals(builds, serviceName) {
19829
20513
 
19830
20514
  // src/connectors/eas/resolve.ts
19831
20515
  init_cjs_shims();
19832
- var import_types89 = require("@neat.is/types");
20516
+ var import_types90 = require("@neat.is/types");
19833
20517
  var NO_ENV2 = "unknown";
19834
20518
  var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
19835
20519
  var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
@@ -19845,8 +20529,8 @@ function configBasenamesForPhase(phase) {
19845
20529
  function configNodeService(graph, configNodeId) {
19846
20530
  for (const edgeId of graph.inboundEdges(configNodeId)) {
19847
20531
  const edge = graph.getEdgeAttributes(edgeId);
19848
- if (edge.type !== import_types89.EdgeType.CONFIGURED_BY) continue;
19849
- const parsed = (0, import_types89.parseFileId)(edge.source);
20532
+ if (edge.type !== import_types90.EdgeType.CONFIGURED_BY) continue;
20533
+ const parsed = (0, import_types90.parseFileId)(edge.source);
19850
20534
  if (parsed) return parsed.service;
19851
20535
  }
19852
20536
  return null;
@@ -19857,7 +20541,7 @@ function findConfigNode(graph, basenames, serviceName) {
19857
20541
  graph.forEachNode((id, attrs) => {
19858
20542
  if (scoped) return;
19859
20543
  const node = attrs;
19860
- if (node.type !== import_types89.NodeType.ConfigNode) return;
20544
+ if (node.type !== import_types90.NodeType.ConfigNode) return;
19861
20545
  if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
19862
20546
  if (anyMatch === null) anyMatch = id;
19863
20547
  if (configNodeService(graph, id) === serviceName) scoped = id;
@@ -19874,13 +20558,13 @@ function createEasResolveTarget(graph) {
19874
20558
  if (basenames.length > 0) {
19875
20559
  const configNodeId = findConfigNode(graph, basenames, serviceName);
19876
20560
  if (configNodeId) {
19877
- return { targetNodeId: configNodeId, serviceName, edgeType: import_types89.EdgeType.CALLS };
20561
+ return { targetNodeId: configNodeId, serviceName, edgeType: import_types90.EdgeType.CALLS };
19878
20562
  }
19879
20563
  }
19880
20564
  return {
19881
20565
  targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV2),
19882
20566
  serviceName,
19883
- edgeType: import_types89.EdgeType.CALLS
20567
+ edgeType: import_types90.EdgeType.CALLS
19884
20568
  };
19885
20569
  };
19886
20570
  }
@@ -20605,11 +21289,11 @@ function registerRoutes(scope, ctx) {
20605
21289
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
20606
21290
  const parsed = [];
20607
21291
  for (const c of candidates) {
20608
- const r = import_types92.DivergenceTypeSchema.safeParse(c);
21292
+ const r = import_types93.DivergenceTypeSchema.safeParse(c);
20609
21293
  if (!r.success) {
20610
21294
  return reply.code(400).send({
20611
21295
  error: `unknown divergence type "${c}"`,
20612
- allowed: import_types92.DivergenceTypeSchema.options
21296
+ allowed: import_types93.DivergenceTypeSchema.options
20613
21297
  });
20614
21298
  }
20615
21299
  parsed.push(r.data);
@@ -20851,6 +21535,18 @@ function registerRoutes(scope, ctx) {
20851
21535
  matches: matches.slice(0, safeLimit)
20852
21536
  };
20853
21537
  });
21538
+ scope.get("/graph/ask", async (req, reply) => {
21539
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
21540
+ if (!proj) return;
21541
+ const question = (req.query.q ?? "").trim();
21542
+ if (!question) return reply.code(400).send({ error: "query parameter `q` is required" });
21543
+ const epath = errorsPathFor(proj);
21544
+ const incidents = epath ? await readErrorEvents(epath) : [];
21545
+ return askGraph(proj.graph, question, {
21546
+ ...proj.searchIndex ? { searchIndex: proj.searchIndex } : {},
21547
+ incidents
21548
+ });
21549
+ });
20854
21550
  scope.get(
20855
21551
  "/graph/diff",
20856
21552
  async (req, reply) => {
@@ -20951,7 +21647,7 @@ function registerRoutes(scope, ctx) {
20951
21647
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
20952
21648
  let violations = await log.readAll();
20953
21649
  if (req.query.severity) {
20954
- const sev = import_types92.PolicySeveritySchema.safeParse(req.query.severity);
21650
+ const sev = import_types93.PolicySeveritySchema.safeParse(req.query.severity);
20955
21651
  if (!sev.success) {
20956
21652
  return reply.code(400).send({
20957
21653
  error: "invalid severity",
@@ -20990,7 +21686,7 @@ function registerRoutes(scope, ctx) {
20990
21686
  scope.post("/policies/check", async (req, reply) => {
20991
21687
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
20992
21688
  if (!proj) return;
20993
- const parsed = import_types92.PoliciesCheckBodySchema.safeParse(req.body ?? {});
21689
+ const parsed = import_types93.PoliciesCheckBodySchema.safeParse(req.body ?? {});
20994
21690
  if (!parsed.success) {
20995
21691
  return reply.code(400).send({
20996
21692
  error: "invalid /policies/check body",
@@ -21339,7 +22035,7 @@ function unroutedErrorsPath(neatHome3) {
21339
22035
  }
21340
22036
 
21341
22037
  // src/daemon.ts
21342
- var import_types93 = require("@neat.is/types");
22038
+ var import_types94 = require("@neat.is/types");
21343
22039
  function daemonJsonPath(scanPath) {
21344
22040
  return import_node_path75.default.join(scanPath, "neat-out", "daemon.json");
21345
22041
  }
@@ -21451,8 +22147,8 @@ function isTokenContained(needle, haystack) {
21451
22147
  const n = needle.toLowerCase();
21452
22148
  const h = haystack.toLowerCase();
21453
22149
  if (!h.includes(n)) return false;
21454
- const tokens = h.split(/[-_]/);
21455
- return tokens.includes(n);
22150
+ const tokens2 = h.split(/[-_]/);
22151
+ return tokens2.includes(n);
21456
22152
  }
21457
22153
  function serviceNameMatchesProject(serviceName, project) {
21458
22154
  if (serviceName.toLowerCase() === project.toLowerCase()) return true;
@@ -21464,7 +22160,7 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
21464
22160
  if (!serviceName) return true;
21465
22161
  if (serviceNameMatchesProject(serviceName, project)) return true;
21466
22162
  return graph.someNode(
21467
- (_id, attrs) => attrs.type === import_types93.NodeType.ServiceNode && attrs.name === serviceName
22163
+ (_id, attrs) => attrs.type === import_types94.NodeType.ServiceNode && attrs.name === serviceName
21468
22164
  );
21469
22165
  }
21470
22166
  async function bootstrapProject(entry, connectors = [], neatHome3) {