@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/neatd.cjs CHANGED
@@ -1355,7 +1355,7 @@ function databaseRootCauseShape(graph, origin, walk10) {
1355
1355
  for (const id of walk10.path) {
1356
1356
  const owner = resolveOwningService(graph, id);
1357
1357
  if (!owner) continue;
1358
- const { id: serviceId15, svc } = owner;
1358
+ const { id: serviceId16, svc } = owner;
1359
1359
  const deps = svc.dependencies ?? {};
1360
1360
  for (const pair of candidatePairs) {
1361
1361
  const declared = deps[pair.driver];
@@ -1368,7 +1368,7 @@ function databaseRootCauseShape(graph, origin, walk10) {
1368
1368
  );
1369
1369
  if (!result.compatible) {
1370
1370
  return {
1371
- rootCauseNode: serviceId15,
1371
+ rootCauseNode: serviceId16,
1372
1372
  rootCauseReason: result.reason ?? "incompatible driver",
1373
1373
  ...result.minDriverVersion ? {
1374
1374
  fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
@@ -1383,7 +1383,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
1383
1383
  for (const id of walk10.path) {
1384
1384
  const owner = resolveOwningService(graph, id);
1385
1385
  if (!owner) continue;
1386
- const { id: serviceId15, svc } = owner;
1386
+ const { id: serviceId16, svc } = owner;
1387
1387
  const deps = svc.dependencies ?? {};
1388
1388
  const serviceNodeEngine = svc.nodeEngine;
1389
1389
  for (const constraint of nodeEngineConstraints()) {
@@ -1392,7 +1392,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
1392
1392
  const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
1393
1393
  if (!result.compatible && result.reason) {
1394
1394
  return {
1395
- rootCauseNode: serviceId15,
1395
+ rootCauseNode: serviceId16,
1396
1396
  rootCauseReason: result.reason,
1397
1397
  ...result.requiredNodeVersion ? {
1398
1398
  fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
@@ -1407,7 +1407,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
1407
1407
  const result = checkPackageConflict(conflict, declared, requiredDeclared);
1408
1408
  if (!result.compatible && result.reason) {
1409
1409
  return {
1410
- rootCauseNode: serviceId15,
1410
+ rootCauseNode: serviceId16,
1411
1411
  rootCauseReason: result.reason,
1412
1412
  fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
1413
1413
  };
@@ -1512,9 +1512,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
1512
1512
  function isFailingCallEdge(e) {
1513
1513
  return e.type === import_types.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
1514
1514
  }
1515
- function callSourcesForService(graph, serviceId15) {
1516
- const ids = [serviceId15];
1517
- for (const edgeId of graph.outboundEdges(serviceId15)) {
1515
+ function callSourcesForService(graph, serviceId16) {
1516
+ const ids = [serviceId16];
1517
+ for (const edgeId of graph.outboundEdges(serviceId16)) {
1518
1518
  const e = graph.getEdgeAttributes(edgeId);
1519
1519
  if (e.type !== import_types.EdgeType.CONTAINS) continue;
1520
1520
  const tgt = graph.getNodeAttributes(e.target);
@@ -1538,9 +1538,9 @@ function failingCallDominates(e, id, curEdge, curId) {
1538
1538
  }
1539
1539
  return id < curId;
1540
1540
  }
1541
- function dominantFailingCall(graph, serviceId15, visited) {
1541
+ function dominantFailingCall(graph, serviceId16, visited) {
1542
1542
  let best = null;
1543
- for (const src of callSourcesForService(graph, serviceId15)) {
1543
+ for (const src of callSourcesForService(graph, serviceId16)) {
1544
1544
  for (const edgeId of graph.outboundEdges(src)) {
1545
1545
  const e = graph.getEdgeAttributes(edgeId);
1546
1546
  if (!isFailingCallEdge(e)) continue;
@@ -1579,9 +1579,9 @@ function staleCallDominates(e, id, curEdge, curId) {
1579
1579
  if (ev !== cv) return ev > cv;
1580
1580
  return id < curId;
1581
1581
  }
1582
- function dominantStaleCall(graph, serviceId15, visited) {
1582
+ function dominantStaleCall(graph, serviceId16, visited) {
1583
1583
  const bestByCallee = /* @__PURE__ */ new Map();
1584
- for (const src of callSourcesForService(graph, serviceId15)) {
1584
+ for (const src of callSourcesForService(graph, serviceId16)) {
1585
1585
  for (const edgeId of graph.outboundEdges(src)) {
1586
1586
  const e = graph.getEdgeAttributes(edgeId);
1587
1587
  if (e.type !== import_types.EdgeType.CALLS) continue;
@@ -4780,11 +4780,11 @@ function actixAttributeRoute(attrItem, out) {
4780
4780
  if (!nameNode) return;
4781
4781
  const macro = nameNode.type === "identifier" ? nameNode.text : nameNode.type === "scoped_identifier" ? nameNode.childForFieldName("name")?.text ?? null : null;
4782
4782
  if (!macro) return;
4783
- const tokens = attr.childForFieldName("arguments");
4784
- if (!tokens || tokens.type !== "token_tree") return;
4783
+ const tokens2 = attr.childForFieldName("arguments");
4784
+ if (!tokens2 || tokens2.type !== "token_tree") return;
4785
4785
  const strings = [];
4786
- for (let i = 0; i < tokens.namedChildCount; i++) {
4787
- const s = rustStringContent(tokens.namedChild(i));
4786
+ for (let i = 0; i < tokens2.namedChildCount; i++) {
4787
+ const s = rustStringContent(tokens2.namedChild(i));
4788
4788
  if (s !== null) strings.push(s);
4789
4789
  }
4790
4790
  const pathStr = strings[0];
@@ -6678,29 +6678,29 @@ function promoteFrontierNodes(graph, opts = {}) {
6678
6678
  toPromote.push({ frontierId: id, serviceId: target });
6679
6679
  });
6680
6680
  let promoted = 0;
6681
- for (const { frontierId: frontierId2, serviceId: serviceId15 } of toPromote) {
6681
+ for (const { frontierId: frontierId2, serviceId: serviceId16 } of toPromote) {
6682
6682
  if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
6683
6683
  const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
6684
6684
  if (!gate.allowed) {
6685
6685
  continue;
6686
6686
  }
6687
6687
  }
6688
- rewireFrontierEdges(graph, frontierId2, serviceId15);
6688
+ rewireFrontierEdges(graph, frontierId2, serviceId16);
6689
6689
  graph.dropNode(frontierId2);
6690
6690
  promoted++;
6691
6691
  }
6692
6692
  return promoted;
6693
6693
  }
6694
- function rewireFrontierEdges(graph, frontierId2, serviceId15) {
6694
+ function rewireFrontierEdges(graph, frontierId2, serviceId16) {
6695
6695
  const inbound = [...graph.inboundEdges(frontierId2)];
6696
6696
  const outbound = [...graph.outboundEdges(frontierId2)];
6697
6697
  for (const edgeId of inbound) {
6698
6698
  const edge = graph.getEdgeAttributes(edgeId);
6699
- rebuildEdge(graph, edge, edge.source, serviceId15, edgeId);
6699
+ rebuildEdge(graph, edge, edge.source, serviceId16, edgeId);
6700
6700
  }
6701
6701
  for (const edgeId of outbound) {
6702
6702
  const edge = graph.getEdgeAttributes(edgeId);
6703
- rebuildEdge(graph, edge, serviceId15, edge.target, edgeId);
6703
+ rebuildEdge(graph, edge, serviceId16, edge.target, edgeId);
6704
6704
  }
6705
6705
  }
6706
6706
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
@@ -7812,9 +7812,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
7812
7812
  "StatefulSet",
7813
7813
  "DaemonSet"
7814
7814
  ]);
7815
- function addAliases(graph, serviceId15, candidates) {
7816
- if (!graph.hasNode(serviceId15)) return;
7817
- const node = graph.getNodeAttributes(serviceId15);
7815
+ function addAliases(graph, serviceId16, candidates) {
7816
+ if (!graph.hasNode(serviceId16)) return;
7817
+ const node = graph.getNodeAttributes(serviceId16);
7818
7818
  if (node.type !== import_types19.NodeType.ServiceNode) return;
7819
7819
  const set = new Set(node.aliases ?? []);
7820
7820
  for (const c of candidates) {
@@ -7824,7 +7824,7 @@ function addAliases(graph, serviceId15, candidates) {
7824
7824
  }
7825
7825
  if (set.size === 0) return;
7826
7826
  const updated = { ...node, aliases: [...set].sort() };
7827
- graph.replaceNodeAttributes(serviceId15, updated);
7827
+ graph.replaceNodeAttributes(serviceId16, updated);
7828
7828
  }
7829
7829
  function indexServicesByName(services) {
7830
7830
  const map = /* @__PURE__ */ new Map();
@@ -7857,12 +7857,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
7857
7857
  }
7858
7858
  if (!compose?.services) return;
7859
7859
  for (const [composeName, svc] of Object.entries(compose.services)) {
7860
- const serviceId15 = serviceIndex.get(composeName);
7861
- if (!serviceId15) continue;
7860
+ const serviceId16 = serviceIndex.get(composeName);
7861
+ if (!serviceId16) continue;
7862
7862
  const aliases = /* @__PURE__ */ new Set([composeName]);
7863
7863
  if (svc.container_name) aliases.add(svc.container_name);
7864
7864
  if (svc.hostname) aliases.add(svc.hostname);
7865
- addAliases(graph, serviceId15, aliases);
7865
+ addAliases(graph, serviceId16, aliases);
7866
7866
  }
7867
7867
  }
7868
7868
  var LABEL_KEYS = /* @__PURE__ */ new Set([
@@ -9852,12 +9852,12 @@ async function resolveConfigs(literals, keys, serviceDir, looksLike, parse11) {
9852
9852
  const value = await interpolateEnvRefs(raw, serviceDir);
9853
9853
  if (!looksLike(value)) continue;
9854
9854
  const parsed = parse11(value);
9855
- if (parsed) out.push(parsed);
9855
+ if (parsed) out.push({ ...parsed, hostSource: "config" });
9856
9856
  }
9857
9857
  for (const lit of literals) {
9858
9858
  if (!looksLike(lit)) continue;
9859
9859
  const parsed = parse11(lit);
9860
- if (parsed) out.push(parsed);
9860
+ if (parsed) out.push({ ...parsed, hostSource: "literal" });
9861
9861
  }
9862
9862
  return out;
9863
9863
  }
@@ -10126,7 +10126,10 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
10126
10126
  type: import_types23.EdgeType.CONNECTS_TO,
10127
10127
  provenance: import_types23.Provenance.EXTRACTED,
10128
10128
  confidence: (0, import_types23.confidenceForExtracted)("structural"),
10129
- evidence: { file: evidenceFile }
10129
+ // Carry how the host was recovered (ADR-213) so the divergence ranker
10130
+ // can tell a real declared store from a hardcoded fault-injection /
10131
+ // flag-gated probe. Only set when the parser distinguished the two.
10132
+ evidence: config.hostSource ? { file: evidenceFile, hostSource: config.hostSource } : { file: evidenceFile }
10130
10133
  };
10131
10134
  if (!graph.hasEdge(edge.id)) {
10132
10135
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -15851,7 +15854,7 @@ var Projects = class {
15851
15854
  init_cjs_shims();
15852
15855
  var import_fastify2 = __toESM(require("fastify"), 1);
15853
15856
  var import_cors = __toESM(require("@fastify/cors"), 1);
15854
- var import_types92 = require("@neat.is/types");
15857
+ var import_types93 = require("@neat.is/types");
15855
15858
 
15856
15859
  // src/extend/index.ts
15857
15860
  init_cjs_shims();
@@ -16455,6 +16458,53 @@ function detectColumnDrift(node) {
16455
16458
  function involvesNode(d, nodeId) {
16456
16459
  return d.source === nodeId || d.target === nodeId;
16457
16460
  }
16461
+ function datastoreEverObserved(graph, nodeId) {
16462
+ if (!graph.hasNode(nodeId)) return false;
16463
+ const n = graph.getNodeAttributes(nodeId);
16464
+ if (n.type === import_types58.NodeType.DatabaseNode) {
16465
+ const via = n.discoveredVia;
16466
+ if (via === "otel" || via === "merged") return true;
16467
+ }
16468
+ for (const edgeId of graph.inboundEdges(nodeId)) {
16469
+ const e = graph.getEdgeAttributes(edgeId);
16470
+ if (e.provenance === import_types58.Provenance.OBSERVED || e.provenance === import_types58.Provenance.STALE) return true;
16471
+ }
16472
+ return false;
16473
+ }
16474
+ function serviceHasObservedSameEngineStore(graph, buckets2, serviceId16, engine, excludeTarget) {
16475
+ for (const bucket of buckets2.values()) {
16476
+ if (bucket.type !== import_types58.EdgeType.CONNECTS_TO) continue;
16477
+ if (bucket.source !== serviceId16) continue;
16478
+ if (bucket.target === excludeTarget) continue;
16479
+ if (!graph.hasNode(bucket.target)) continue;
16480
+ const target = graph.getNodeAttributes(bucket.target);
16481
+ if (target.type !== import_types58.NodeType.DatabaseNode) continue;
16482
+ if (target.engine !== engine) continue;
16483
+ if (bucket.observed || datastoreEverObserved(graph, bucket.target)) return true;
16484
+ }
16485
+ return false;
16486
+ }
16487
+ var DEAD_CODE_PROBE_CONFIDENCE = 0.1;
16488
+ function dampenDeadCodeProbes(graph, buckets2, all) {
16489
+ return all.map((d) => {
16490
+ if (d.type !== "missing-observed") return d;
16491
+ if (!d.extracted || d.edgeType !== import_types58.EdgeType.CONNECTS_TO) return d;
16492
+ if (!graph.hasNode(d.target)) return d;
16493
+ const target = graph.getNodeAttributes(d.target);
16494
+ if (target.type !== import_types58.NodeType.DatabaseNode) return d;
16495
+ if (d.extracted.evidence?.hostSource !== "literal") return d;
16496
+ if (datastoreEverObserved(graph, d.target)) return d;
16497
+ const engine = target.engine;
16498
+ if (!serviceHasObservedSameEngineStore(graph, buckets2, d.source, engine, d.target)) return d;
16499
+ const host = target.host ?? target.name;
16500
+ return {
16501
+ ...d,
16502
+ confidence: Math.min(d.confidence, DEAD_CODE_PROBE_CONFIDENCE),
16503
+ 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.`,
16504
+ 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."
16505
+ };
16506
+ });
16507
+ }
16458
16508
  function suppressHostMismatchHalves(all) {
16459
16509
  const observedHalf = /* @__PURE__ */ new Set();
16460
16510
  const declaredHalf = /* @__PURE__ */ new Set();
@@ -16491,7 +16541,8 @@ function computeDivergences(graph, opts = {}) {
16491
16541
  }
16492
16542
  });
16493
16543
  const reconciled = suppressHostMismatchHalves(all);
16494
- let filtered = reconciled;
16544
+ const dampened = dampenDeadCodeProbes(graph, buckets2, reconciled);
16545
+ let filtered = dampened;
16495
16546
  if (opts.type) {
16496
16547
  const allowed = opts.type;
16497
16548
  filtered = filtered.filter((d) => allowed.has(d.type));
@@ -16574,6 +16625,639 @@ function queryLogEntries(opts) {
16574
16625
  return merged;
16575
16626
  }
16576
16627
 
16628
+ // src/ask.ts
16629
+ init_cjs_shims();
16630
+ var import_types59 = require("@neat.is/types");
16631
+ var DEFAULT_MAX_NODES = 3;
16632
+ var MAX_FACTS_PER_SECTION = 6;
16633
+ var INTENT_RULES = [
16634
+ {
16635
+ intent: "root-cause",
16636
+ test: /\b(why|root[\s-]?cause|failing|fail(?:s|ed)?|breaking|broke(?:n)?|crash(?:ing|ed)?|throw(?:ing|s)?|culprit|5\d\d)\b/
16637
+ },
16638
+ {
16639
+ intent: "blast-radius",
16640
+ 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/
16641
+ },
16642
+ {
16643
+ intent: "divergence",
16644
+ test: /\b(diverg\w*|weird|mismatch|drift|out\s+of\s+sync|inconsistent|disagree\w*|declared\s+vs|anything\s+wrong)\b/
16645
+ },
16646
+ {
16647
+ intent: "incidents",
16648
+ test: /\b(incidents?|recent\s+(?:errors?|failures?)|error\s+history|failure\s+history)\b/
16649
+ },
16650
+ {
16651
+ intent: "observed",
16652
+ test: /\b(at\s+runtime|in\s+prod(?:uction)?|actually\s+call\w*|really\s+call\w*|observed|runtime\s+traffic)\b/
16653
+ },
16654
+ {
16655
+ intent: "dependencies",
16656
+ test: /\b(depend\w*|calls?|uses?|imports?|relies\s+on|needs?)\b/
16657
+ }
16658
+ ];
16659
+ function classifyIntent(question) {
16660
+ const q = question.toLowerCase();
16661
+ for (const rule of INTENT_RULES) {
16662
+ if (rule.test.test(q)) return rule.intent;
16663
+ }
16664
+ return "overview";
16665
+ }
16666
+ var STOPWORDS = /* @__PURE__ */ new Set([
16667
+ "the",
16668
+ "a",
16669
+ "an",
16670
+ "is",
16671
+ "are",
16672
+ "was",
16673
+ "were",
16674
+ "be",
16675
+ "been",
16676
+ "being",
16677
+ "of",
16678
+ "to",
16679
+ "in",
16680
+ "on",
16681
+ "at",
16682
+ "by",
16683
+ "for",
16684
+ "with",
16685
+ "and",
16686
+ "or",
16687
+ "but",
16688
+ "if",
16689
+ "as",
16690
+ "this",
16691
+ "that",
16692
+ "these",
16693
+ "those",
16694
+ "it",
16695
+ "its",
16696
+ "do",
16697
+ "does",
16698
+ "did",
16699
+ "has",
16700
+ "have",
16701
+ "had",
16702
+ "what",
16703
+ "which",
16704
+ "who",
16705
+ "whom",
16706
+ "how",
16707
+ "when",
16708
+ "where",
16709
+ "my",
16710
+ "me",
16711
+ "i",
16712
+ "we",
16713
+ "you",
16714
+ "they",
16715
+ "them",
16716
+ "from",
16717
+ "into",
16718
+ "about",
16719
+ "would",
16720
+ "will",
16721
+ "can",
16722
+ "could",
16723
+ "should",
16724
+ "anything",
16725
+ "everything",
16726
+ "something",
16727
+ "all",
16728
+ "any",
16729
+ "some",
16730
+ "no",
16731
+ "not",
16732
+ "so",
16733
+ "up",
16734
+ "out",
16735
+ "over",
16736
+ "get",
16737
+ "got",
16738
+ "show",
16739
+ "tell",
16740
+ "explain",
16741
+ "find",
16742
+ "give",
16743
+ "happen",
16744
+ "happens",
16745
+ "happening",
16746
+ "here",
16747
+ "there",
16748
+ "system",
16749
+ "code",
16750
+ "service",
16751
+ "node",
16752
+ "graph"
16753
+ ]);
16754
+ var INTENT_WORDS = /* @__PURE__ */ new Set([
16755
+ "why",
16756
+ "root",
16757
+ "cause",
16758
+ "failing",
16759
+ "fail",
16760
+ "fails",
16761
+ "failed",
16762
+ "breaking",
16763
+ "broke",
16764
+ "broken",
16765
+ "crash",
16766
+ "crashing",
16767
+ "crashed",
16768
+ "throw",
16769
+ "throwing",
16770
+ "throws",
16771
+ "culprit",
16772
+ "blast",
16773
+ "radius",
16774
+ "break",
16775
+ "breaks",
16776
+ "impact",
16777
+ "downstream",
16778
+ "dependent",
16779
+ "dependents",
16780
+ "redeploy",
16781
+ "affect",
16782
+ "affects",
16783
+ "affected",
16784
+ "diverge",
16785
+ "divergence",
16786
+ "divergences",
16787
+ "weird",
16788
+ "mismatch",
16789
+ "drift",
16790
+ "inconsistent",
16791
+ "disagree",
16792
+ "declared",
16793
+ "observed",
16794
+ "incident",
16795
+ "incidents",
16796
+ "recent",
16797
+ "errors",
16798
+ "error",
16799
+ "failures",
16800
+ "failure",
16801
+ "history",
16802
+ "runtime",
16803
+ "production",
16804
+ "prod",
16805
+ "traffic",
16806
+ "actually",
16807
+ "really",
16808
+ "depend",
16809
+ "depends",
16810
+ "dependency",
16811
+ "dependencies",
16812
+ "call",
16813
+ "calls",
16814
+ "use",
16815
+ "uses",
16816
+ "using",
16817
+ "import",
16818
+ "imports",
16819
+ "relies",
16820
+ "rely",
16821
+ "needs",
16822
+ "need",
16823
+ "sync"
16824
+ ]);
16825
+ function tokens(input) {
16826
+ const spaced = input.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
16827
+ return (spaced.toLowerCase().match(/[a-z0-9]+/g) ?? []).filter((t) => t.length >= 2);
16828
+ }
16829
+ function queryTokens(question) {
16830
+ const seen = /* @__PURE__ */ new Set();
16831
+ const out = [];
16832
+ for (const t of tokens(question)) {
16833
+ if (STOPWORDS.has(t) || INTENT_WORDS.has(t)) continue;
16834
+ if (seen.has(t)) continue;
16835
+ seen.add(t);
16836
+ out.push(t);
16837
+ }
16838
+ return out;
16839
+ }
16840
+ function nodeName(node) {
16841
+ return node.name ?? node.id;
16842
+ }
16843
+ function idBody(id) {
16844
+ const colon = id.indexOf(":");
16845
+ return colon >= 0 ? id.slice(colon + 1) : id;
16846
+ }
16847
+ var EMBED_MIN_SCORE = 0.35;
16848
+ async function resolveEntities(graph, question, searchIndex, maxNodes) {
16849
+ const qTokens = queryTokens(question);
16850
+ const normalized = question.toLowerCase();
16851
+ const best = /* @__PURE__ */ new Map();
16852
+ const consider = (cand) => {
16853
+ const cur = best.get(cand.nodeId);
16854
+ if (!cur || cand.score > cur.score) best.set(cand.nodeId, cand);
16855
+ };
16856
+ graph.forEachNode((id, attrs) => {
16857
+ const node = attrs;
16858
+ if (node.type === import_types59.NodeType.FrontierNode) return;
16859
+ const name = nodeName(node);
16860
+ const body = idBody(id);
16861
+ const labelTokens = /* @__PURE__ */ new Set([...tokens(body), ...tokens(name)]);
16862
+ if (labelTokens.size === 0) return;
16863
+ let matched = 0;
16864
+ for (const t of qTokens) if (labelTokens.has(t)) matched += 1;
16865
+ const named = body.length >= 2 && normalized.includes(body.toLowerCase()) || name.length >= 2 && normalized.includes(name.toLowerCase());
16866
+ if (matched === 0 && !named) return;
16867
+ const coverage = qTokens.length > 0 ? matched / qTokens.length : 0;
16868
+ const via = named ? "id" : matched > 0 && tokens(name).some((t) => qTokens.includes(t)) ? "label" : "token";
16869
+ const score = named ? Math.max(0.9, coverage) : Math.min(0.85, 0.3 + 0.7 * coverage);
16870
+ consider({ nodeId: id, label: name, via, score: Math.min(1, score) });
16871
+ });
16872
+ if (searchIndex) {
16873
+ try {
16874
+ const res = await searchIndex.search(question, 10);
16875
+ if (res.provider !== "substring") {
16876
+ for (const m of res.matches) {
16877
+ if (m.node.type === import_types59.NodeType.FrontierNode) continue;
16878
+ const already = best.get(m.node.id);
16879
+ if (!already && m.score < EMBED_MIN_SCORE) continue;
16880
+ consider({
16881
+ nodeId: m.node.id,
16882
+ label: nodeName(m.node),
16883
+ via: "embedding",
16884
+ score: Math.max(0, Math.min(1, m.score))
16885
+ });
16886
+ }
16887
+ }
16888
+ } catch {
16889
+ }
16890
+ }
16891
+ const viaRank = { id: 3, label: 2, token: 1, embedding: 0 };
16892
+ return [...best.values()].sort(
16893
+ (a, b) => b.score - a.score || viaRank[b.via] - viaRank[a.via] || a.nodeId.localeCompare(b.nodeId)
16894
+ ).slice(0, maxNodes).map((s) => ({ nodeId: s.nodeId, label: s.label, via: s.via, score: Number(s.score.toFixed(3)) }));
16895
+ }
16896
+ function incidentsForNode(nodeId, incidents) {
16897
+ if (!incidents || incidents.length === 0) return [];
16898
+ const svc = nodeId.replace(/^service:/, "");
16899
+ return incidents.filter((e) => e.affectedNode === nodeId || e.service === svc);
16900
+ }
16901
+ function edgeSignalNote(e) {
16902
+ const bits = [];
16903
+ if (e.signal) {
16904
+ bits.push(`${e.signal.spanCount} spans`);
16905
+ if (e.signal.errorCount > 0) bits.push(`${e.signal.errorCount} errors`);
16906
+ if (e.signal.latencyMs?.p95 !== void 0) bits.push(`p95 ${Math.round(e.signal.latencyMs.p95)}ms`);
16907
+ } else if (e.callCount !== void 0) {
16908
+ bits.push(`${e.callCount} calls`);
16909
+ }
16910
+ return bits.length ? ` (${bits.join(", ")})` : "";
16911
+ }
16912
+ function buildRootCauseSection(graph, node, incidents, now) {
16913
+ const result = getRootCause(graph, node, void 0, incidents, { now });
16914
+ if (!result) return null;
16915
+ const lastProv = result.edgeProvenances[result.edgeProvenances.length - 1] ?? import_types59.Provenance.OBSERVED;
16916
+ const facts = [
16917
+ {
16918
+ text: `Root cause: ${result.rootCauseNode} \u2014 ${result.rootCauseReason}`,
16919
+ provenance: lastProv,
16920
+ confidence: result.confidence
16921
+ }
16922
+ ];
16923
+ const candidates = result.candidates ?? [];
16924
+ for (const c of candidates.slice(0, 3)) {
16925
+ facts.push({
16926
+ text: `Candidate ${c.node} \u2014 ${c.classification}: ${c.reason}`,
16927
+ ...c.provenance ? { provenance: c.provenance } : {},
16928
+ confidence: c.confidence
16929
+ });
16930
+ }
16931
+ if (result.traversalPath.length > 1) {
16932
+ facts.push({ text: `Traversal: ${result.traversalPath.join(" \u2190 ")}` });
16933
+ }
16934
+ if (result.fixRecommendation) {
16935
+ facts.push({ text: `Recommended fix: ${result.fixRecommendation}` });
16936
+ }
16937
+ return { heading: "Root cause (navigation)", facts: facts.slice(0, MAX_FACTS_PER_SECTION) };
16938
+ }
16939
+ function buildDependenciesSection(graph, node) {
16940
+ const result = getTransitiveDependencies(graph, node, 2);
16941
+ if (result.total === 0) return null;
16942
+ const facts = result.dependencies.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
16943
+ text: `${d.nodeId} \u2014 ${d.edgeType} (distance ${d.distance})`,
16944
+ // Per-fact provenance: the transitive walk mixes EXTRACTED and OBSERVED
16945
+ // edges, so the tag is on the fact, not asserted for the whole section.
16946
+ provenance: d.provenance
16947
+ }));
16948
+ return { heading: "Dependencies", facts };
16949
+ }
16950
+ function buildObservedSection(graph, node) {
16951
+ const result = getObservedDependencies(graph, node);
16952
+ const facts = [];
16953
+ for (const e of result.dependencies.slice(0, MAX_FACTS_PER_SECTION)) {
16954
+ const via = e.source !== node ? ` via ${e.source}` : "";
16955
+ facts.push({
16956
+ text: `calls ${e.target}${via}${edgeSignalNote(e)}`,
16957
+ provenance: e.provenance,
16958
+ confidence: confidenceForEdge(e)
16959
+ });
16960
+ }
16961
+ if (facts.length === 0) {
16962
+ if (result.observed && result.inboundObservedCount > 0) {
16963
+ facts.push({
16964
+ text: `no outbound runtime calls, but OTel observed ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 a pure receiver`,
16965
+ provenance: import_types59.Provenance.OBSERVED
16966
+ });
16967
+ } else {
16968
+ return null;
16969
+ }
16970
+ }
16971
+ return { heading: "Runtime dependencies (OBSERVED)", facts };
16972
+ }
16973
+ function buildBlastSection(graph, node) {
16974
+ const result = getBlastRadius(graph, node);
16975
+ if (result.totalAffected === 0) return null;
16976
+ const facts = result.affectedNodes.slice(0, MAX_FACTS_PER_SECTION).map((n) => ({
16977
+ text: `${n.nodeId} (distance ${n.distance})`,
16978
+ provenance: n.edgeProvenance,
16979
+ confidence: n.confidence
16980
+ }));
16981
+ return {
16982
+ heading: `Blast radius \u2014 ${result.totalAffected} dependent${result.totalAffected === 1 ? "" : "s"}`,
16983
+ facts
16984
+ };
16985
+ }
16986
+ function buildIncidentsSection(node, incidents) {
16987
+ const relevant = incidentsForNode(node, incidents);
16988
+ if (relevant.length === 0) return null;
16989
+ const ordered = [...relevant].sort((a, b) => b.timestamp.localeCompare(a.timestamp)).slice(0, 4);
16990
+ const facts = ordered.map((ev) => ({
16991
+ text: `${ev.timestamp} \u2014 ${ev.service}: ${ev.errorMessage}`,
16992
+ // ErrorEvents are observation records — OBSERVED by definition.
16993
+ provenance: import_types59.Provenance.OBSERVED
16994
+ }));
16995
+ return { heading: `Recent incidents (OBSERVED) \u2014 ${relevant.length} recorded`, facts };
16996
+ }
16997
+ function divergenceLine(d) {
16998
+ if ((d.type === "missing-observed" || d.type === "missing-extracted") && d.column) {
16999
+ return `[${d.type}] ${d.table ?? d.source} column ${d.column} \u2014 ${d.reason}`;
17000
+ }
17001
+ return `[${d.type}] ${d.source} \u2192 ${d.target} \u2014 ${d.reason}`;
17002
+ }
17003
+ function buildDivergenceSection(graph, node) {
17004
+ const result = computeDivergences(graph, { node });
17005
+ if (result.totalAffected === 0) return null;
17006
+ const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
17007
+ text: divergenceLine(d),
17008
+ confidence: d.confidence
17009
+ // A divergence is composite by construction (EXTRACTED vs OBSERVED), so it
17010
+ // carries no single provenance — the fact leaves it unset.
17011
+ }));
17012
+ return {
17013
+ heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
17014
+ facts
17015
+ };
17016
+ }
17017
+ function buildGlobalDivergenceSection(graph) {
17018
+ const result = computeDivergences(graph);
17019
+ if (result.totalAffected === 0) {
17020
+ return {
17021
+ heading: "Divergences (EXTRACTED vs OBSERVED)",
17022
+ facts: [{ text: "None \u2014 declared code and observed runtime agree across the graph." }]
17023
+ };
17024
+ }
17025
+ const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
17026
+ text: divergenceLine(d),
17027
+ confidence: d.confidence
17028
+ // Composite by construction (EXTRACTED vs OBSERVED), so no single provenance.
17029
+ }));
17030
+ return {
17031
+ heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
17032
+ facts
17033
+ };
17034
+ }
17035
+ function buildGlobalIncidentsSection(incidents) {
17036
+ if (!incidents || incidents.length === 0) {
17037
+ return {
17038
+ heading: "Incidents across the system",
17039
+ facts: [{ text: "None recorded \u2014 the OBSERVED incident store is empty." }]
17040
+ };
17041
+ }
17042
+ const byKey = /* @__PURE__ */ new Map();
17043
+ for (const ev of incidents) {
17044
+ const key = ev.affectedNode || (0, import_types59.serviceId)(ev.service);
17045
+ const cur = byKey.get(key);
17046
+ if (!cur) {
17047
+ byKey.set(key, { key, count: 1, latest: ev.timestamp, sampleMsg: ev.errorMessage });
17048
+ } else {
17049
+ cur.count += 1;
17050
+ if (ev.timestamp > cur.latest) {
17051
+ cur.latest = ev.timestamp;
17052
+ cur.sampleMsg = ev.errorMessage;
17053
+ }
17054
+ }
17055
+ }
17056
+ 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);
17057
+ const facts = rows.map((r) => ({
17058
+ text: `${r.key} \u2014 ${r.count} incident${r.count === 1 ? "" : "s"}, latest ${r.latest}: ${r.sampleMsg}`,
17059
+ provenance: import_types59.Provenance.OBSERVED
17060
+ }));
17061
+ return {
17062
+ heading: `Incidents across the system \u2014 ${incidents.length} recorded`,
17063
+ facts
17064
+ };
17065
+ }
17066
+ function buildOverviewSections(graph, incidents) {
17067
+ const nodeByType = /* @__PURE__ */ new Map();
17068
+ const services = [];
17069
+ graph.forEachNode((_id, attrs) => {
17070
+ const node = attrs;
17071
+ nodeByType.set(node.type, (nodeByType.get(node.type) ?? 0) + 1);
17072
+ if (node.type === import_types59.NodeType.ServiceNode) services.push(node.id);
17073
+ });
17074
+ const edgeByProv = /* @__PURE__ */ new Map();
17075
+ graph.forEachEdge((_id, attrs) => {
17076
+ const p = attrs.provenance;
17077
+ edgeByProv.set(p, (edgeByProv.get(p) ?? 0) + 1);
17078
+ });
17079
+ const sections = [];
17080
+ const count = (t) => nodeByType.get(t) ?? 0;
17081
+ const shapeFacts = [
17082
+ { text: `${graph.order} nodes, ${graph.size} edges` },
17083
+ {
17084
+ 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`
17085
+ }
17086
+ ];
17087
+ for (const p of [import_types59.Provenance.EXTRACTED, import_types59.Provenance.OBSERVED, import_types59.Provenance.INFERRED, import_types59.Provenance.STALE]) {
17088
+ const n = edgeByProv.get(p) ?? 0;
17089
+ if (n > 0) shapeFacts.push({ text: `${n} ${p} edge${n === 1 ? "" : "s"}`, provenance: p });
17090
+ }
17091
+ sections.push({ heading: "System shape", facts: shapeFacts });
17092
+ 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);
17093
+ if (depCounts.length > 0) {
17094
+ sections.push({
17095
+ heading: "Busiest services (by direct dependencies)",
17096
+ facts: depCounts.map((r) => ({
17097
+ text: `${r.s} \u2014 ${r.n} direct dependenc${r.n === 1 ? "y" : "ies"}`
17098
+ }))
17099
+ });
17100
+ }
17101
+ if (incidents && incidents.length > 0) {
17102
+ const incCount = /* @__PURE__ */ new Map();
17103
+ for (const ev of incidents) {
17104
+ const key = ev.affectedNode || (0, import_types59.serviceId)(ev.service);
17105
+ incCount.set(key, (incCount.get(key) ?? 0) + 1);
17106
+ }
17107
+ const top = [...incCount.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, MAX_FACTS_PER_SECTION);
17108
+ sections.push({
17109
+ heading: `Nodes with incidents \u2014 ${incidents.length} recorded`,
17110
+ facts: top.map(([k, n]) => ({
17111
+ text: `${k} \u2014 ${n} incident${n === 1 ? "" : "s"}`,
17112
+ provenance: import_types59.Provenance.OBSERVED
17113
+ }))
17114
+ });
17115
+ }
17116
+ const div = computeDivergences(graph);
17117
+ sections.push({
17118
+ heading: "Divergences",
17119
+ facts: [
17120
+ {
17121
+ 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.`
17122
+ }
17123
+ ]
17124
+ });
17125
+ return sections;
17126
+ }
17127
+ function buildGlobalSections(intent, graph, incidents) {
17128
+ switch (intent) {
17129
+ case "divergence":
17130
+ return [buildGlobalDivergenceSection(graph)];
17131
+ case "incidents":
17132
+ return [buildGlobalIncidentsSection(incidents)];
17133
+ case "overview":
17134
+ return buildOverviewSections(graph, incidents);
17135
+ default:
17136
+ return null;
17137
+ }
17138
+ }
17139
+ var SECTION_ORDER = {
17140
+ "root-cause": ["root-cause", "incidents", "blast", "observed", "divergence"],
17141
+ "blast-radius": ["blast", "dependencies", "observed", "incidents"],
17142
+ dependencies: ["dependencies", "observed", "blast"],
17143
+ observed: ["observed", "dependencies", "incidents"],
17144
+ incidents: ["incidents", "root-cause", "observed"],
17145
+ divergence: ["divergence", "observed", "dependencies", "incidents"],
17146
+ overview: ["dependencies", "observed", "blast", "incidents", "divergence"]
17147
+ };
17148
+ function buildSection(kind, graph, node, incidents, now) {
17149
+ switch (kind) {
17150
+ case "root-cause":
17151
+ return buildRootCauseSection(graph, node, incidents, now);
17152
+ case "dependencies":
17153
+ return buildDependenciesSection(graph, node);
17154
+ case "observed":
17155
+ return buildObservedSection(graph, node);
17156
+ case "blast":
17157
+ return buildBlastSection(graph, node);
17158
+ case "incidents":
17159
+ return buildIncidentsSection(node, incidents);
17160
+ case "divergence":
17161
+ return buildDivergenceSection(graph, node);
17162
+ }
17163
+ }
17164
+ function summarizeGlobal(intent, sections) {
17165
+ const lead = sections[0];
17166
+ switch (intent) {
17167
+ case "divergence": {
17168
+ const none = lead?.facts[0]?.text.startsWith("None") ?? true;
17169
+ return none ? "No divergences across the graph \u2014 declared code and observed runtime agree." : `${lead.heading} across the graph, highest-confidence first below.`;
17170
+ }
17171
+ case "incidents": {
17172
+ const none = lead?.facts[0]?.text.startsWith("None") ?? true;
17173
+ return none ? "No incidents recorded across the system \u2014 the OBSERVED incident store is empty." : `${lead.heading}, aggregated by node below.`;
17174
+ }
17175
+ case "overview":
17176
+ default:
17177
+ 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.`;
17178
+ }
17179
+ }
17180
+ function summarize(question, intent, matched, primary, sections, scope) {
17181
+ if (scope === "global") {
17182
+ return summarizeGlobal(intent, sections);
17183
+ }
17184
+ if (!primary) {
17185
+ 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.`;
17186
+ }
17187
+ const lead = sections[0];
17188
+ const others = matched.slice(1);
17189
+ const alsoNote = others.length ? ` Also matched: ${others.map((m) => m.nodeId).join(", ")}.` : "";
17190
+ let core;
17191
+ switch (intent) {
17192
+ case "root-cause": {
17193
+ const rc = sections.find((s) => s.heading === "Root cause (navigation)");
17194
+ if (rc) {
17195
+ core = rc.facts[0]?.text ?? "";
17196
+ } else if (lead) {
17197
+ core = `No root cause surfaced for ${primary} \u2014 it may be healthy, or the failure isn't recorded. Nearest context: ${lead.heading.toLowerCase()}.`;
17198
+ } else {
17199
+ core = `No root cause surfaced for ${primary} \u2014 it may be healthy.`;
17200
+ }
17201
+ break;
17202
+ }
17203
+ case "blast-radius":
17204
+ core = lead ? `${lead.heading} of ${primary}.` : `${primary} has no dependents \u2014 nothing else would break if it failed.`;
17205
+ break;
17206
+ case "dependencies":
17207
+ core = lead ? `${primary}: ${lead.heading.toLowerCase()} listed below.` : `${primary} has no declared dependencies in the graph.`;
17208
+ break;
17209
+ case "observed":
17210
+ core = lead ? `${primary} at runtime: ${lead.facts.length} OBSERVED fact${lead.facts.length === 1 ? "" : "s"}.` : `No runtime traffic OBSERVED for ${primary}.`;
17211
+ break;
17212
+ case "incidents":
17213
+ core = lead ? `${primary}: ${lead.heading.toLowerCase()}.` : `No incidents recorded against ${primary}.`;
17214
+ break;
17215
+ case "divergence":
17216
+ core = lead ? `${lead.heading} involving ${primary}.` : `No divergences involve ${primary} \u2014 declared and observed agree here.`;
17217
+ break;
17218
+ default:
17219
+ core = `${primary}: ${sections.length} view${sections.length === 1 ? "" : "s"} of its fused local context below.`;
17220
+ }
17221
+ return `${core}${alsoNote}`;
17222
+ }
17223
+ async function askGraph(graph, question, opts = {}) {
17224
+ const now = opts.now ?? Date.now();
17225
+ const maxNodes = opts.maxNodes ?? DEFAULT_MAX_NODES;
17226
+ const intent = classifyIntent(question);
17227
+ const matched = await resolveEntities(graph, question, opts.searchIndex, maxNodes);
17228
+ const primary = matched[0]?.nodeId;
17229
+ const sections = [];
17230
+ let scope;
17231
+ if (primary) {
17232
+ scope = "node";
17233
+ for (const kind of SECTION_ORDER[intent]) {
17234
+ const s = buildSection(kind, graph, primary, opts.incidents, now);
17235
+ if (s && s.facts.length > 0) sections.push(s);
17236
+ }
17237
+ } else {
17238
+ const global = buildGlobalSections(intent, graph, opts.incidents);
17239
+ if (global) {
17240
+ scope = "global";
17241
+ for (const s of global) if (s.facts.length > 0) sections.push(s);
17242
+ }
17243
+ }
17244
+ const answer = summarize(question, intent, matched, primary, sections, scope);
17245
+ const provSet = /* @__PURE__ */ new Set();
17246
+ for (const s of sections) for (const f of s.facts) if (f.provenance) provSet.add(f.provenance);
17247
+ const confidence = sections[0]?.facts[0]?.confidence;
17248
+ return import_types59.AskResultSchema.parse({
17249
+ question,
17250
+ intent,
17251
+ matched,
17252
+ ...primary ? { primaryNode: primary } : {},
17253
+ ...scope ? { scope } : {},
17254
+ sections,
17255
+ answer,
17256
+ ...confidence !== void 0 ? { confidence } : {},
17257
+ provenance: [...provSet]
17258
+ });
17259
+ }
17260
+
16577
17261
  // src/diff.ts
16578
17262
  init_cjs_shims();
16579
17263
  var import_node_fs36 = require("fs");
@@ -16656,7 +17340,7 @@ init_cjs_shims();
16656
17340
  var import_node_fs37 = require("fs");
16657
17341
  var import_node_os3 = __toESM(require("os"), 1);
16658
17342
  var import_node_path72 = __toESM(require("path"), 1);
16659
- var import_types59 = require("@neat.is/types");
17343
+ var import_types60 = require("@neat.is/types");
16660
17344
  var LOCK_TIMEOUT_MS = 5e3;
16661
17345
  var LOCK_RETRY_MS = 50;
16662
17346
  function neatHome() {
@@ -16858,10 +17542,10 @@ async function readRegistry() {
16858
17542
  throw err;
16859
17543
  }
16860
17544
  const parsed = JSON.parse(raw);
16861
- return import_types59.RegistryFileSchema.parse(parsed);
17545
+ return import_types60.RegistryFileSchema.parse(parsed);
16862
17546
  }
16863
17547
  async function writeRegistry(reg) {
16864
- const validated = import_types59.RegistryFileSchema.parse(reg);
17548
+ const validated = import_types60.RegistryFileSchema.parse(reg);
16865
17549
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
16866
17550
  }
16867
17551
  async function getProject(name) {
@@ -17212,15 +17896,15 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
17212
17896
 
17213
17897
  // src/connectors/index.ts
17214
17898
  init_cjs_shims();
17215
- var import_types60 = require("@neat.is/types");
17899
+ var import_types61 = require("@neat.is/types");
17216
17900
  var NO_ENV = "unknown";
17217
17901
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
17218
17902
  if (!graph.hasNode(targetNodeId)) return void 0;
17219
17903
  const sites = [];
17220
17904
  for (const edgeId of graph.inboundEdges(targetNodeId)) {
17221
17905
  const edge = graph.getEdgeAttributes(edgeId);
17222
- if (edge.provenance !== import_types60.Provenance.EXTRACTED) continue;
17223
- const parsed = (0, import_types60.parseFileId)(edge.source);
17906
+ if (edge.provenance !== import_types61.Provenance.EXTRACTED) continue;
17907
+ const parsed = (0, import_types61.parseFileId)(edge.source);
17224
17908
  if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
17225
17909
  const site = { relPath: edge.evidence.file };
17226
17910
  if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
@@ -17231,7 +17915,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
17231
17915
  function routeCallSiteFor(graph, targetNodeId) {
17232
17916
  if (!graph.hasNode(targetNodeId)) return void 0;
17233
17917
  const attrs = graph.getNodeAttributes(targetNodeId);
17234
- if (attrs.type !== import_types60.NodeType.RouteNode || !attrs.path) return void 0;
17918
+ if (attrs.type !== import_types61.NodeType.RouteNode || !attrs.path) return void 0;
17235
17919
  const site = { relPath: attrs.path };
17236
17920
  if (attrs.line !== void 0) site.line = attrs.line;
17237
17921
  return site;
@@ -17843,23 +18527,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
17843
18527
 
17844
18528
  // src/connectors/supabase/resolve.ts
17845
18529
  init_cjs_shims();
17846
- var import_types62 = require("@neat.is/types");
18530
+ var import_types63 = require("@neat.is/types");
17847
18531
  function createSupabaseResolveTarget(graph, config) {
17848
18532
  return (signal, _ctx) => {
17849
18533
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
17850
18534
  return null;
17851
18535
  }
17852
- const subResourceId = (0, import_types62.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
18536
+ const subResourceId = (0, import_types63.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
17853
18537
  if (graph.hasNode(subResourceId)) {
17854
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types62.EdgeType.CALLS };
18538
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
17855
18539
  }
17856
- const bareResourceId = (0, import_types62.infraId)(signal.targetKind, signal.targetName);
18540
+ const bareResourceId = (0, import_types63.infraId)(signal.targetKind, signal.targetName);
17857
18541
  if (graph.hasNode(bareResourceId)) {
17858
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types62.EdgeType.CALLS };
18542
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
17859
18543
  }
17860
- const projectLevelId = (0, import_types62.infraId)("supabase", config.nodeRef);
18544
+ const projectLevelId = (0, import_types63.infraId)("supabase", config.nodeRef);
17861
18545
  if (graph.hasNode(projectLevelId)) {
17862
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types62.EdgeType.CALLS };
18546
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
17863
18547
  }
17864
18548
  return null;
17865
18549
  };
@@ -17952,7 +18636,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
17952
18636
 
17953
18637
  // src/connectors/railway/index.ts
17954
18638
  init_cjs_shims();
17955
- var import_types66 = require("@neat.is/types");
18639
+ var import_types67 = require("@neat.is/types");
17956
18640
 
17957
18641
  // src/connectors/railway/client.ts
17958
18642
  init_cjs_shims();
@@ -18103,7 +18787,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
18103
18787
  const out = [];
18104
18788
  graph.forEachNode((_id, attrs) => {
18105
18789
  const node = attrs;
18106
- if (node.type !== import_types66.NodeType.RouteNode) return;
18790
+ if (node.type !== import_types67.NodeType.RouteNode) return;
18107
18791
  const route = attrs;
18108
18792
  if (route.service !== serviceName) return;
18109
18793
  out.push({
@@ -18207,12 +18891,12 @@ function createRailwayResolveTarget(config) {
18207
18891
  const serviceName = config.serviceNameById[config.serviceId];
18208
18892
  if (!serviceName) return null;
18209
18893
  if (signal.targetKind === ROUTE_TARGET_KIND) {
18210
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types66.EdgeType.CALLS };
18894
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types67.EdgeType.CALLS };
18211
18895
  }
18212
18896
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
18213
18897
  const peerName = config.serviceNameById[signal.targetName];
18214
18898
  if (!peerName) return null;
18215
- return { targetNodeId: (0, import_types66.serviceId)(peerName), serviceName, edgeType: import_types66.EdgeType.CONNECTS_TO };
18899
+ return { targetNodeId: (0, import_types67.serviceId)(peerName), serviceName, edgeType: import_types67.EdgeType.CONNECTS_TO };
18216
18900
  }
18217
18901
  return null;
18218
18902
  };
@@ -18400,7 +19084,7 @@ function mapLogEntriesToSignals(entries) {
18400
19084
 
18401
19085
  // src/connectors/firebase/resolve.ts
18402
19086
  init_cjs_shims();
18403
- var import_types67 = require("@neat.is/types");
19087
+ var import_types68 = require("@neat.is/types");
18404
19088
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
18405
19089
  switch (resourceType) {
18406
19090
  case "cloud_function":
@@ -18415,7 +19099,7 @@ function routeEntriesFor(graph, serviceName) {
18415
19099
  const entries = [];
18416
19100
  graph.forEachNode((_id, attrs) => {
18417
19101
  const node = attrs;
18418
- if (node.type !== import_types67.NodeType.RouteNode) return;
19102
+ if (node.type !== import_types68.NodeType.RouteNode) return;
18419
19103
  const route = attrs;
18420
19104
  if (route.service !== serviceName) return;
18421
19105
  entries.push({
@@ -18447,7 +19131,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
18447
19131
  return {
18448
19132
  targetNodeId: match.routeNodeId,
18449
19133
  serviceName,
18450
- edgeType: import_types67.EdgeType.CALLS
19134
+ edgeType: import_types68.EdgeType.CALLS
18451
19135
  };
18452
19136
  };
18453
19137
  }
@@ -18474,7 +19158,7 @@ init_cjs_shims();
18474
19158
 
18475
19159
  // src/connectors/cloudflare/connector.ts
18476
19160
  init_cjs_shims();
18477
- var import_types69 = require("@neat.is/types");
19161
+ var import_types70 = require("@neat.is/types");
18478
19162
 
18479
19163
  // src/connectors/cloudflare/client.ts
18480
19164
  init_cjs_shims();
@@ -18638,7 +19322,7 @@ function findTaggedWorkerFileNode(graph, workerName) {
18638
19322
  graph.forEachNode((id, attrs) => {
18639
19323
  if (found) return;
18640
19324
  const a = attrs;
18641
- if (a.type === import_types69.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
19325
+ if (a.type === import_types70.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
18642
19326
  found = id;
18643
19327
  }
18644
19328
  });
@@ -18650,7 +19334,7 @@ function findMatchingRouteNode(graph, serviceName, method, path78) {
18650
19334
  graph.forEachNode((id, attrs) => {
18651
19335
  if (found) return;
18652
19336
  const a = attrs;
18653
- if (a.type !== import_types69.NodeType.RouteNode || a.service !== serviceName) return;
19337
+ if (a.type !== import_types70.NodeType.RouteNode || a.service !== serviceName) return;
18654
19338
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
18655
19339
  const routeMethod = (a.method ?? "").toUpperCase();
18656
19340
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -18669,11 +19353,11 @@ function createCloudflareResolveTarget(config, graph) {
18669
19353
  };
18670
19354
  const mapping = config.workers?.[scriptName];
18671
19355
  if (mapping) {
18672
- const wholeFileId = (0, import_types69.fileId)(mapping.service, mapping.entryFile);
19356
+ const wholeFileId = (0, import_types70.fileId)(mapping.service, mapping.entryFile);
18673
19357
  return {
18674
19358
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
18675
19359
  serviceName: mapping.service,
18676
- edgeType: import_types69.EdgeType.CALLS
19360
+ edgeType: import_types70.EdgeType.CALLS
18677
19361
  };
18678
19362
  }
18679
19363
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -18682,13 +19366,13 @@ function createCloudflareResolveTarget(config, graph) {
18682
19366
  return {
18683
19367
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
18684
19368
  serviceName: fileNode.service,
18685
- edgeType: import_types69.EdgeType.CALLS
19369
+ edgeType: import_types70.EdgeType.CALLS
18686
19370
  };
18687
19371
  }
18688
19372
  return {
18689
- targetNodeId: (0, import_types69.infraId)("cloudflare-worker", scriptName),
19373
+ targetNodeId: (0, import_types70.infraId)("cloudflare-worker", scriptName),
18690
19374
  serviceName: scriptName,
18691
- edgeType: import_types69.EdgeType.CALLS,
19375
+ edgeType: import_types70.EdgeType.CALLS,
18692
19376
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
18693
19377
  };
18694
19378
  };
@@ -18884,14 +19568,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
18884
19568
 
18885
19569
  // src/connectors/neon/resolve.ts
18886
19570
  init_cjs_shims();
18887
- var import_types73 = require("@neat.is/types");
19571
+ var import_types74 = require("@neat.is/types");
18888
19572
  function createNeonResolveTarget(config) {
18889
19573
  return (signal) => {
18890
19574
  if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
18891
19575
  return {
18892
- targetNodeId: (0, import_types73.infraId)("sql-table", signal.targetName),
19576
+ targetNodeId: (0, import_types74.infraId)("sql-table", signal.targetName),
18893
19577
  serviceName: config.serviceName,
18894
- edgeType: import_types73.EdgeType.CALLS,
19578
+ edgeType: import_types74.EdgeType.CALLS,
18895
19579
  ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
18896
19580
  };
18897
19581
  };
@@ -19072,14 +19756,14 @@ function mapLogEntriesToSignals2(entries) {
19072
19756
 
19073
19757
  // src/connectors/cloud-run/resolve.ts
19074
19758
  init_cjs_shims();
19075
- var import_types77 = require("@neat.is/types");
19759
+ var import_types78 = require("@neat.is/types");
19076
19760
  var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
19077
19761
  function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
19078
19762
  let found = null;
19079
19763
  graph.forEachNode((_id, attrs) => {
19080
19764
  if (found) return;
19081
19765
  const node = attrs;
19082
- if (node.type !== import_types77.NodeType.RouteNode) return;
19766
+ if (node.type !== import_types78.NodeType.RouteNode) return;
19083
19767
  const route = attrs;
19084
19768
  if (route.service !== serviceName || !route.pathTemplate) return;
19085
19769
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -19104,13 +19788,13 @@ function createCloudRunResolveTarget(graph, config) {
19104
19788
  normalizePathTemplate(path78)
19105
19789
  );
19106
19790
  if (routeNodeId) {
19107
- return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types77.EdgeType.CALLS };
19791
+ return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types78.EdgeType.CALLS };
19108
19792
  }
19109
19793
  }
19110
19794
  return {
19111
- targetNodeId: (0, import_types77.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
19795
+ targetNodeId: (0, import_types78.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
19112
19796
  serviceName: mappedService ?? gcpServiceName,
19113
- edgeType: import_types77.EdgeType.CALLS,
19797
+ edgeType: import_types78.EdgeType.CALLS,
19114
19798
  ensureInfraNode: {
19115
19799
  kind: CLOUD_RUN_SERVICE_INFRA_KIND,
19116
19800
  name: gcpServiceName,
@@ -19151,7 +19835,7 @@ function createCloudRunConnector(graph, config = {}) {
19151
19835
 
19152
19836
  // src/connectors/render/index.ts
19153
19837
  init_cjs_shims();
19154
- var import_types80 = require("@neat.is/types");
19838
+ var import_types81 = require("@neat.is/types");
19155
19839
 
19156
19840
  // src/connectors/render/types.ts
19157
19841
  init_cjs_shims();
@@ -19229,7 +19913,7 @@ function buildRenderRouteIndex(graph, serviceName) {
19229
19913
  const out = [];
19230
19914
  graph.forEachNode((_id, attrs) => {
19231
19915
  const node = attrs;
19232
- if (node.type !== import_types80.NodeType.RouteNode) return;
19916
+ if (node.type !== import_types81.NodeType.RouteNode) return;
19233
19917
  const route = attrs;
19234
19918
  if (route.service !== serviceName) return;
19235
19919
  out.push({
@@ -19314,7 +19998,7 @@ function mapRenderRequestLogsToSignals(entries, routeIndex) {
19314
19998
  function createRenderResolveTarget(config) {
19315
19999
  return (signal) => {
19316
20000
  if (signal.targetKind === ROUTE_TARGET_KIND2) {
19317
- return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types80.EdgeType.CALLS };
20001
+ return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types81.EdgeType.CALLS };
19318
20002
  }
19319
20003
  return null;
19320
20004
  };
@@ -19452,21 +20136,21 @@ function mapInsightsToSignals(rows, observedAtIso) {
19452
20136
 
19453
20137
  // src/connectors/planetscale/resolve.ts
19454
20138
  init_cjs_shims();
19455
- var import_types84 = require("@neat.is/types");
20139
+ var import_types85 = require("@neat.is/types");
19456
20140
  var PLANETSCALE_DATABASE_KIND = "planetscale-database";
19457
20141
  function createPlanetscaleResolveTarget(graph, config) {
19458
20142
  const databaseName = `${config.organization}/${config.database}`;
19459
20143
  return (signal, _ctx) => {
19460
20144
  if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
19461
- const tableId = (0, import_types84.infraId)("sql-table", signal.targetName);
20145
+ const tableId = (0, import_types85.infraId)("sql-table", signal.targetName);
19462
20146
  if (graph.hasNode(tableId)) {
19463
- return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types84.EdgeType.CALLS };
20147
+ return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types85.EdgeType.CALLS };
19464
20148
  }
19465
- const providerId = (0, import_types84.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
20149
+ const providerId = (0, import_types85.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
19466
20150
  return {
19467
20151
  targetNodeId: providerId,
19468
20152
  serviceName: config.serviceName,
19469
- edgeType: import_types84.EdgeType.CALLS,
20153
+ edgeType: import_types85.EdgeType.CALLS,
19470
20154
  ensureInfraNode: { kind: PLANETSCALE_DATABASE_KIND, name: databaseName, provider: "planetscale" }
19471
20155
  };
19472
20156
  };
@@ -19731,7 +20415,7 @@ function mapBuildsToSignals(builds, serviceName) {
19731
20415
 
19732
20416
  // src/connectors/eas/resolve.ts
19733
20417
  init_cjs_shims();
19734
- var import_types89 = require("@neat.is/types");
20418
+ var import_types90 = require("@neat.is/types");
19735
20419
  var NO_ENV2 = "unknown";
19736
20420
  var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
19737
20421
  var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
@@ -19747,8 +20431,8 @@ function configBasenamesForPhase(phase) {
19747
20431
  function configNodeService(graph, configNodeId) {
19748
20432
  for (const edgeId of graph.inboundEdges(configNodeId)) {
19749
20433
  const edge = graph.getEdgeAttributes(edgeId);
19750
- if (edge.type !== import_types89.EdgeType.CONFIGURED_BY) continue;
19751
- const parsed = (0, import_types89.parseFileId)(edge.source);
20434
+ if (edge.type !== import_types90.EdgeType.CONFIGURED_BY) continue;
20435
+ const parsed = (0, import_types90.parseFileId)(edge.source);
19752
20436
  if (parsed) return parsed.service;
19753
20437
  }
19754
20438
  return null;
@@ -19759,7 +20443,7 @@ function findConfigNode(graph, basenames, serviceName) {
19759
20443
  graph.forEachNode((id, attrs) => {
19760
20444
  if (scoped) return;
19761
20445
  const node = attrs;
19762
- if (node.type !== import_types89.NodeType.ConfigNode) return;
20446
+ if (node.type !== import_types90.NodeType.ConfigNode) return;
19763
20447
  if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
19764
20448
  if (anyMatch === null) anyMatch = id;
19765
20449
  if (configNodeService(graph, id) === serviceName) scoped = id;
@@ -19776,13 +20460,13 @@ function createEasResolveTarget(graph) {
19776
20460
  if (basenames.length > 0) {
19777
20461
  const configNodeId = findConfigNode(graph, basenames, serviceName);
19778
20462
  if (configNodeId) {
19779
- return { targetNodeId: configNodeId, serviceName, edgeType: import_types89.EdgeType.CALLS };
20463
+ return { targetNodeId: configNodeId, serviceName, edgeType: import_types90.EdgeType.CALLS };
19780
20464
  }
19781
20465
  }
19782
20466
  return {
19783
20467
  targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV2),
19784
20468
  serviceName,
19785
- edgeType: import_types89.EdgeType.CALLS
20469
+ edgeType: import_types90.EdgeType.CALLS
19786
20470
  };
19787
20471
  };
19788
20472
  }
@@ -20507,11 +21191,11 @@ function registerRoutes(scope, ctx) {
20507
21191
  const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
20508
21192
  const parsed = [];
20509
21193
  for (const c of candidates) {
20510
- const r = import_types92.DivergenceTypeSchema.safeParse(c);
21194
+ const r = import_types93.DivergenceTypeSchema.safeParse(c);
20511
21195
  if (!r.success) {
20512
21196
  return reply.code(400).send({
20513
21197
  error: `unknown divergence type "${c}"`,
20514
- allowed: import_types92.DivergenceTypeSchema.options
21198
+ allowed: import_types93.DivergenceTypeSchema.options
20515
21199
  });
20516
21200
  }
20517
21201
  parsed.push(r.data);
@@ -20753,6 +21437,18 @@ function registerRoutes(scope, ctx) {
20753
21437
  matches: matches.slice(0, safeLimit)
20754
21438
  };
20755
21439
  });
21440
+ scope.get("/graph/ask", async (req2, reply) => {
21441
+ const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
21442
+ if (!proj) return;
21443
+ const question = (req2.query.q ?? "").trim();
21444
+ if (!question) return reply.code(400).send({ error: "query parameter `q` is required" });
21445
+ const epath = errorsPathFor(proj);
21446
+ const incidents = epath ? await readErrorEvents(epath) : [];
21447
+ return askGraph(proj.graph, question, {
21448
+ ...proj.searchIndex ? { searchIndex: proj.searchIndex } : {},
21449
+ incidents
21450
+ });
21451
+ });
20756
21452
  scope.get(
20757
21453
  "/graph/diff",
20758
21454
  async (req2, reply) => {
@@ -20853,7 +21549,7 @@ function registerRoutes(scope, ctx) {
20853
21549
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
20854
21550
  let violations = await log.readAll();
20855
21551
  if (req2.query.severity) {
20856
- const sev = import_types92.PolicySeveritySchema.safeParse(req2.query.severity);
21552
+ const sev = import_types93.PolicySeveritySchema.safeParse(req2.query.severity);
20857
21553
  if (!sev.success) {
20858
21554
  return reply.code(400).send({
20859
21555
  error: "invalid severity",
@@ -20892,7 +21588,7 @@ function registerRoutes(scope, ctx) {
20892
21588
  scope.post("/policies/check", async (req2, reply) => {
20893
21589
  const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
20894
21590
  if (!proj) return;
20895
- const parsed = import_types92.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
21591
+ const parsed = import_types93.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
20896
21592
  if (!parsed.success) {
20897
21593
  return reply.code(400).send({
20898
21594
  error: "invalid /policies/check body",
@@ -21233,7 +21929,7 @@ function unroutedErrorsPath(neatHome4) {
21233
21929
  }
21234
21930
 
21235
21931
  // src/daemon.ts
21236
- var import_types93 = require("@neat.is/types");
21932
+ var import_types94 = require("@neat.is/types");
21237
21933
  function daemonJsonPath(scanPath) {
21238
21934
  return import_node_path75.default.join(scanPath, "neat-out", "daemon.json");
21239
21935
  }
@@ -21359,8 +22055,8 @@ function isTokenContained(needle, haystack) {
21359
22055
  const n = needle.toLowerCase();
21360
22056
  const h = haystack.toLowerCase();
21361
22057
  if (!h.includes(n)) return false;
21362
- const tokens = h.split(/[-_]/);
21363
- return tokens.includes(n);
22058
+ const tokens2 = h.split(/[-_]/);
22059
+ return tokens2.includes(n);
21364
22060
  }
21365
22061
  function serviceNameMatchesProject(serviceName, project) {
21366
22062
  if (serviceName.toLowerCase() === project.toLowerCase()) return true;
@@ -21372,7 +22068,7 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
21372
22068
  if (!serviceName) return true;
21373
22069
  if (serviceNameMatchesProject(serviceName, project)) return true;
21374
22070
  return graph.someNode(
21375
- (_id, attrs) => attrs.type === import_types93.NodeType.ServiceNode && attrs.name === serviceName
22071
+ (_id, attrs) => attrs.type === import_types94.NodeType.ServiceNode && attrs.name === serviceName
21376
22072
  );
21377
22073
  }
21378
22074
  async function bootstrapProject(entry2, connectors = [], neatHome4) {