@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/server.cjs CHANGED
@@ -787,7 +787,7 @@ function getGraph(project = DEFAULT_PROJECT) {
787
787
  init_cjs_shims();
788
788
  var import_fastify2 = __toESM(require("fastify"), 1);
789
789
  var import_cors = __toESM(require("@fastify/cors"), 1);
790
- var import_types92 = require("@neat.is/types");
790
+ var import_types93 = require("@neat.is/types");
791
791
 
792
792
  // src/extend/index.ts
793
793
  init_cjs_shims();
@@ -4160,11 +4160,11 @@ function actixAttributeRoute(attrItem, out) {
4160
4160
  if (!nameNode) return;
4161
4161
  const macro = nameNode.type === "identifier" ? nameNode.text : nameNode.type === "scoped_identifier" ? nameNode.childForFieldName("name")?.text ?? null : null;
4162
4162
  if (!macro) return;
4163
- const tokens = attr.childForFieldName("arguments");
4164
- if (!tokens || tokens.type !== "token_tree") return;
4163
+ const tokens2 = attr.childForFieldName("arguments");
4164
+ if (!tokens2 || tokens2.type !== "token_tree") return;
4165
4165
  const strings = [];
4166
- for (let i = 0; i < tokens.namedChildCount; i++) {
4167
- const s = rustStringContent(tokens.namedChild(i));
4166
+ for (let i = 0; i < tokens2.namedChildCount; i++) {
4167
+ const s = rustStringContent(tokens2.namedChild(i));
4168
4168
  if (s !== null) strings.push(s);
4169
4169
  }
4170
4170
  const pathStr = strings[0];
@@ -5982,29 +5982,29 @@ function promoteFrontierNodes(graph, opts = {}) {
5982
5982
  toPromote.push({ frontierId: id, serviceId: target });
5983
5983
  });
5984
5984
  let promoted = 0;
5985
- for (const { frontierId: frontierId2, serviceId: serviceId15 } of toPromote) {
5985
+ for (const { frontierId: frontierId2, serviceId: serviceId16 } of toPromote) {
5986
5986
  if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
5987
5987
  const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
5988
5988
  if (!gate.allowed) {
5989
5989
  continue;
5990
5990
  }
5991
5991
  }
5992
- rewireFrontierEdges(graph, frontierId2, serviceId15);
5992
+ rewireFrontierEdges(graph, frontierId2, serviceId16);
5993
5993
  graph.dropNode(frontierId2);
5994
5994
  promoted++;
5995
5995
  }
5996
5996
  return promoted;
5997
5997
  }
5998
- function rewireFrontierEdges(graph, frontierId2, serviceId15) {
5998
+ function rewireFrontierEdges(graph, frontierId2, serviceId16) {
5999
5999
  const inbound = [...graph.inboundEdges(frontierId2)];
6000
6000
  const outbound = [...graph.outboundEdges(frontierId2)];
6001
6001
  for (const edgeId of inbound) {
6002
6002
  const edge = graph.getEdgeAttributes(edgeId);
6003
- rebuildEdge(graph, edge, edge.source, serviceId15, edgeId);
6003
+ rebuildEdge(graph, edge, edge.source, serviceId16, edgeId);
6004
6004
  }
6005
6005
  for (const edgeId of outbound) {
6006
6006
  const edge = graph.getEdgeAttributes(edgeId);
6007
- rebuildEdge(graph, edge, serviceId15, edge.target, edgeId);
6007
+ rebuildEdge(graph, edge, serviceId16, edge.target, edgeId);
6008
6008
  }
6009
6009
  }
6010
6010
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
@@ -6350,7 +6350,7 @@ function databaseRootCauseShape(graph, origin, walk10) {
6350
6350
  for (const id of walk10.path) {
6351
6351
  const owner = resolveOwningService(graph, id);
6352
6352
  if (!owner) continue;
6353
- const { id: serviceId15, svc } = owner;
6353
+ const { id: serviceId16, svc } = owner;
6354
6354
  const deps = svc.dependencies ?? {};
6355
6355
  for (const pair of candidatePairs) {
6356
6356
  const declared = deps[pair.driver];
@@ -6363,7 +6363,7 @@ function databaseRootCauseShape(graph, origin, walk10) {
6363
6363
  );
6364
6364
  if (!result.compatible) {
6365
6365
  return {
6366
- rootCauseNode: serviceId15,
6366
+ rootCauseNode: serviceId16,
6367
6367
  rootCauseReason: result.reason ?? "incompatible driver",
6368
6368
  ...result.minDriverVersion ? {
6369
6369
  fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
@@ -6378,7 +6378,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
6378
6378
  for (const id of walk10.path) {
6379
6379
  const owner = resolveOwningService(graph, id);
6380
6380
  if (!owner) continue;
6381
- const { id: serviceId15, svc } = owner;
6381
+ const { id: serviceId16, svc } = owner;
6382
6382
  const deps = svc.dependencies ?? {};
6383
6383
  const serviceNodeEngine = svc.nodeEngine;
6384
6384
  for (const constraint of nodeEngineConstraints()) {
@@ -6387,7 +6387,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
6387
6387
  const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
6388
6388
  if (!result.compatible && result.reason) {
6389
6389
  return {
6390
- rootCauseNode: serviceId15,
6390
+ rootCauseNode: serviceId16,
6391
6391
  rootCauseReason: result.reason,
6392
6392
  ...result.requiredNodeVersion ? {
6393
6393
  fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
@@ -6402,7 +6402,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
6402
6402
  const result = checkPackageConflict(conflict, declared, requiredDeclared);
6403
6403
  if (!result.compatible && result.reason) {
6404
6404
  return {
6405
- rootCauseNode: serviceId15,
6405
+ rootCauseNode: serviceId16,
6406
6406
  rootCauseReason: result.reason,
6407
6407
  fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
6408
6408
  };
@@ -6507,9 +6507,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
6507
6507
  function isFailingCallEdge(e) {
6508
6508
  return e.type === import_types8.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
6509
6509
  }
6510
- function callSourcesForService(graph, serviceId15) {
6511
- const ids = [serviceId15];
6512
- for (const edgeId of graph.outboundEdges(serviceId15)) {
6510
+ function callSourcesForService(graph, serviceId16) {
6511
+ const ids = [serviceId16];
6512
+ for (const edgeId of graph.outboundEdges(serviceId16)) {
6513
6513
  const e = graph.getEdgeAttributes(edgeId);
6514
6514
  if (e.type !== import_types8.EdgeType.CONTAINS) continue;
6515
6515
  const tgt = graph.getNodeAttributes(e.target);
@@ -6533,9 +6533,9 @@ function failingCallDominates(e, id, curEdge, curId) {
6533
6533
  }
6534
6534
  return id < curId;
6535
6535
  }
6536
- function dominantFailingCall(graph, serviceId15, visited) {
6536
+ function dominantFailingCall(graph, serviceId16, visited) {
6537
6537
  let best = null;
6538
- for (const src of callSourcesForService(graph, serviceId15)) {
6538
+ for (const src of callSourcesForService(graph, serviceId16)) {
6539
6539
  for (const edgeId of graph.outboundEdges(src)) {
6540
6540
  const e = graph.getEdgeAttributes(edgeId);
6541
6541
  if (!isFailingCallEdge(e)) continue;
@@ -6574,9 +6574,9 @@ function staleCallDominates(e, id, curEdge, curId) {
6574
6574
  if (ev !== cv) return ev > cv;
6575
6575
  return id < curId;
6576
6576
  }
6577
- function dominantStaleCall(graph, serviceId15, visited) {
6577
+ function dominantStaleCall(graph, serviceId16, visited) {
6578
6578
  const bestByCallee = /* @__PURE__ */ new Map();
6579
- for (const src of callSourcesForService(graph, serviceId15)) {
6579
+ for (const src of callSourcesForService(graph, serviceId16)) {
6580
6580
  for (const edgeId of graph.outboundEdges(src)) {
6581
6581
  const e = graph.getEdgeAttributes(edgeId);
6582
6582
  if (e.type !== import_types8.EdgeType.CALLS) continue;
@@ -7399,6 +7399,53 @@ function detectColumnDrift(node) {
7399
7399
  function involvesNode(d, nodeId) {
7400
7400
  return d.source === nodeId || d.target === nodeId;
7401
7401
  }
7402
+ function datastoreEverObserved(graph, nodeId) {
7403
+ if (!graph.hasNode(nodeId)) return false;
7404
+ const n = graph.getNodeAttributes(nodeId);
7405
+ if (n.type === import_types9.NodeType.DatabaseNode) {
7406
+ const via = n.discoveredVia;
7407
+ if (via === "otel" || via === "merged") return true;
7408
+ }
7409
+ for (const edgeId of graph.inboundEdges(nodeId)) {
7410
+ const e = graph.getEdgeAttributes(edgeId);
7411
+ if (e.provenance === import_types9.Provenance.OBSERVED || e.provenance === import_types9.Provenance.STALE) return true;
7412
+ }
7413
+ return false;
7414
+ }
7415
+ function serviceHasObservedSameEngineStore(graph, buckets2, serviceId16, engine, excludeTarget) {
7416
+ for (const bucket of buckets2.values()) {
7417
+ if (bucket.type !== import_types9.EdgeType.CONNECTS_TO) continue;
7418
+ if (bucket.source !== serviceId16) continue;
7419
+ if (bucket.target === excludeTarget) continue;
7420
+ if (!graph.hasNode(bucket.target)) continue;
7421
+ const target = graph.getNodeAttributes(bucket.target);
7422
+ if (target.type !== import_types9.NodeType.DatabaseNode) continue;
7423
+ if (target.engine !== engine) continue;
7424
+ if (bucket.observed || datastoreEverObserved(graph, bucket.target)) return true;
7425
+ }
7426
+ return false;
7427
+ }
7428
+ var DEAD_CODE_PROBE_CONFIDENCE = 0.1;
7429
+ function dampenDeadCodeProbes(graph, buckets2, all) {
7430
+ return all.map((d) => {
7431
+ if (d.type !== "missing-observed") return d;
7432
+ if (!d.extracted || d.edgeType !== import_types9.EdgeType.CONNECTS_TO) return d;
7433
+ if (!graph.hasNode(d.target)) return d;
7434
+ const target = graph.getNodeAttributes(d.target);
7435
+ if (target.type !== import_types9.NodeType.DatabaseNode) return d;
7436
+ if (d.extracted.evidence?.hostSource !== "literal") return d;
7437
+ if (datastoreEverObserved(graph, d.target)) return d;
7438
+ const engine = target.engine;
7439
+ if (!serviceHasObservedSameEngineStore(graph, buckets2, d.source, engine, d.target)) return d;
7440
+ const host = target.host ?? target.name;
7441
+ return {
7442
+ ...d,
7443
+ confidence: Math.min(d.confidence, DEAD_CODE_PROBE_CONFIDENCE),
7444
+ 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.`,
7445
+ 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."
7446
+ };
7447
+ });
7448
+ }
7402
7449
  function suppressHostMismatchHalves(all) {
7403
7450
  const observedHalf = /* @__PURE__ */ new Set();
7404
7451
  const declaredHalf = /* @__PURE__ */ new Set();
@@ -7435,7 +7482,8 @@ function computeDivergences(graph, opts = {}) {
7435
7482
  }
7436
7483
  });
7437
7484
  const reconciled = suppressHostMismatchHalves(all);
7438
- let filtered = reconciled;
7485
+ const dampened = dampenDeadCodeProbes(graph, buckets2, reconciled);
7486
+ let filtered = dampened;
7439
7487
  if (opts.type) {
7440
7488
  const allowed = opts.type;
7441
7489
  filtered = filtered.filter((d) => allowed.has(d.type));
@@ -8431,9 +8479,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
8431
8479
  "StatefulSet",
8432
8480
  "DaemonSet"
8433
8481
  ]);
8434
- function addAliases(graph, serviceId15, candidates) {
8435
- if (!graph.hasNode(serviceId15)) return;
8436
- const node = graph.getNodeAttributes(serviceId15);
8482
+ function addAliases(graph, serviceId16, candidates) {
8483
+ if (!graph.hasNode(serviceId16)) return;
8484
+ const node = graph.getNodeAttributes(serviceId16);
8437
8485
  if (node.type !== import_types20.NodeType.ServiceNode) return;
8438
8486
  const set = new Set(node.aliases ?? []);
8439
8487
  for (const c of candidates) {
@@ -8443,7 +8491,7 @@ function addAliases(graph, serviceId15, candidates) {
8443
8491
  }
8444
8492
  if (set.size === 0) return;
8445
8493
  const updated = { ...node, aliases: [...set].sort() };
8446
- graph.replaceNodeAttributes(serviceId15, updated);
8494
+ graph.replaceNodeAttributes(serviceId16, updated);
8447
8495
  }
8448
8496
  function indexServicesByName(services) {
8449
8497
  const map = /* @__PURE__ */ new Map();
@@ -8476,12 +8524,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
8476
8524
  }
8477
8525
  if (!compose?.services) return;
8478
8526
  for (const [composeName, svc] of Object.entries(compose.services)) {
8479
- const serviceId15 = serviceIndex.get(composeName);
8480
- if (!serviceId15) continue;
8527
+ const serviceId16 = serviceIndex.get(composeName);
8528
+ if (!serviceId16) continue;
8481
8529
  const aliases = /* @__PURE__ */ new Set([composeName]);
8482
8530
  if (svc.container_name) aliases.add(svc.container_name);
8483
8531
  if (svc.hostname) aliases.add(svc.hostname);
8484
- addAliases(graph, serviceId15, aliases);
8532
+ addAliases(graph, serviceId16, aliases);
8485
8533
  }
8486
8534
  }
8487
8535
  var LABEL_KEYS = /* @__PURE__ */ new Set([
@@ -10471,12 +10519,12 @@ async function resolveConfigs(literals, keys, serviceDir, looksLike, parse11) {
10471
10519
  const value = await interpolateEnvRefs(raw, serviceDir);
10472
10520
  if (!looksLike(value)) continue;
10473
10521
  const parsed = parse11(value);
10474
- if (parsed) out.push(parsed);
10522
+ if (parsed) out.push({ ...parsed, hostSource: "config" });
10475
10523
  }
10476
10524
  for (const lit of literals) {
10477
10525
  if (!looksLike(lit)) continue;
10478
10526
  const parsed = parse11(lit);
10479
- if (parsed) out.push(parsed);
10527
+ if (parsed) out.push({ ...parsed, hostSource: "literal" });
10480
10528
  }
10481
10529
  return out;
10482
10530
  }
@@ -10745,7 +10793,10 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
10745
10793
  type: import_types24.EdgeType.CONNECTS_TO,
10746
10794
  provenance: import_types24.Provenance.EXTRACTED,
10747
10795
  confidence: (0, import_types24.confidenceForExtracted)("structural"),
10748
- evidence: { file: evidenceFile }
10796
+ // Carry how the host was recovered (ADR-213) so the divergence ranker
10797
+ // can tell a real declared store from a hardcoded fault-injection /
10798
+ // flag-gated probe. Only set when the parser distinguished the two.
10799
+ evidence: config.hostSource ? { file: evidenceFile, hostSource: config.hostSource } : { file: evidenceFile }
10749
10800
  };
10750
10801
  if (!graph.hasEdge(edge.id)) {
10751
10802
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -16268,6 +16319,639 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
16268
16319
  return result;
16269
16320
  }
16270
16321
 
16322
+ // src/ask.ts
16323
+ init_cjs_shims();
16324
+ var import_types58 = require("@neat.is/types");
16325
+ var DEFAULT_MAX_NODES = 3;
16326
+ var MAX_FACTS_PER_SECTION = 6;
16327
+ var INTENT_RULES = [
16328
+ {
16329
+ intent: "root-cause",
16330
+ test: /\b(why|root[\s-]?cause|failing|fail(?:s|ed)?|breaking|broke(?:n)?|crash(?:ing|ed)?|throw(?:ing|s)?|culprit|5\d\d)\b/
16331
+ },
16332
+ {
16333
+ intent: "blast-radius",
16334
+ 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/
16335
+ },
16336
+ {
16337
+ intent: "divergence",
16338
+ test: /\b(diverg\w*|weird|mismatch|drift|out\s+of\s+sync|inconsistent|disagree\w*|declared\s+vs|anything\s+wrong)\b/
16339
+ },
16340
+ {
16341
+ intent: "incidents",
16342
+ test: /\b(incidents?|recent\s+(?:errors?|failures?)|error\s+history|failure\s+history)\b/
16343
+ },
16344
+ {
16345
+ intent: "observed",
16346
+ test: /\b(at\s+runtime|in\s+prod(?:uction)?|actually\s+call\w*|really\s+call\w*|observed|runtime\s+traffic)\b/
16347
+ },
16348
+ {
16349
+ intent: "dependencies",
16350
+ test: /\b(depend\w*|calls?|uses?|imports?|relies\s+on|needs?)\b/
16351
+ }
16352
+ ];
16353
+ function classifyIntent(question) {
16354
+ const q = question.toLowerCase();
16355
+ for (const rule of INTENT_RULES) {
16356
+ if (rule.test.test(q)) return rule.intent;
16357
+ }
16358
+ return "overview";
16359
+ }
16360
+ var STOPWORDS = /* @__PURE__ */ new Set([
16361
+ "the",
16362
+ "a",
16363
+ "an",
16364
+ "is",
16365
+ "are",
16366
+ "was",
16367
+ "were",
16368
+ "be",
16369
+ "been",
16370
+ "being",
16371
+ "of",
16372
+ "to",
16373
+ "in",
16374
+ "on",
16375
+ "at",
16376
+ "by",
16377
+ "for",
16378
+ "with",
16379
+ "and",
16380
+ "or",
16381
+ "but",
16382
+ "if",
16383
+ "as",
16384
+ "this",
16385
+ "that",
16386
+ "these",
16387
+ "those",
16388
+ "it",
16389
+ "its",
16390
+ "do",
16391
+ "does",
16392
+ "did",
16393
+ "has",
16394
+ "have",
16395
+ "had",
16396
+ "what",
16397
+ "which",
16398
+ "who",
16399
+ "whom",
16400
+ "how",
16401
+ "when",
16402
+ "where",
16403
+ "my",
16404
+ "me",
16405
+ "i",
16406
+ "we",
16407
+ "you",
16408
+ "they",
16409
+ "them",
16410
+ "from",
16411
+ "into",
16412
+ "about",
16413
+ "would",
16414
+ "will",
16415
+ "can",
16416
+ "could",
16417
+ "should",
16418
+ "anything",
16419
+ "everything",
16420
+ "something",
16421
+ "all",
16422
+ "any",
16423
+ "some",
16424
+ "no",
16425
+ "not",
16426
+ "so",
16427
+ "up",
16428
+ "out",
16429
+ "over",
16430
+ "get",
16431
+ "got",
16432
+ "show",
16433
+ "tell",
16434
+ "explain",
16435
+ "find",
16436
+ "give",
16437
+ "happen",
16438
+ "happens",
16439
+ "happening",
16440
+ "here",
16441
+ "there",
16442
+ "system",
16443
+ "code",
16444
+ "service",
16445
+ "node",
16446
+ "graph"
16447
+ ]);
16448
+ var INTENT_WORDS = /* @__PURE__ */ new Set([
16449
+ "why",
16450
+ "root",
16451
+ "cause",
16452
+ "failing",
16453
+ "fail",
16454
+ "fails",
16455
+ "failed",
16456
+ "breaking",
16457
+ "broke",
16458
+ "broken",
16459
+ "crash",
16460
+ "crashing",
16461
+ "crashed",
16462
+ "throw",
16463
+ "throwing",
16464
+ "throws",
16465
+ "culprit",
16466
+ "blast",
16467
+ "radius",
16468
+ "break",
16469
+ "breaks",
16470
+ "impact",
16471
+ "downstream",
16472
+ "dependent",
16473
+ "dependents",
16474
+ "redeploy",
16475
+ "affect",
16476
+ "affects",
16477
+ "affected",
16478
+ "diverge",
16479
+ "divergence",
16480
+ "divergences",
16481
+ "weird",
16482
+ "mismatch",
16483
+ "drift",
16484
+ "inconsistent",
16485
+ "disagree",
16486
+ "declared",
16487
+ "observed",
16488
+ "incident",
16489
+ "incidents",
16490
+ "recent",
16491
+ "errors",
16492
+ "error",
16493
+ "failures",
16494
+ "failure",
16495
+ "history",
16496
+ "runtime",
16497
+ "production",
16498
+ "prod",
16499
+ "traffic",
16500
+ "actually",
16501
+ "really",
16502
+ "depend",
16503
+ "depends",
16504
+ "dependency",
16505
+ "dependencies",
16506
+ "call",
16507
+ "calls",
16508
+ "use",
16509
+ "uses",
16510
+ "using",
16511
+ "import",
16512
+ "imports",
16513
+ "relies",
16514
+ "rely",
16515
+ "needs",
16516
+ "need",
16517
+ "sync"
16518
+ ]);
16519
+ function tokens(input) {
16520
+ const spaced = input.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
16521
+ return (spaced.toLowerCase().match(/[a-z0-9]+/g) ?? []).filter((t) => t.length >= 2);
16522
+ }
16523
+ function queryTokens(question) {
16524
+ const seen = /* @__PURE__ */ new Set();
16525
+ const out = [];
16526
+ for (const t of tokens(question)) {
16527
+ if (STOPWORDS.has(t) || INTENT_WORDS.has(t)) continue;
16528
+ if (seen.has(t)) continue;
16529
+ seen.add(t);
16530
+ out.push(t);
16531
+ }
16532
+ return out;
16533
+ }
16534
+ function nodeName(node) {
16535
+ return node.name ?? node.id;
16536
+ }
16537
+ function idBody(id) {
16538
+ const colon = id.indexOf(":");
16539
+ return colon >= 0 ? id.slice(colon + 1) : id;
16540
+ }
16541
+ var EMBED_MIN_SCORE = 0.35;
16542
+ async function resolveEntities(graph, question, searchIndex, maxNodes) {
16543
+ const qTokens = queryTokens(question);
16544
+ const normalized = question.toLowerCase();
16545
+ const best = /* @__PURE__ */ new Map();
16546
+ const consider = (cand) => {
16547
+ const cur = best.get(cand.nodeId);
16548
+ if (!cur || cand.score > cur.score) best.set(cand.nodeId, cand);
16549
+ };
16550
+ graph.forEachNode((id, attrs) => {
16551
+ const node = attrs;
16552
+ if (node.type === import_types58.NodeType.FrontierNode) return;
16553
+ const name = nodeName(node);
16554
+ const body = idBody(id);
16555
+ const labelTokens = /* @__PURE__ */ new Set([...tokens(body), ...tokens(name)]);
16556
+ if (labelTokens.size === 0) return;
16557
+ let matched = 0;
16558
+ for (const t of qTokens) if (labelTokens.has(t)) matched += 1;
16559
+ const named = body.length >= 2 && normalized.includes(body.toLowerCase()) || name.length >= 2 && normalized.includes(name.toLowerCase());
16560
+ if (matched === 0 && !named) return;
16561
+ const coverage = qTokens.length > 0 ? matched / qTokens.length : 0;
16562
+ const via = named ? "id" : matched > 0 && tokens(name).some((t) => qTokens.includes(t)) ? "label" : "token";
16563
+ const score = named ? Math.max(0.9, coverage) : Math.min(0.85, 0.3 + 0.7 * coverage);
16564
+ consider({ nodeId: id, label: name, via, score: Math.min(1, score) });
16565
+ });
16566
+ if (searchIndex) {
16567
+ try {
16568
+ const res = await searchIndex.search(question, 10);
16569
+ if (res.provider !== "substring") {
16570
+ for (const m of res.matches) {
16571
+ if (m.node.type === import_types58.NodeType.FrontierNode) continue;
16572
+ const already = best.get(m.node.id);
16573
+ if (!already && m.score < EMBED_MIN_SCORE) continue;
16574
+ consider({
16575
+ nodeId: m.node.id,
16576
+ label: nodeName(m.node),
16577
+ via: "embedding",
16578
+ score: Math.max(0, Math.min(1, m.score))
16579
+ });
16580
+ }
16581
+ }
16582
+ } catch {
16583
+ }
16584
+ }
16585
+ const viaRank = { id: 3, label: 2, token: 1, embedding: 0 };
16586
+ return [...best.values()].sort(
16587
+ (a, b) => b.score - a.score || viaRank[b.via] - viaRank[a.via] || a.nodeId.localeCompare(b.nodeId)
16588
+ ).slice(0, maxNodes).map((s) => ({ nodeId: s.nodeId, label: s.label, via: s.via, score: Number(s.score.toFixed(3)) }));
16589
+ }
16590
+ function incidentsForNode(nodeId, incidents) {
16591
+ if (!incidents || incidents.length === 0) return [];
16592
+ const svc = nodeId.replace(/^service:/, "");
16593
+ return incidents.filter((e) => e.affectedNode === nodeId || e.service === svc);
16594
+ }
16595
+ function edgeSignalNote(e) {
16596
+ const bits = [];
16597
+ if (e.signal) {
16598
+ bits.push(`${e.signal.spanCount} spans`);
16599
+ if (e.signal.errorCount > 0) bits.push(`${e.signal.errorCount} errors`);
16600
+ if (e.signal.latencyMs?.p95 !== void 0) bits.push(`p95 ${Math.round(e.signal.latencyMs.p95)}ms`);
16601
+ } else if (e.callCount !== void 0) {
16602
+ bits.push(`${e.callCount} calls`);
16603
+ }
16604
+ return bits.length ? ` (${bits.join(", ")})` : "";
16605
+ }
16606
+ function buildRootCauseSection(graph, node, incidents, now) {
16607
+ const result = getRootCause(graph, node, void 0, incidents, { now });
16608
+ if (!result) return null;
16609
+ const lastProv = result.edgeProvenances[result.edgeProvenances.length - 1] ?? import_types58.Provenance.OBSERVED;
16610
+ const facts = [
16611
+ {
16612
+ text: `Root cause: ${result.rootCauseNode} \u2014 ${result.rootCauseReason}`,
16613
+ provenance: lastProv,
16614
+ confidence: result.confidence
16615
+ }
16616
+ ];
16617
+ const candidates = result.candidates ?? [];
16618
+ for (const c of candidates.slice(0, 3)) {
16619
+ facts.push({
16620
+ text: `Candidate ${c.node} \u2014 ${c.classification}: ${c.reason}`,
16621
+ ...c.provenance ? { provenance: c.provenance } : {},
16622
+ confidence: c.confidence
16623
+ });
16624
+ }
16625
+ if (result.traversalPath.length > 1) {
16626
+ facts.push({ text: `Traversal: ${result.traversalPath.join(" \u2190 ")}` });
16627
+ }
16628
+ if (result.fixRecommendation) {
16629
+ facts.push({ text: `Recommended fix: ${result.fixRecommendation}` });
16630
+ }
16631
+ return { heading: "Root cause (navigation)", facts: facts.slice(0, MAX_FACTS_PER_SECTION) };
16632
+ }
16633
+ function buildDependenciesSection(graph, node) {
16634
+ const result = getTransitiveDependencies(graph, node, 2);
16635
+ if (result.total === 0) return null;
16636
+ const facts = result.dependencies.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
16637
+ text: `${d.nodeId} \u2014 ${d.edgeType} (distance ${d.distance})`,
16638
+ // Per-fact provenance: the transitive walk mixes EXTRACTED and OBSERVED
16639
+ // edges, so the tag is on the fact, not asserted for the whole section.
16640
+ provenance: d.provenance
16641
+ }));
16642
+ return { heading: "Dependencies", facts };
16643
+ }
16644
+ function buildObservedSection(graph, node) {
16645
+ const result = getObservedDependencies(graph, node);
16646
+ const facts = [];
16647
+ for (const e of result.dependencies.slice(0, MAX_FACTS_PER_SECTION)) {
16648
+ const via = e.source !== node ? ` via ${e.source}` : "";
16649
+ facts.push({
16650
+ text: `calls ${e.target}${via}${edgeSignalNote(e)}`,
16651
+ provenance: e.provenance,
16652
+ confidence: confidenceForEdge(e)
16653
+ });
16654
+ }
16655
+ if (facts.length === 0) {
16656
+ if (result.observed && result.inboundObservedCount > 0) {
16657
+ facts.push({
16658
+ text: `no outbound runtime calls, but OTel observed ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 a pure receiver`,
16659
+ provenance: import_types58.Provenance.OBSERVED
16660
+ });
16661
+ } else {
16662
+ return null;
16663
+ }
16664
+ }
16665
+ return { heading: "Runtime dependencies (OBSERVED)", facts };
16666
+ }
16667
+ function buildBlastSection(graph, node) {
16668
+ const result = getBlastRadius(graph, node);
16669
+ if (result.totalAffected === 0) return null;
16670
+ const facts = result.affectedNodes.slice(0, MAX_FACTS_PER_SECTION).map((n) => ({
16671
+ text: `${n.nodeId} (distance ${n.distance})`,
16672
+ provenance: n.edgeProvenance,
16673
+ confidence: n.confidence
16674
+ }));
16675
+ return {
16676
+ heading: `Blast radius \u2014 ${result.totalAffected} dependent${result.totalAffected === 1 ? "" : "s"}`,
16677
+ facts
16678
+ };
16679
+ }
16680
+ function buildIncidentsSection(node, incidents) {
16681
+ const relevant = incidentsForNode(node, incidents);
16682
+ if (relevant.length === 0) return null;
16683
+ const ordered = [...relevant].sort((a, b) => b.timestamp.localeCompare(a.timestamp)).slice(0, 4);
16684
+ const facts = ordered.map((ev) => ({
16685
+ text: `${ev.timestamp} \u2014 ${ev.service}: ${ev.errorMessage}`,
16686
+ // ErrorEvents are observation records — OBSERVED by definition.
16687
+ provenance: import_types58.Provenance.OBSERVED
16688
+ }));
16689
+ return { heading: `Recent incidents (OBSERVED) \u2014 ${relevant.length} recorded`, facts };
16690
+ }
16691
+ function divergenceLine(d) {
16692
+ if ((d.type === "missing-observed" || d.type === "missing-extracted") && d.column) {
16693
+ return `[${d.type}] ${d.table ?? d.source} column ${d.column} \u2014 ${d.reason}`;
16694
+ }
16695
+ return `[${d.type}] ${d.source} \u2192 ${d.target} \u2014 ${d.reason}`;
16696
+ }
16697
+ function buildDivergenceSection(graph, node) {
16698
+ const result = computeDivergences(graph, { node });
16699
+ if (result.totalAffected === 0) return null;
16700
+ const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
16701
+ text: divergenceLine(d),
16702
+ confidence: d.confidence
16703
+ // A divergence is composite by construction (EXTRACTED vs OBSERVED), so it
16704
+ // carries no single provenance — the fact leaves it unset.
16705
+ }));
16706
+ return {
16707
+ heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
16708
+ facts
16709
+ };
16710
+ }
16711
+ function buildGlobalDivergenceSection(graph) {
16712
+ const result = computeDivergences(graph);
16713
+ if (result.totalAffected === 0) {
16714
+ return {
16715
+ heading: "Divergences (EXTRACTED vs OBSERVED)",
16716
+ facts: [{ text: "None \u2014 declared code and observed runtime agree across the graph." }]
16717
+ };
16718
+ }
16719
+ const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
16720
+ text: divergenceLine(d),
16721
+ confidence: d.confidence
16722
+ // Composite by construction (EXTRACTED vs OBSERVED), so no single provenance.
16723
+ }));
16724
+ return {
16725
+ heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
16726
+ facts
16727
+ };
16728
+ }
16729
+ function buildGlobalIncidentsSection(incidents) {
16730
+ if (!incidents || incidents.length === 0) {
16731
+ return {
16732
+ heading: "Incidents across the system",
16733
+ facts: [{ text: "None recorded \u2014 the OBSERVED incident store is empty." }]
16734
+ };
16735
+ }
16736
+ const byKey = /* @__PURE__ */ new Map();
16737
+ for (const ev of incidents) {
16738
+ const key = ev.affectedNode || (0, import_types58.serviceId)(ev.service);
16739
+ const cur = byKey.get(key);
16740
+ if (!cur) {
16741
+ byKey.set(key, { key, count: 1, latest: ev.timestamp, sampleMsg: ev.errorMessage });
16742
+ } else {
16743
+ cur.count += 1;
16744
+ if (ev.timestamp > cur.latest) {
16745
+ cur.latest = ev.timestamp;
16746
+ cur.sampleMsg = ev.errorMessage;
16747
+ }
16748
+ }
16749
+ }
16750
+ 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);
16751
+ const facts = rows.map((r) => ({
16752
+ text: `${r.key} \u2014 ${r.count} incident${r.count === 1 ? "" : "s"}, latest ${r.latest}: ${r.sampleMsg}`,
16753
+ provenance: import_types58.Provenance.OBSERVED
16754
+ }));
16755
+ return {
16756
+ heading: `Incidents across the system \u2014 ${incidents.length} recorded`,
16757
+ facts
16758
+ };
16759
+ }
16760
+ function buildOverviewSections(graph, incidents) {
16761
+ const nodeByType = /* @__PURE__ */ new Map();
16762
+ const services = [];
16763
+ graph.forEachNode((_id, attrs) => {
16764
+ const node = attrs;
16765
+ nodeByType.set(node.type, (nodeByType.get(node.type) ?? 0) + 1);
16766
+ if (node.type === import_types58.NodeType.ServiceNode) services.push(node.id);
16767
+ });
16768
+ const edgeByProv = /* @__PURE__ */ new Map();
16769
+ graph.forEachEdge((_id, attrs) => {
16770
+ const p = attrs.provenance;
16771
+ edgeByProv.set(p, (edgeByProv.get(p) ?? 0) + 1);
16772
+ });
16773
+ const sections = [];
16774
+ const count = (t) => nodeByType.get(t) ?? 0;
16775
+ const shapeFacts = [
16776
+ { text: `${graph.order} nodes, ${graph.size} edges` },
16777
+ {
16778
+ text: `${count(import_types58.NodeType.ServiceNode)} services, ${count(import_types58.NodeType.FileNode)} files, ${count(import_types58.NodeType.SymbolNode)} symbols, ${count(import_types58.NodeType.DatabaseNode)} databases`
16779
+ }
16780
+ ];
16781
+ for (const p of [import_types58.Provenance.EXTRACTED, import_types58.Provenance.OBSERVED, import_types58.Provenance.INFERRED, import_types58.Provenance.STALE]) {
16782
+ const n = edgeByProv.get(p) ?? 0;
16783
+ if (n > 0) shapeFacts.push({ text: `${n} ${p} edge${n === 1 ? "" : "s"}`, provenance: p });
16784
+ }
16785
+ sections.push({ heading: "System shape", facts: shapeFacts });
16786
+ 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);
16787
+ if (depCounts.length > 0) {
16788
+ sections.push({
16789
+ heading: "Busiest services (by direct dependencies)",
16790
+ facts: depCounts.map((r) => ({
16791
+ text: `${r.s} \u2014 ${r.n} direct dependenc${r.n === 1 ? "y" : "ies"}`
16792
+ }))
16793
+ });
16794
+ }
16795
+ if (incidents && incidents.length > 0) {
16796
+ const incCount = /* @__PURE__ */ new Map();
16797
+ for (const ev of incidents) {
16798
+ const key = ev.affectedNode || (0, import_types58.serviceId)(ev.service);
16799
+ incCount.set(key, (incCount.get(key) ?? 0) + 1);
16800
+ }
16801
+ const top = [...incCount.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, MAX_FACTS_PER_SECTION);
16802
+ sections.push({
16803
+ heading: `Nodes with incidents \u2014 ${incidents.length} recorded`,
16804
+ facts: top.map(([k, n]) => ({
16805
+ text: `${k} \u2014 ${n} incident${n === 1 ? "" : "s"}`,
16806
+ provenance: import_types58.Provenance.OBSERVED
16807
+ }))
16808
+ });
16809
+ }
16810
+ const div = computeDivergences(graph);
16811
+ sections.push({
16812
+ heading: "Divergences",
16813
+ facts: [
16814
+ {
16815
+ 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.`
16816
+ }
16817
+ ]
16818
+ });
16819
+ return sections;
16820
+ }
16821
+ function buildGlobalSections(intent, graph, incidents) {
16822
+ switch (intent) {
16823
+ case "divergence":
16824
+ return [buildGlobalDivergenceSection(graph)];
16825
+ case "incidents":
16826
+ return [buildGlobalIncidentsSection(incidents)];
16827
+ case "overview":
16828
+ return buildOverviewSections(graph, incidents);
16829
+ default:
16830
+ return null;
16831
+ }
16832
+ }
16833
+ var SECTION_ORDER = {
16834
+ "root-cause": ["root-cause", "incidents", "blast", "observed", "divergence"],
16835
+ "blast-radius": ["blast", "dependencies", "observed", "incidents"],
16836
+ dependencies: ["dependencies", "observed", "blast"],
16837
+ observed: ["observed", "dependencies", "incidents"],
16838
+ incidents: ["incidents", "root-cause", "observed"],
16839
+ divergence: ["divergence", "observed", "dependencies", "incidents"],
16840
+ overview: ["dependencies", "observed", "blast", "incidents", "divergence"]
16841
+ };
16842
+ function buildSection(kind, graph, node, incidents, now) {
16843
+ switch (kind) {
16844
+ case "root-cause":
16845
+ return buildRootCauseSection(graph, node, incidents, now);
16846
+ case "dependencies":
16847
+ return buildDependenciesSection(graph, node);
16848
+ case "observed":
16849
+ return buildObservedSection(graph, node);
16850
+ case "blast":
16851
+ return buildBlastSection(graph, node);
16852
+ case "incidents":
16853
+ return buildIncidentsSection(node, incidents);
16854
+ case "divergence":
16855
+ return buildDivergenceSection(graph, node);
16856
+ }
16857
+ }
16858
+ function summarizeGlobal(intent, sections) {
16859
+ const lead = sections[0];
16860
+ switch (intent) {
16861
+ case "divergence": {
16862
+ const none = lead?.facts[0]?.text.startsWith("None") ?? true;
16863
+ return none ? "No divergences across the graph \u2014 declared code and observed runtime agree." : `${lead.heading} across the graph, highest-confidence first below.`;
16864
+ }
16865
+ case "incidents": {
16866
+ const none = lead?.facts[0]?.text.startsWith("None") ?? true;
16867
+ return none ? "No incidents recorded across the system \u2014 the OBSERVED incident store is empty." : `${lead.heading}, aggregated by node below.`;
16868
+ }
16869
+ case "overview":
16870
+ default:
16871
+ 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.`;
16872
+ }
16873
+ }
16874
+ function summarize(question, intent, matched, primary, sections, scope) {
16875
+ if (scope === "global") {
16876
+ return summarizeGlobal(intent, sections);
16877
+ }
16878
+ if (!primary) {
16879
+ 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.`;
16880
+ }
16881
+ const lead = sections[0];
16882
+ const others = matched.slice(1);
16883
+ const alsoNote = others.length ? ` Also matched: ${others.map((m) => m.nodeId).join(", ")}.` : "";
16884
+ let core;
16885
+ switch (intent) {
16886
+ case "root-cause": {
16887
+ const rc = sections.find((s) => s.heading === "Root cause (navigation)");
16888
+ if (rc) {
16889
+ core = rc.facts[0]?.text ?? "";
16890
+ } else if (lead) {
16891
+ core = `No root cause surfaced for ${primary} \u2014 it may be healthy, or the failure isn't recorded. Nearest context: ${lead.heading.toLowerCase()}.`;
16892
+ } else {
16893
+ core = `No root cause surfaced for ${primary} \u2014 it may be healthy.`;
16894
+ }
16895
+ break;
16896
+ }
16897
+ case "blast-radius":
16898
+ core = lead ? `${lead.heading} of ${primary}.` : `${primary} has no dependents \u2014 nothing else would break if it failed.`;
16899
+ break;
16900
+ case "dependencies":
16901
+ core = lead ? `${primary}: ${lead.heading.toLowerCase()} listed below.` : `${primary} has no declared dependencies in the graph.`;
16902
+ break;
16903
+ case "observed":
16904
+ core = lead ? `${primary} at runtime: ${lead.facts.length} OBSERVED fact${lead.facts.length === 1 ? "" : "s"}.` : `No runtime traffic OBSERVED for ${primary}.`;
16905
+ break;
16906
+ case "incidents":
16907
+ core = lead ? `${primary}: ${lead.heading.toLowerCase()}.` : `No incidents recorded against ${primary}.`;
16908
+ break;
16909
+ case "divergence":
16910
+ core = lead ? `${lead.heading} involving ${primary}.` : `No divergences involve ${primary} \u2014 declared and observed agree here.`;
16911
+ break;
16912
+ default:
16913
+ core = `${primary}: ${sections.length} view${sections.length === 1 ? "" : "s"} of its fused local context below.`;
16914
+ }
16915
+ return `${core}${alsoNote}`;
16916
+ }
16917
+ async function askGraph(graph, question, opts = {}) {
16918
+ const now = opts.now ?? Date.now();
16919
+ const maxNodes = opts.maxNodes ?? DEFAULT_MAX_NODES;
16920
+ const intent = classifyIntent(question);
16921
+ const matched = await resolveEntities(graph, question, opts.searchIndex, maxNodes);
16922
+ const primary = matched[0]?.nodeId;
16923
+ const sections = [];
16924
+ let scope;
16925
+ if (primary) {
16926
+ scope = "node";
16927
+ for (const kind of SECTION_ORDER[intent]) {
16928
+ const s = buildSection(kind, graph, primary, opts.incidents, now);
16929
+ if (s && s.facts.length > 0) sections.push(s);
16930
+ }
16931
+ } else {
16932
+ const global = buildGlobalSections(intent, graph, opts.incidents);
16933
+ if (global) {
16934
+ scope = "global";
16935
+ for (const s of global) if (s.facts.length > 0) sections.push(s);
16936
+ }
16937
+ }
16938
+ const answer = summarize(question, intent, matched, primary, sections, scope);
16939
+ const provSet = /* @__PURE__ */ new Set();
16940
+ for (const s of sections) for (const f of s.facts) if (f.provenance) provSet.add(f.provenance);
16941
+ const confidence = sections[0]?.facts[0]?.confidence;
16942
+ return import_types58.AskResultSchema.parse({
16943
+ question,
16944
+ intent,
16945
+ matched,
16946
+ ...primary ? { primaryNode: primary } : {},
16947
+ ...scope ? { scope } : {},
16948
+ sections,
16949
+ answer,
16950
+ ...confidence !== void 0 ? { confidence } : {},
16951
+ provenance: [...provSet]
16952
+ });
16953
+ }
16954
+
16271
16955
  // src/diff.ts
16272
16956
  init_cjs_shims();
16273
16957
  var import_node_fs35 = require("fs");
@@ -16349,7 +17033,7 @@ function canonicalJson(value) {
16349
17033
  init_cjs_shims();
16350
17034
  var import_node_fs36 = require("fs");
16351
17035
  var import_node_path70 = __toESM(require("path"), 1);
16352
- var import_types58 = require("@neat.is/types");
17036
+ var import_types59 = require("@neat.is/types");
16353
17037
  var SCHEMA_VERSION = 7;
16354
17038
  function migrateV1ToV2(payload) {
16355
17039
  const nodes = payload.graph.nodes;
@@ -16373,7 +17057,7 @@ function migrateV5ToV6(payload) {
16373
17057
  if (Array.isArray(nodes)) {
16374
17058
  for (const node of nodes) {
16375
17059
  const attrs = node.attributes;
16376
- if (!attrs || attrs.type !== import_types58.NodeType.InfraNode) continue;
17060
+ if (!attrs || attrs.type !== import_types59.NodeType.InfraNode) continue;
16377
17061
  if (attrs.kind !== "sql-table" && attrs.kind !== "supabase-table") continue;
16378
17062
  if (!Array.isArray(attrs.columns)) attrs.columns = [];
16379
17063
  }
@@ -16389,12 +17073,12 @@ function migrateV2ToV3(payload) {
16389
17073
  for (const edge of edges) {
16390
17074
  const attrs = edge.attributes;
16391
17075
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
16392
- attrs.provenance = import_types58.Provenance.OBSERVED;
17076
+ attrs.provenance = import_types59.Provenance.OBSERVED;
16393
17077
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
16394
17078
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
16395
17079
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
16396
17080
  if (type && source && target) {
16397
- const newId = (0, import_types58.observedEdgeId)(source, target, type);
17081
+ const newId = (0, import_types59.observedEdgeId)(source, target, type);
16398
17082
  attrs.id = newId;
16399
17083
  if (edge.key) edge.key = newId;
16400
17084
  }
@@ -16552,7 +17236,7 @@ init_cjs_shims();
16552
17236
  var import_node_fs37 = require("fs");
16553
17237
  var import_node_os3 = __toESM(require("os"), 1);
16554
17238
  var import_node_path72 = __toESM(require("path"), 1);
16555
- var import_types59 = require("@neat.is/types");
17239
+ var import_types60 = require("@neat.is/types");
16556
17240
  function neatHome() {
16557
17241
  const override = process.env.NEAT_HOME;
16558
17242
  if (override && override.length > 0) return import_node_path72.default.resolve(override);
@@ -16642,7 +17326,7 @@ async function readRegistry() {
16642
17326
  throw err;
16643
17327
  }
16644
17328
  const parsed = JSON.parse(raw);
16645
- return import_types59.RegistryFileSchema.parse(parsed);
17329
+ return import_types60.RegistryFileSchema.parse(parsed);
16646
17330
  }
16647
17331
  async function getProject(name) {
16648
17332
  const reg = await readRegistry();
@@ -16923,15 +17607,15 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
16923
17607
 
16924
17608
  // src/connectors/index.ts
16925
17609
  init_cjs_shims();
16926
- var import_types60 = require("@neat.is/types");
17610
+ var import_types61 = require("@neat.is/types");
16927
17611
  var NO_ENV = "unknown";
16928
17612
  function staticCallSiteFor(graph, serviceName, targetNodeId) {
16929
17613
  if (!graph.hasNode(targetNodeId)) return void 0;
16930
17614
  const sites = [];
16931
17615
  for (const edgeId of graph.inboundEdges(targetNodeId)) {
16932
17616
  const edge = graph.getEdgeAttributes(edgeId);
16933
- if (edge.provenance !== import_types60.Provenance.EXTRACTED) continue;
16934
- const parsed = (0, import_types60.parseFileId)(edge.source);
17617
+ if (edge.provenance !== import_types61.Provenance.EXTRACTED) continue;
17618
+ const parsed = (0, import_types61.parseFileId)(edge.source);
16935
17619
  if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
16936
17620
  const site = { relPath: edge.evidence.file };
16937
17621
  if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
@@ -16942,7 +17626,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
16942
17626
  function routeCallSiteFor(graph, targetNodeId) {
16943
17627
  if (!graph.hasNode(targetNodeId)) return void 0;
16944
17628
  const attrs = graph.getNodeAttributes(targetNodeId);
16945
- if (attrs.type !== import_types60.NodeType.RouteNode || !attrs.path) return void 0;
17629
+ if (attrs.type !== import_types61.NodeType.RouteNode || !attrs.path) return void 0;
16946
17630
  const site = { relPath: attrs.path };
16947
17631
  if (attrs.line !== void 0) site.line = attrs.line;
16948
17632
  return site;
@@ -17513,23 +18197,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
17513
18197
 
17514
18198
  // src/connectors/supabase/resolve.ts
17515
18199
  init_cjs_shims();
17516
- var import_types62 = require("@neat.is/types");
18200
+ var import_types63 = require("@neat.is/types");
17517
18201
  function createSupabaseResolveTarget(graph, config) {
17518
18202
  return (signal, _ctx) => {
17519
18203
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
17520
18204
  return null;
17521
18205
  }
17522
- const subResourceId = (0, import_types62.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
18206
+ const subResourceId = (0, import_types63.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
17523
18207
  if (graph.hasNode(subResourceId)) {
17524
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types62.EdgeType.CALLS };
18208
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
17525
18209
  }
17526
- const bareResourceId = (0, import_types62.infraId)(signal.targetKind, signal.targetName);
18210
+ const bareResourceId = (0, import_types63.infraId)(signal.targetKind, signal.targetName);
17527
18211
  if (graph.hasNode(bareResourceId)) {
17528
- return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types62.EdgeType.CALLS };
18212
+ return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
17529
18213
  }
17530
- const projectLevelId = (0, import_types62.infraId)("supabase", config.nodeRef);
18214
+ const projectLevelId = (0, import_types63.infraId)("supabase", config.nodeRef);
17531
18215
  if (graph.hasNode(projectLevelId)) {
17532
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types62.EdgeType.CALLS };
18216
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types63.EdgeType.CALLS };
17533
18217
  }
17534
18218
  return null;
17535
18219
  };
@@ -17622,7 +18306,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
17622
18306
 
17623
18307
  // src/connectors/railway/index.ts
17624
18308
  init_cjs_shims();
17625
- var import_types66 = require("@neat.is/types");
18309
+ var import_types67 = require("@neat.is/types");
17626
18310
 
17627
18311
  // src/connectors/railway/client.ts
17628
18312
  init_cjs_shims();
@@ -17773,7 +18457,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
17773
18457
  const out = [];
17774
18458
  graph.forEachNode((_id, attrs) => {
17775
18459
  const node = attrs;
17776
- if (node.type !== import_types66.NodeType.RouteNode) return;
18460
+ if (node.type !== import_types67.NodeType.RouteNode) return;
17777
18461
  const route = attrs;
17778
18462
  if (route.service !== serviceName) return;
17779
18463
  out.push({
@@ -17877,12 +18561,12 @@ function createRailwayResolveTarget(config) {
17877
18561
  const serviceName = config.serviceNameById[config.serviceId];
17878
18562
  if (!serviceName) return null;
17879
18563
  if (signal.targetKind === ROUTE_TARGET_KIND) {
17880
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types66.EdgeType.CALLS };
18564
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types67.EdgeType.CALLS };
17881
18565
  }
17882
18566
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
17883
18567
  const peerName = config.serviceNameById[signal.targetName];
17884
18568
  if (!peerName) return null;
17885
- return { targetNodeId: (0, import_types66.serviceId)(peerName), serviceName, edgeType: import_types66.EdgeType.CONNECTS_TO };
18569
+ return { targetNodeId: (0, import_types67.serviceId)(peerName), serviceName, edgeType: import_types67.EdgeType.CONNECTS_TO };
17886
18570
  }
17887
18571
  return null;
17888
18572
  };
@@ -18070,7 +18754,7 @@ function mapLogEntriesToSignals(entries) {
18070
18754
 
18071
18755
  // src/connectors/firebase/resolve.ts
18072
18756
  init_cjs_shims();
18073
- var import_types67 = require("@neat.is/types");
18757
+ var import_types68 = require("@neat.is/types");
18074
18758
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
18075
18759
  switch (resourceType) {
18076
18760
  case "cloud_function":
@@ -18085,7 +18769,7 @@ function routeEntriesFor(graph, serviceName) {
18085
18769
  const entries = [];
18086
18770
  graph.forEachNode((_id, attrs) => {
18087
18771
  const node = attrs;
18088
- if (node.type !== import_types67.NodeType.RouteNode) return;
18772
+ if (node.type !== import_types68.NodeType.RouteNode) return;
18089
18773
  const route = attrs;
18090
18774
  if (route.service !== serviceName) return;
18091
18775
  entries.push({
@@ -18117,7 +18801,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
18117
18801
  return {
18118
18802
  targetNodeId: match.routeNodeId,
18119
18803
  serviceName,
18120
- edgeType: import_types67.EdgeType.CALLS
18804
+ edgeType: import_types68.EdgeType.CALLS
18121
18805
  };
18122
18806
  };
18123
18807
  }
@@ -18144,7 +18828,7 @@ init_cjs_shims();
18144
18828
 
18145
18829
  // src/connectors/cloudflare/connector.ts
18146
18830
  init_cjs_shims();
18147
- var import_types69 = require("@neat.is/types");
18831
+ var import_types70 = require("@neat.is/types");
18148
18832
 
18149
18833
  // src/connectors/cloudflare/client.ts
18150
18834
  init_cjs_shims();
@@ -18308,7 +18992,7 @@ function findTaggedWorkerFileNode(graph, workerName) {
18308
18992
  graph.forEachNode((id, attrs) => {
18309
18993
  if (found) return;
18310
18994
  const a = attrs;
18311
- if (a.type === import_types69.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
18995
+ if (a.type === import_types70.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
18312
18996
  found = id;
18313
18997
  }
18314
18998
  });
@@ -18320,7 +19004,7 @@ function findMatchingRouteNode(graph, serviceName, method, path76) {
18320
19004
  graph.forEachNode((id, attrs) => {
18321
19005
  if (found) return;
18322
19006
  const a = attrs;
18323
- if (a.type !== import_types69.NodeType.RouteNode || a.service !== serviceName) return;
19007
+ if (a.type !== import_types70.NodeType.RouteNode || a.service !== serviceName) return;
18324
19008
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
18325
19009
  const routeMethod = (a.method ?? "").toUpperCase();
18326
19010
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -18339,11 +19023,11 @@ function createCloudflareResolveTarget(config, graph) {
18339
19023
  };
18340
19024
  const mapping = config.workers?.[scriptName];
18341
19025
  if (mapping) {
18342
- const wholeFileId = (0, import_types69.fileId)(mapping.service, mapping.entryFile);
19026
+ const wholeFileId = (0, import_types70.fileId)(mapping.service, mapping.entryFile);
18343
19027
  return {
18344
19028
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
18345
19029
  serviceName: mapping.service,
18346
- edgeType: import_types69.EdgeType.CALLS
19030
+ edgeType: import_types70.EdgeType.CALLS
18347
19031
  };
18348
19032
  }
18349
19033
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -18352,13 +19036,13 @@ function createCloudflareResolveTarget(config, graph) {
18352
19036
  return {
18353
19037
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
18354
19038
  serviceName: fileNode.service,
18355
- edgeType: import_types69.EdgeType.CALLS
19039
+ edgeType: import_types70.EdgeType.CALLS
18356
19040
  };
18357
19041
  }
18358
19042
  return {
18359
- targetNodeId: (0, import_types69.infraId)("cloudflare-worker", scriptName),
19043
+ targetNodeId: (0, import_types70.infraId)("cloudflare-worker", scriptName),
18360
19044
  serviceName: scriptName,
18361
- edgeType: import_types69.EdgeType.CALLS,
19045
+ edgeType: import_types70.EdgeType.CALLS,
18362
19046
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
18363
19047
  };
18364
19048
  };
@@ -18554,14 +19238,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
18554
19238
 
18555
19239
  // src/connectors/neon/resolve.ts
18556
19240
  init_cjs_shims();
18557
- var import_types73 = require("@neat.is/types");
19241
+ var import_types74 = require("@neat.is/types");
18558
19242
  function createNeonResolveTarget(config) {
18559
19243
  return (signal) => {
18560
19244
  if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
18561
19245
  return {
18562
- targetNodeId: (0, import_types73.infraId)("sql-table", signal.targetName),
19246
+ targetNodeId: (0, import_types74.infraId)("sql-table", signal.targetName),
18563
19247
  serviceName: config.serviceName,
18564
- edgeType: import_types73.EdgeType.CALLS,
19248
+ edgeType: import_types74.EdgeType.CALLS,
18565
19249
  ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
18566
19250
  };
18567
19251
  };
@@ -18742,14 +19426,14 @@ function mapLogEntriesToSignals2(entries) {
18742
19426
 
18743
19427
  // src/connectors/cloud-run/resolve.ts
18744
19428
  init_cjs_shims();
18745
- var import_types77 = require("@neat.is/types");
19429
+ var import_types78 = require("@neat.is/types");
18746
19430
  var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
18747
19431
  function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
18748
19432
  let found = null;
18749
19433
  graph.forEachNode((_id, attrs) => {
18750
19434
  if (found) return;
18751
19435
  const node = attrs;
18752
- if (node.type !== import_types77.NodeType.RouteNode) return;
19436
+ if (node.type !== import_types78.NodeType.RouteNode) return;
18753
19437
  const route = attrs;
18754
19438
  if (route.service !== serviceName || !route.pathTemplate) return;
18755
19439
  if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
@@ -18774,13 +19458,13 @@ function createCloudRunResolveTarget(graph, config) {
18774
19458
  normalizePathTemplate(path76)
18775
19459
  );
18776
19460
  if (routeNodeId) {
18777
- return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types77.EdgeType.CALLS };
19461
+ return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types78.EdgeType.CALLS };
18778
19462
  }
18779
19463
  }
18780
19464
  return {
18781
- targetNodeId: (0, import_types77.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
19465
+ targetNodeId: (0, import_types78.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
18782
19466
  serviceName: mappedService ?? gcpServiceName,
18783
- edgeType: import_types77.EdgeType.CALLS,
19467
+ edgeType: import_types78.EdgeType.CALLS,
18784
19468
  ensureInfraNode: {
18785
19469
  kind: CLOUD_RUN_SERVICE_INFRA_KIND,
18786
19470
  name: gcpServiceName,
@@ -18821,7 +19505,7 @@ function createCloudRunConnector(graph, config = {}) {
18821
19505
 
18822
19506
  // src/connectors/render/index.ts
18823
19507
  init_cjs_shims();
18824
- var import_types80 = require("@neat.is/types");
19508
+ var import_types81 = require("@neat.is/types");
18825
19509
 
18826
19510
  // src/connectors/render/types.ts
18827
19511
  init_cjs_shims();
@@ -18899,7 +19583,7 @@ function buildRenderRouteIndex(graph, serviceName) {
18899
19583
  const out = [];
18900
19584
  graph.forEachNode((_id, attrs) => {
18901
19585
  const node = attrs;
18902
- if (node.type !== import_types80.NodeType.RouteNode) return;
19586
+ if (node.type !== import_types81.NodeType.RouteNode) return;
18903
19587
  const route = attrs;
18904
19588
  if (route.service !== serviceName) return;
18905
19589
  out.push({
@@ -18984,7 +19668,7 @@ function mapRenderRequestLogsToSignals(entries, routeIndex) {
18984
19668
  function createRenderResolveTarget(config) {
18985
19669
  return (signal) => {
18986
19670
  if (signal.targetKind === ROUTE_TARGET_KIND2) {
18987
- return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types80.EdgeType.CALLS };
19671
+ return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types81.EdgeType.CALLS };
18988
19672
  }
18989
19673
  return null;
18990
19674
  };
@@ -19122,21 +19806,21 @@ function mapInsightsToSignals(rows, observedAtIso) {
19122
19806
 
19123
19807
  // src/connectors/planetscale/resolve.ts
19124
19808
  init_cjs_shims();
19125
- var import_types84 = require("@neat.is/types");
19809
+ var import_types85 = require("@neat.is/types");
19126
19810
  var PLANETSCALE_DATABASE_KIND = "planetscale-database";
19127
19811
  function createPlanetscaleResolveTarget(graph, config) {
19128
19812
  const databaseName = `${config.organization}/${config.database}`;
19129
19813
  return (signal, _ctx) => {
19130
19814
  if (signal.targetKind !== PLANETSCALE_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
19131
- const tableId = (0, import_types84.infraId)("sql-table", signal.targetName);
19815
+ const tableId = (0, import_types85.infraId)("sql-table", signal.targetName);
19132
19816
  if (graph.hasNode(tableId)) {
19133
- return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types84.EdgeType.CALLS };
19817
+ return { targetNodeId: tableId, serviceName: config.serviceName, edgeType: import_types85.EdgeType.CALLS };
19134
19818
  }
19135
- const providerId = (0, import_types84.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
19819
+ const providerId = (0, import_types85.infraId)(PLANETSCALE_DATABASE_KIND, databaseName);
19136
19820
  return {
19137
19821
  targetNodeId: providerId,
19138
19822
  serviceName: config.serviceName,
19139
- edgeType: import_types84.EdgeType.CALLS,
19823
+ edgeType: import_types85.EdgeType.CALLS,
19140
19824
  ensureInfraNode: { kind: PLANETSCALE_DATABASE_KIND, name: databaseName, provider: "planetscale" }
19141
19825
  };
19142
19826
  };
@@ -19401,7 +20085,7 @@ function mapBuildsToSignals(builds, serviceName) {
19401
20085
 
19402
20086
  // src/connectors/eas/resolve.ts
19403
20087
  init_cjs_shims();
19404
- var import_types89 = require("@neat.is/types");
20088
+ var import_types90 = require("@neat.is/types");
19405
20089
  var NO_ENV2 = "unknown";
19406
20090
  var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
19407
20091
  var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
@@ -19417,8 +20101,8 @@ function configBasenamesForPhase(phase) {
19417
20101
  function configNodeService(graph, configNodeId) {
19418
20102
  for (const edgeId of graph.inboundEdges(configNodeId)) {
19419
20103
  const edge = graph.getEdgeAttributes(edgeId);
19420
- if (edge.type !== import_types89.EdgeType.CONFIGURED_BY) continue;
19421
- const parsed = (0, import_types89.parseFileId)(edge.source);
20104
+ if (edge.type !== import_types90.EdgeType.CONFIGURED_BY) continue;
20105
+ const parsed = (0, import_types90.parseFileId)(edge.source);
19422
20106
  if (parsed) return parsed.service;
19423
20107
  }
19424
20108
  return null;
@@ -19429,7 +20113,7 @@ function findConfigNode(graph, basenames, serviceName) {
19429
20113
  graph.forEachNode((id, attrs) => {
19430
20114
  if (scoped) return;
19431
20115
  const node = attrs;
19432
- if (node.type !== import_types89.NodeType.ConfigNode) return;
20116
+ if (node.type !== import_types90.NodeType.ConfigNode) return;
19433
20117
  if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
19434
20118
  if (anyMatch === null) anyMatch = id;
19435
20119
  if (configNodeService(graph, id) === serviceName) scoped = id;
@@ -19446,13 +20130,13 @@ function createEasResolveTarget(graph) {
19446
20130
  if (basenames.length > 0) {
19447
20131
  const configNodeId = findConfigNode(graph, basenames, serviceName);
19448
20132
  if (configNodeId) {
19449
- return { targetNodeId: configNodeId, serviceName, edgeType: import_types89.EdgeType.CALLS };
20133
+ return { targetNodeId: configNodeId, serviceName, edgeType: import_types90.EdgeType.CALLS };
19450
20134
  }
19451
20135
  }
19452
20136
  return {
19453
20137
  targetNodeId: resolveFusedServiceId(graph, serviceName, NO_ENV2),
19454
20138
  serviceName,
19455
- edgeType: import_types89.EdgeType.CALLS
20139
+ edgeType: import_types90.EdgeType.CALLS
19456
20140
  };
19457
20141
  };
19458
20142
  }
@@ -20131,11 +20815,11 @@ function registerRoutes(scope, ctx) {
20131
20815
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
20132
20816
  const parsed = [];
20133
20817
  for (const c of candidates) {
20134
- const r = import_types92.DivergenceTypeSchema.safeParse(c);
20818
+ const r = import_types93.DivergenceTypeSchema.safeParse(c);
20135
20819
  if (!r.success) {
20136
20820
  return reply.code(400).send({
20137
20821
  error: `unknown divergence type "${c}"`,
20138
- allowed: import_types92.DivergenceTypeSchema.options
20822
+ allowed: import_types93.DivergenceTypeSchema.options
20139
20823
  });
20140
20824
  }
20141
20825
  parsed.push(r.data);
@@ -20377,6 +21061,18 @@ function registerRoutes(scope, ctx) {
20377
21061
  matches: matches.slice(0, safeLimit)
20378
21062
  };
20379
21063
  });
21064
+ scope.get("/graph/ask", async (req, reply) => {
21065
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
21066
+ if (!proj) return;
21067
+ const question = (req.query.q ?? "").trim();
21068
+ if (!question) return reply.code(400).send({ error: "query parameter `q` is required" });
21069
+ const epath = errorsPathFor(proj);
21070
+ const incidents = epath ? await readErrorEvents(epath) : [];
21071
+ return askGraph(proj.graph, question, {
21072
+ ...proj.searchIndex ? { searchIndex: proj.searchIndex } : {},
21073
+ incidents
21074
+ });
21075
+ });
20380
21076
  scope.get(
20381
21077
  "/graph/diff",
20382
21078
  async (req, reply) => {
@@ -20477,7 +21173,7 @@ function registerRoutes(scope, ctx) {
20477
21173
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
20478
21174
  let violations = await log.readAll();
20479
21175
  if (req.query.severity) {
20480
- const sev = import_types92.PolicySeveritySchema.safeParse(req.query.severity);
21176
+ const sev = import_types93.PolicySeveritySchema.safeParse(req.query.severity);
20481
21177
  if (!sev.success) {
20482
21178
  return reply.code(400).send({
20483
21179
  error: "invalid severity",
@@ -20516,7 +21212,7 @@ function registerRoutes(scope, ctx) {
20516
21212
  scope.post("/policies/check", async (req, reply) => {
20517
21213
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
20518
21214
  if (!proj) return;
20519
- const parsed = import_types92.PoliciesCheckBodySchema.safeParse(req.body ?? {});
21215
+ const parsed = import_types93.PoliciesCheckBodySchema.safeParse(req.body ?? {});
20520
21216
  if (!parsed.success) {
20521
21217
  return reply.code(400).send({
20522
21218
  error: "invalid /policies/check body",