@neat.is/core 0.9.2-dev.20260821 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-RQQUI3NQ.js → chunk-HD5X5TWY.js} +690 -46
- package/dist/chunk-HD5X5TWY.js.map +1 -0
- package/dist/{chunk-L4SZIIER.js → chunk-TMHCS4ZY.js} +2 -2
- package/dist/cli.cjs +1148 -279
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +333 -110
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +733 -88
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +733 -88
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +732 -87
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-RQQUI3NQ.js.map +0 -1
- /package/dist/{chunk-L4SZIIER.js.map → chunk-TMHCS4ZY.js.map} +0 -0
|
@@ -3096,11 +3096,11 @@ function actixAttributeRoute(attrItem, out) {
|
|
|
3096
3096
|
if (!nameNode) return;
|
|
3097
3097
|
const macro = nameNode.type === "identifier" ? nameNode.text : nameNode.type === "scoped_identifier" ? nameNode.childForFieldName("name")?.text ?? null : null;
|
|
3098
3098
|
if (!macro) return;
|
|
3099
|
-
const
|
|
3100
|
-
if (!
|
|
3099
|
+
const tokens2 = attr.childForFieldName("arguments");
|
|
3100
|
+
if (!tokens2 || tokens2.type !== "token_tree") return;
|
|
3101
3101
|
const strings = [];
|
|
3102
|
-
for (let i = 0; i <
|
|
3103
|
-
const s = rustStringContent(
|
|
3102
|
+
for (let i = 0; i < tokens2.namedChildCount; i++) {
|
|
3103
|
+
const s = rustStringContent(tokens2.namedChild(i));
|
|
3104
3104
|
if (s !== null) strings.push(s);
|
|
3105
3105
|
}
|
|
3106
3106
|
const pathStr = strings[0];
|
|
@@ -4992,29 +4992,29 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
4992
4992
|
toPromote.push({ frontierId: id, serviceId: target });
|
|
4993
4993
|
});
|
|
4994
4994
|
let promoted = 0;
|
|
4995
|
-
for (const { frontierId: frontierId2, serviceId:
|
|
4995
|
+
for (const { frontierId: frontierId2, serviceId: serviceId16 } of toPromote) {
|
|
4996
4996
|
if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
|
|
4997
4997
|
const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
|
|
4998
4998
|
if (!gate.allowed) {
|
|
4999
4999
|
continue;
|
|
5000
5000
|
}
|
|
5001
5001
|
}
|
|
5002
|
-
rewireFrontierEdges(graph, frontierId2,
|
|
5002
|
+
rewireFrontierEdges(graph, frontierId2, serviceId16);
|
|
5003
5003
|
graph.dropNode(frontierId2);
|
|
5004
5004
|
promoted++;
|
|
5005
5005
|
}
|
|
5006
5006
|
return promoted;
|
|
5007
5007
|
}
|
|
5008
|
-
function rewireFrontierEdges(graph, frontierId2,
|
|
5008
|
+
function rewireFrontierEdges(graph, frontierId2, serviceId16) {
|
|
5009
5009
|
const inbound = [...graph.inboundEdges(frontierId2)];
|
|
5010
5010
|
const outbound = [...graph.outboundEdges(frontierId2)];
|
|
5011
5011
|
for (const edgeId of inbound) {
|
|
5012
5012
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
5013
|
-
rebuildEdge(graph, edge, edge.source,
|
|
5013
|
+
rebuildEdge(graph, edge, edge.source, serviceId16, edgeId);
|
|
5014
5014
|
}
|
|
5015
5015
|
for (const edgeId of outbound) {
|
|
5016
5016
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
5017
|
-
rebuildEdge(graph, edge,
|
|
5017
|
+
rebuildEdge(graph, edge, serviceId16, edge.target, edgeId);
|
|
5018
5018
|
}
|
|
5019
5019
|
}
|
|
5020
5020
|
function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
@@ -5373,7 +5373,7 @@ function databaseRootCauseShape(graph, origin, walk10) {
|
|
|
5373
5373
|
for (const id of walk10.path) {
|
|
5374
5374
|
const owner = resolveOwningService(graph, id);
|
|
5375
5375
|
if (!owner) continue;
|
|
5376
|
-
const { id:
|
|
5376
|
+
const { id: serviceId16, svc } = owner;
|
|
5377
5377
|
const deps = svc.dependencies ?? {};
|
|
5378
5378
|
for (const pair of candidatePairs) {
|
|
5379
5379
|
const declared = deps[pair.driver];
|
|
@@ -5386,7 +5386,7 @@ function databaseRootCauseShape(graph, origin, walk10) {
|
|
|
5386
5386
|
);
|
|
5387
5387
|
if (!result.compatible) {
|
|
5388
5388
|
return {
|
|
5389
|
-
rootCauseNode:
|
|
5389
|
+
rootCauseNode: serviceId16,
|
|
5390
5390
|
rootCauseReason: result.reason ?? "incompatible driver",
|
|
5391
5391
|
...result.minDriverVersion ? {
|
|
5392
5392
|
fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
|
|
@@ -5401,7 +5401,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
|
|
|
5401
5401
|
for (const id of walk10.path) {
|
|
5402
5402
|
const owner = resolveOwningService(graph, id);
|
|
5403
5403
|
if (!owner) continue;
|
|
5404
|
-
const { id:
|
|
5404
|
+
const { id: serviceId16, svc } = owner;
|
|
5405
5405
|
const deps = svc.dependencies ?? {};
|
|
5406
5406
|
const serviceNodeEngine = svc.nodeEngine;
|
|
5407
5407
|
for (const constraint of nodeEngineConstraints()) {
|
|
@@ -5410,7 +5410,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
|
|
|
5410
5410
|
const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
|
|
5411
5411
|
if (!result.compatible && result.reason) {
|
|
5412
5412
|
return {
|
|
5413
|
-
rootCauseNode:
|
|
5413
|
+
rootCauseNode: serviceId16,
|
|
5414
5414
|
rootCauseReason: result.reason,
|
|
5415
5415
|
...result.requiredNodeVersion ? {
|
|
5416
5416
|
fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
|
|
@@ -5425,7 +5425,7 @@ function serviceRootCauseShape(graph, _origin, walk10) {
|
|
|
5425
5425
|
const result = checkPackageConflict(conflict, declared, requiredDeclared);
|
|
5426
5426
|
if (!result.compatible && result.reason) {
|
|
5427
5427
|
return {
|
|
5428
|
-
rootCauseNode:
|
|
5428
|
+
rootCauseNode: serviceId16,
|
|
5429
5429
|
rootCauseReason: result.reason,
|
|
5430
5430
|
fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
|
|
5431
5431
|
};
|
|
@@ -5530,9 +5530,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
|
|
|
5530
5530
|
function isFailingCallEdge(e) {
|
|
5531
5531
|
return e.type === EdgeType6.CALLS && (e.signal?.errorCount ?? 0) > 0;
|
|
5532
5532
|
}
|
|
5533
|
-
function callSourcesForService(graph,
|
|
5534
|
-
const ids = [
|
|
5535
|
-
for (const edgeId of graph.outboundEdges(
|
|
5533
|
+
function callSourcesForService(graph, serviceId16) {
|
|
5534
|
+
const ids = [serviceId16];
|
|
5535
|
+
for (const edgeId of graph.outboundEdges(serviceId16)) {
|
|
5536
5536
|
const e = graph.getEdgeAttributes(edgeId);
|
|
5537
5537
|
if (e.type !== EdgeType6.CONTAINS) continue;
|
|
5538
5538
|
const tgt = graph.getNodeAttributes(e.target);
|
|
@@ -5556,9 +5556,9 @@ function failingCallDominates(e, id, curEdge, curId) {
|
|
|
5556
5556
|
}
|
|
5557
5557
|
return id < curId;
|
|
5558
5558
|
}
|
|
5559
|
-
function dominantFailingCall(graph,
|
|
5559
|
+
function dominantFailingCall(graph, serviceId16, visited) {
|
|
5560
5560
|
let best = null;
|
|
5561
|
-
for (const src of callSourcesForService(graph,
|
|
5561
|
+
for (const src of callSourcesForService(graph, serviceId16)) {
|
|
5562
5562
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
5563
5563
|
const e = graph.getEdgeAttributes(edgeId);
|
|
5564
5564
|
if (!isFailingCallEdge(e)) continue;
|
|
@@ -5597,9 +5597,9 @@ function staleCallDominates(e, id, curEdge, curId) {
|
|
|
5597
5597
|
if (ev !== cv) return ev > cv;
|
|
5598
5598
|
return id < curId;
|
|
5599
5599
|
}
|
|
5600
|
-
function dominantStaleCall(graph,
|
|
5600
|
+
function dominantStaleCall(graph, serviceId16, visited) {
|
|
5601
5601
|
const bestByCallee = /* @__PURE__ */ new Map();
|
|
5602
|
-
for (const src of callSourcesForService(graph,
|
|
5602
|
+
for (const src of callSourcesForService(graph, serviceId16)) {
|
|
5603
5603
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
5604
5604
|
const e = graph.getEdgeAttributes(edgeId);
|
|
5605
5605
|
if (e.type !== EdgeType6.CALLS) continue;
|
|
@@ -7059,9 +7059,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
|
7059
7059
|
"StatefulSet",
|
|
7060
7060
|
"DaemonSet"
|
|
7061
7061
|
]);
|
|
7062
|
-
function addAliases(graph,
|
|
7063
|
-
if (!graph.hasNode(
|
|
7064
|
-
const node = graph.getNodeAttributes(
|
|
7062
|
+
function addAliases(graph, serviceId16, candidates) {
|
|
7063
|
+
if (!graph.hasNode(serviceId16)) return;
|
|
7064
|
+
const node = graph.getNodeAttributes(serviceId16);
|
|
7065
7065
|
if (node.type !== NodeType16.ServiceNode) return;
|
|
7066
7066
|
const set = new Set(node.aliases ?? []);
|
|
7067
7067
|
for (const c of candidates) {
|
|
@@ -7071,7 +7071,7 @@ function addAliases(graph, serviceId15, candidates) {
|
|
|
7071
7071
|
}
|
|
7072
7072
|
if (set.size === 0) return;
|
|
7073
7073
|
const updated = { ...node, aliases: [...set].sort() };
|
|
7074
|
-
graph.replaceNodeAttributes(
|
|
7074
|
+
graph.replaceNodeAttributes(serviceId16, updated);
|
|
7075
7075
|
}
|
|
7076
7076
|
function indexServicesByName(services) {
|
|
7077
7077
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -7104,12 +7104,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
7104
7104
|
}
|
|
7105
7105
|
if (!compose?.services) return;
|
|
7106
7106
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
7107
|
-
const
|
|
7108
|
-
if (!
|
|
7107
|
+
const serviceId16 = serviceIndex.get(composeName);
|
|
7108
|
+
if (!serviceId16) continue;
|
|
7109
7109
|
const aliases = /* @__PURE__ */ new Set([composeName]);
|
|
7110
7110
|
if (svc.container_name) aliases.add(svc.container_name);
|
|
7111
7111
|
if (svc.hostname) aliases.add(svc.hostname);
|
|
7112
|
-
addAliases(graph,
|
|
7112
|
+
addAliases(graph, serviceId16, aliases);
|
|
7113
7113
|
}
|
|
7114
7114
|
}
|
|
7115
7115
|
var LABEL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -16331,6 +16331,638 @@ function queryLogEntries(opts) {
|
|
|
16331
16331
|
return merged;
|
|
16332
16332
|
}
|
|
16333
16333
|
|
|
16334
|
+
// src/ask.ts
|
|
16335
|
+
import { AskResultSchema, NodeType as NodeType32, Provenance as Provenance26, serviceId as serviceId14 } from "@neat.is/types";
|
|
16336
|
+
var DEFAULT_MAX_NODES = 3;
|
|
16337
|
+
var MAX_FACTS_PER_SECTION = 6;
|
|
16338
|
+
var INTENT_RULES = [
|
|
16339
|
+
{
|
|
16340
|
+
intent: "root-cause",
|
|
16341
|
+
test: /\b(why|root[\s-]?cause|failing|fail(?:s|ed)?|breaking|broke(?:n)?|crash(?:ing|ed)?|throw(?:ing|s)?|culprit|5\d\d)\b/
|
|
16342
|
+
},
|
|
16343
|
+
{
|
|
16344
|
+
intent: "blast-radius",
|
|
16345
|
+
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/
|
|
16346
|
+
},
|
|
16347
|
+
{
|
|
16348
|
+
intent: "divergence",
|
|
16349
|
+
test: /\b(diverg\w*|weird|mismatch|drift|out\s+of\s+sync|inconsistent|disagree\w*|declared\s+vs|anything\s+wrong)\b/
|
|
16350
|
+
},
|
|
16351
|
+
{
|
|
16352
|
+
intent: "incidents",
|
|
16353
|
+
test: /\b(incidents?|recent\s+(?:errors?|failures?)|error\s+history|failure\s+history)\b/
|
|
16354
|
+
},
|
|
16355
|
+
{
|
|
16356
|
+
intent: "observed",
|
|
16357
|
+
test: /\b(at\s+runtime|in\s+prod(?:uction)?|actually\s+call\w*|really\s+call\w*|observed|runtime\s+traffic)\b/
|
|
16358
|
+
},
|
|
16359
|
+
{
|
|
16360
|
+
intent: "dependencies",
|
|
16361
|
+
test: /\b(depend\w*|calls?|uses?|imports?|relies\s+on|needs?)\b/
|
|
16362
|
+
}
|
|
16363
|
+
];
|
|
16364
|
+
function classifyIntent(question) {
|
|
16365
|
+
const q = question.toLowerCase();
|
|
16366
|
+
for (const rule of INTENT_RULES) {
|
|
16367
|
+
if (rule.test.test(q)) return rule.intent;
|
|
16368
|
+
}
|
|
16369
|
+
return "overview";
|
|
16370
|
+
}
|
|
16371
|
+
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
16372
|
+
"the",
|
|
16373
|
+
"a",
|
|
16374
|
+
"an",
|
|
16375
|
+
"is",
|
|
16376
|
+
"are",
|
|
16377
|
+
"was",
|
|
16378
|
+
"were",
|
|
16379
|
+
"be",
|
|
16380
|
+
"been",
|
|
16381
|
+
"being",
|
|
16382
|
+
"of",
|
|
16383
|
+
"to",
|
|
16384
|
+
"in",
|
|
16385
|
+
"on",
|
|
16386
|
+
"at",
|
|
16387
|
+
"by",
|
|
16388
|
+
"for",
|
|
16389
|
+
"with",
|
|
16390
|
+
"and",
|
|
16391
|
+
"or",
|
|
16392
|
+
"but",
|
|
16393
|
+
"if",
|
|
16394
|
+
"as",
|
|
16395
|
+
"this",
|
|
16396
|
+
"that",
|
|
16397
|
+
"these",
|
|
16398
|
+
"those",
|
|
16399
|
+
"it",
|
|
16400
|
+
"its",
|
|
16401
|
+
"do",
|
|
16402
|
+
"does",
|
|
16403
|
+
"did",
|
|
16404
|
+
"has",
|
|
16405
|
+
"have",
|
|
16406
|
+
"had",
|
|
16407
|
+
"what",
|
|
16408
|
+
"which",
|
|
16409
|
+
"who",
|
|
16410
|
+
"whom",
|
|
16411
|
+
"how",
|
|
16412
|
+
"when",
|
|
16413
|
+
"where",
|
|
16414
|
+
"my",
|
|
16415
|
+
"me",
|
|
16416
|
+
"i",
|
|
16417
|
+
"we",
|
|
16418
|
+
"you",
|
|
16419
|
+
"they",
|
|
16420
|
+
"them",
|
|
16421
|
+
"from",
|
|
16422
|
+
"into",
|
|
16423
|
+
"about",
|
|
16424
|
+
"would",
|
|
16425
|
+
"will",
|
|
16426
|
+
"can",
|
|
16427
|
+
"could",
|
|
16428
|
+
"should",
|
|
16429
|
+
"anything",
|
|
16430
|
+
"everything",
|
|
16431
|
+
"something",
|
|
16432
|
+
"all",
|
|
16433
|
+
"any",
|
|
16434
|
+
"some",
|
|
16435
|
+
"no",
|
|
16436
|
+
"not",
|
|
16437
|
+
"so",
|
|
16438
|
+
"up",
|
|
16439
|
+
"out",
|
|
16440
|
+
"over",
|
|
16441
|
+
"get",
|
|
16442
|
+
"got",
|
|
16443
|
+
"show",
|
|
16444
|
+
"tell",
|
|
16445
|
+
"explain",
|
|
16446
|
+
"find",
|
|
16447
|
+
"give",
|
|
16448
|
+
"happen",
|
|
16449
|
+
"happens",
|
|
16450
|
+
"happening",
|
|
16451
|
+
"here",
|
|
16452
|
+
"there",
|
|
16453
|
+
"system",
|
|
16454
|
+
"code",
|
|
16455
|
+
"service",
|
|
16456
|
+
"node",
|
|
16457
|
+
"graph"
|
|
16458
|
+
]);
|
|
16459
|
+
var INTENT_WORDS = /* @__PURE__ */ new Set([
|
|
16460
|
+
"why",
|
|
16461
|
+
"root",
|
|
16462
|
+
"cause",
|
|
16463
|
+
"failing",
|
|
16464
|
+
"fail",
|
|
16465
|
+
"fails",
|
|
16466
|
+
"failed",
|
|
16467
|
+
"breaking",
|
|
16468
|
+
"broke",
|
|
16469
|
+
"broken",
|
|
16470
|
+
"crash",
|
|
16471
|
+
"crashing",
|
|
16472
|
+
"crashed",
|
|
16473
|
+
"throw",
|
|
16474
|
+
"throwing",
|
|
16475
|
+
"throws",
|
|
16476
|
+
"culprit",
|
|
16477
|
+
"blast",
|
|
16478
|
+
"radius",
|
|
16479
|
+
"break",
|
|
16480
|
+
"breaks",
|
|
16481
|
+
"impact",
|
|
16482
|
+
"downstream",
|
|
16483
|
+
"dependent",
|
|
16484
|
+
"dependents",
|
|
16485
|
+
"redeploy",
|
|
16486
|
+
"affect",
|
|
16487
|
+
"affects",
|
|
16488
|
+
"affected",
|
|
16489
|
+
"diverge",
|
|
16490
|
+
"divergence",
|
|
16491
|
+
"divergences",
|
|
16492
|
+
"weird",
|
|
16493
|
+
"mismatch",
|
|
16494
|
+
"drift",
|
|
16495
|
+
"inconsistent",
|
|
16496
|
+
"disagree",
|
|
16497
|
+
"declared",
|
|
16498
|
+
"observed",
|
|
16499
|
+
"incident",
|
|
16500
|
+
"incidents",
|
|
16501
|
+
"recent",
|
|
16502
|
+
"errors",
|
|
16503
|
+
"error",
|
|
16504
|
+
"failures",
|
|
16505
|
+
"failure",
|
|
16506
|
+
"history",
|
|
16507
|
+
"runtime",
|
|
16508
|
+
"production",
|
|
16509
|
+
"prod",
|
|
16510
|
+
"traffic",
|
|
16511
|
+
"actually",
|
|
16512
|
+
"really",
|
|
16513
|
+
"depend",
|
|
16514
|
+
"depends",
|
|
16515
|
+
"dependency",
|
|
16516
|
+
"dependencies",
|
|
16517
|
+
"call",
|
|
16518
|
+
"calls",
|
|
16519
|
+
"use",
|
|
16520
|
+
"uses",
|
|
16521
|
+
"using",
|
|
16522
|
+
"import",
|
|
16523
|
+
"imports",
|
|
16524
|
+
"relies",
|
|
16525
|
+
"rely",
|
|
16526
|
+
"needs",
|
|
16527
|
+
"need",
|
|
16528
|
+
"sync"
|
|
16529
|
+
]);
|
|
16530
|
+
function tokens(input) {
|
|
16531
|
+
const spaced = input.replace(/([a-z0-9])([A-Z])/g, "$1 $2");
|
|
16532
|
+
return (spaced.toLowerCase().match(/[a-z0-9]+/g) ?? []).filter((t) => t.length >= 2);
|
|
16533
|
+
}
|
|
16534
|
+
function queryTokens(question) {
|
|
16535
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16536
|
+
const out = [];
|
|
16537
|
+
for (const t of tokens(question)) {
|
|
16538
|
+
if (STOPWORDS.has(t) || INTENT_WORDS.has(t)) continue;
|
|
16539
|
+
if (seen.has(t)) continue;
|
|
16540
|
+
seen.add(t);
|
|
16541
|
+
out.push(t);
|
|
16542
|
+
}
|
|
16543
|
+
return out;
|
|
16544
|
+
}
|
|
16545
|
+
function nodeName(node) {
|
|
16546
|
+
return node.name ?? node.id;
|
|
16547
|
+
}
|
|
16548
|
+
function idBody(id) {
|
|
16549
|
+
const colon = id.indexOf(":");
|
|
16550
|
+
return colon >= 0 ? id.slice(colon + 1) : id;
|
|
16551
|
+
}
|
|
16552
|
+
var EMBED_MIN_SCORE = 0.35;
|
|
16553
|
+
async function resolveEntities(graph, question, searchIndex, maxNodes) {
|
|
16554
|
+
const qTokens = queryTokens(question);
|
|
16555
|
+
const normalized = question.toLowerCase();
|
|
16556
|
+
const best = /* @__PURE__ */ new Map();
|
|
16557
|
+
const consider = (cand) => {
|
|
16558
|
+
const cur = best.get(cand.nodeId);
|
|
16559
|
+
if (!cur || cand.score > cur.score) best.set(cand.nodeId, cand);
|
|
16560
|
+
};
|
|
16561
|
+
graph.forEachNode((id, attrs) => {
|
|
16562
|
+
const node = attrs;
|
|
16563
|
+
if (node.type === NodeType32.FrontierNode) return;
|
|
16564
|
+
const name = nodeName(node);
|
|
16565
|
+
const body = idBody(id);
|
|
16566
|
+
const labelTokens = /* @__PURE__ */ new Set([...tokens(body), ...tokens(name)]);
|
|
16567
|
+
if (labelTokens.size === 0) return;
|
|
16568
|
+
let matched = 0;
|
|
16569
|
+
for (const t of qTokens) if (labelTokens.has(t)) matched += 1;
|
|
16570
|
+
const named = body.length >= 2 && normalized.includes(body.toLowerCase()) || name.length >= 2 && normalized.includes(name.toLowerCase());
|
|
16571
|
+
if (matched === 0 && !named) return;
|
|
16572
|
+
const coverage = qTokens.length > 0 ? matched / qTokens.length : 0;
|
|
16573
|
+
const via = named ? "id" : matched > 0 && tokens(name).some((t) => qTokens.includes(t)) ? "label" : "token";
|
|
16574
|
+
const score = named ? Math.max(0.9, coverage) : Math.min(0.85, 0.3 + 0.7 * coverage);
|
|
16575
|
+
consider({ nodeId: id, label: name, via, score: Math.min(1, score) });
|
|
16576
|
+
});
|
|
16577
|
+
if (searchIndex) {
|
|
16578
|
+
try {
|
|
16579
|
+
const res = await searchIndex.search(question, 10);
|
|
16580
|
+
if (res.provider !== "substring") {
|
|
16581
|
+
for (const m of res.matches) {
|
|
16582
|
+
if (m.node.type === NodeType32.FrontierNode) continue;
|
|
16583
|
+
const already = best.get(m.node.id);
|
|
16584
|
+
if (!already && m.score < EMBED_MIN_SCORE) continue;
|
|
16585
|
+
consider({
|
|
16586
|
+
nodeId: m.node.id,
|
|
16587
|
+
label: nodeName(m.node),
|
|
16588
|
+
via: "embedding",
|
|
16589
|
+
score: Math.max(0, Math.min(1, m.score))
|
|
16590
|
+
});
|
|
16591
|
+
}
|
|
16592
|
+
}
|
|
16593
|
+
} catch {
|
|
16594
|
+
}
|
|
16595
|
+
}
|
|
16596
|
+
const viaRank = { id: 3, label: 2, token: 1, embedding: 0 };
|
|
16597
|
+
return [...best.values()].sort(
|
|
16598
|
+
(a, b) => b.score - a.score || viaRank[b.via] - viaRank[a.via] || a.nodeId.localeCompare(b.nodeId)
|
|
16599
|
+
).slice(0, maxNodes).map((s) => ({ nodeId: s.nodeId, label: s.label, via: s.via, score: Number(s.score.toFixed(3)) }));
|
|
16600
|
+
}
|
|
16601
|
+
function incidentsForNode(nodeId, incidents) {
|
|
16602
|
+
if (!incidents || incidents.length === 0) return [];
|
|
16603
|
+
const svc = nodeId.replace(/^service:/, "");
|
|
16604
|
+
return incidents.filter((e) => e.affectedNode === nodeId || e.service === svc);
|
|
16605
|
+
}
|
|
16606
|
+
function edgeSignalNote(e) {
|
|
16607
|
+
const bits = [];
|
|
16608
|
+
if (e.signal) {
|
|
16609
|
+
bits.push(`${e.signal.spanCount} spans`);
|
|
16610
|
+
if (e.signal.errorCount > 0) bits.push(`${e.signal.errorCount} errors`);
|
|
16611
|
+
if (e.signal.latencyMs?.p95 !== void 0) bits.push(`p95 ${Math.round(e.signal.latencyMs.p95)}ms`);
|
|
16612
|
+
} else if (e.callCount !== void 0) {
|
|
16613
|
+
bits.push(`${e.callCount} calls`);
|
|
16614
|
+
}
|
|
16615
|
+
return bits.length ? ` (${bits.join(", ")})` : "";
|
|
16616
|
+
}
|
|
16617
|
+
function buildRootCauseSection(graph, node, incidents, now) {
|
|
16618
|
+
const result = getRootCause(graph, node, void 0, incidents, { now });
|
|
16619
|
+
if (!result) return null;
|
|
16620
|
+
const lastProv = result.edgeProvenances[result.edgeProvenances.length - 1] ?? Provenance26.OBSERVED;
|
|
16621
|
+
const facts = [
|
|
16622
|
+
{
|
|
16623
|
+
text: `Root cause: ${result.rootCauseNode} \u2014 ${result.rootCauseReason}`,
|
|
16624
|
+
provenance: lastProv,
|
|
16625
|
+
confidence: result.confidence
|
|
16626
|
+
}
|
|
16627
|
+
];
|
|
16628
|
+
const candidates = result.candidates ?? [];
|
|
16629
|
+
for (const c of candidates.slice(0, 3)) {
|
|
16630
|
+
facts.push({
|
|
16631
|
+
text: `Candidate ${c.node} \u2014 ${c.classification}: ${c.reason}`,
|
|
16632
|
+
...c.provenance ? { provenance: c.provenance } : {},
|
|
16633
|
+
confidence: c.confidence
|
|
16634
|
+
});
|
|
16635
|
+
}
|
|
16636
|
+
if (result.traversalPath.length > 1) {
|
|
16637
|
+
facts.push({ text: `Traversal: ${result.traversalPath.join(" \u2190 ")}` });
|
|
16638
|
+
}
|
|
16639
|
+
if (result.fixRecommendation) {
|
|
16640
|
+
facts.push({ text: `Recommended fix: ${result.fixRecommendation}` });
|
|
16641
|
+
}
|
|
16642
|
+
return { heading: "Root cause (navigation)", facts: facts.slice(0, MAX_FACTS_PER_SECTION) };
|
|
16643
|
+
}
|
|
16644
|
+
function buildDependenciesSection(graph, node) {
|
|
16645
|
+
const result = getTransitiveDependencies(graph, node, 2);
|
|
16646
|
+
if (result.total === 0) return null;
|
|
16647
|
+
const facts = result.dependencies.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
|
|
16648
|
+
text: `${d.nodeId} \u2014 ${d.edgeType} (distance ${d.distance})`,
|
|
16649
|
+
// Per-fact provenance: the transitive walk mixes EXTRACTED and OBSERVED
|
|
16650
|
+
// edges, so the tag is on the fact, not asserted for the whole section.
|
|
16651
|
+
provenance: d.provenance
|
|
16652
|
+
}));
|
|
16653
|
+
return { heading: "Dependencies", facts };
|
|
16654
|
+
}
|
|
16655
|
+
function buildObservedSection(graph, node) {
|
|
16656
|
+
const result = getObservedDependencies(graph, node);
|
|
16657
|
+
const facts = [];
|
|
16658
|
+
for (const e of result.dependencies.slice(0, MAX_FACTS_PER_SECTION)) {
|
|
16659
|
+
const via = e.source !== node ? ` via ${e.source}` : "";
|
|
16660
|
+
facts.push({
|
|
16661
|
+
text: `calls ${e.target}${via}${edgeSignalNote(e)}`,
|
|
16662
|
+
provenance: e.provenance,
|
|
16663
|
+
confidence: confidenceForEdge(e)
|
|
16664
|
+
});
|
|
16665
|
+
}
|
|
16666
|
+
if (facts.length === 0) {
|
|
16667
|
+
if (result.observed && result.inboundObservedCount > 0) {
|
|
16668
|
+
facts.push({
|
|
16669
|
+
text: `no outbound runtime calls, but OTel observed ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 a pure receiver`,
|
|
16670
|
+
provenance: Provenance26.OBSERVED
|
|
16671
|
+
});
|
|
16672
|
+
} else {
|
|
16673
|
+
return null;
|
|
16674
|
+
}
|
|
16675
|
+
}
|
|
16676
|
+
return { heading: "Runtime dependencies (OBSERVED)", facts };
|
|
16677
|
+
}
|
|
16678
|
+
function buildBlastSection(graph, node) {
|
|
16679
|
+
const result = getBlastRadius(graph, node);
|
|
16680
|
+
if (result.totalAffected === 0) return null;
|
|
16681
|
+
const facts = result.affectedNodes.slice(0, MAX_FACTS_PER_SECTION).map((n) => ({
|
|
16682
|
+
text: `${n.nodeId} (distance ${n.distance})`,
|
|
16683
|
+
provenance: n.edgeProvenance,
|
|
16684
|
+
confidence: n.confidence
|
|
16685
|
+
}));
|
|
16686
|
+
return {
|
|
16687
|
+
heading: `Blast radius \u2014 ${result.totalAffected} dependent${result.totalAffected === 1 ? "" : "s"}`,
|
|
16688
|
+
facts
|
|
16689
|
+
};
|
|
16690
|
+
}
|
|
16691
|
+
function buildIncidentsSection(node, incidents) {
|
|
16692
|
+
const relevant = incidentsForNode(node, incidents);
|
|
16693
|
+
if (relevant.length === 0) return null;
|
|
16694
|
+
const ordered = [...relevant].sort((a, b) => b.timestamp.localeCompare(a.timestamp)).slice(0, 4);
|
|
16695
|
+
const facts = ordered.map((ev) => ({
|
|
16696
|
+
text: `${ev.timestamp} \u2014 ${ev.service}: ${ev.errorMessage}`,
|
|
16697
|
+
// ErrorEvents are observation records — OBSERVED by definition.
|
|
16698
|
+
provenance: Provenance26.OBSERVED
|
|
16699
|
+
}));
|
|
16700
|
+
return { heading: `Recent incidents (OBSERVED) \u2014 ${relevant.length} recorded`, facts };
|
|
16701
|
+
}
|
|
16702
|
+
function divergenceLine(d) {
|
|
16703
|
+
if ((d.type === "missing-observed" || d.type === "missing-extracted") && d.column) {
|
|
16704
|
+
return `[${d.type}] ${d.table ?? d.source} column ${d.column} \u2014 ${d.reason}`;
|
|
16705
|
+
}
|
|
16706
|
+
return `[${d.type}] ${d.source} \u2192 ${d.target} \u2014 ${d.reason}`;
|
|
16707
|
+
}
|
|
16708
|
+
function buildDivergenceSection(graph, node) {
|
|
16709
|
+
const result = computeDivergences(graph, { node });
|
|
16710
|
+
if (result.totalAffected === 0) return null;
|
|
16711
|
+
const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
|
|
16712
|
+
text: divergenceLine(d),
|
|
16713
|
+
confidence: d.confidence
|
|
16714
|
+
// A divergence is composite by construction (EXTRACTED vs OBSERVED), so it
|
|
16715
|
+
// carries no single provenance — the fact leaves it unset.
|
|
16716
|
+
}));
|
|
16717
|
+
return {
|
|
16718
|
+
heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
|
|
16719
|
+
facts
|
|
16720
|
+
};
|
|
16721
|
+
}
|
|
16722
|
+
function buildGlobalDivergenceSection(graph) {
|
|
16723
|
+
const result = computeDivergences(graph);
|
|
16724
|
+
if (result.totalAffected === 0) {
|
|
16725
|
+
return {
|
|
16726
|
+
heading: "Divergences (EXTRACTED vs OBSERVED)",
|
|
16727
|
+
facts: [{ text: "None \u2014 declared code and observed runtime agree across the graph." }]
|
|
16728
|
+
};
|
|
16729
|
+
}
|
|
16730
|
+
const facts = result.divergences.slice(0, MAX_FACTS_PER_SECTION).map((d) => ({
|
|
16731
|
+
text: divergenceLine(d),
|
|
16732
|
+
confidence: d.confidence
|
|
16733
|
+
// Composite by construction (EXTRACTED vs OBSERVED), so no single provenance.
|
|
16734
|
+
}));
|
|
16735
|
+
return {
|
|
16736
|
+
heading: `Divergences (EXTRACTED vs OBSERVED) \u2014 ${result.totalAffected}`,
|
|
16737
|
+
facts
|
|
16738
|
+
};
|
|
16739
|
+
}
|
|
16740
|
+
function buildGlobalIncidentsSection(incidents) {
|
|
16741
|
+
if (!incidents || incidents.length === 0) {
|
|
16742
|
+
return {
|
|
16743
|
+
heading: "Incidents across the system",
|
|
16744
|
+
facts: [{ text: "None recorded \u2014 the OBSERVED incident store is empty." }]
|
|
16745
|
+
};
|
|
16746
|
+
}
|
|
16747
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
16748
|
+
for (const ev of incidents) {
|
|
16749
|
+
const key = ev.affectedNode || serviceId14(ev.service);
|
|
16750
|
+
const cur = byKey.get(key);
|
|
16751
|
+
if (!cur) {
|
|
16752
|
+
byKey.set(key, { key, count: 1, latest: ev.timestamp, sampleMsg: ev.errorMessage });
|
|
16753
|
+
} else {
|
|
16754
|
+
cur.count += 1;
|
|
16755
|
+
if (ev.timestamp > cur.latest) {
|
|
16756
|
+
cur.latest = ev.timestamp;
|
|
16757
|
+
cur.sampleMsg = ev.errorMessage;
|
|
16758
|
+
}
|
|
16759
|
+
}
|
|
16760
|
+
}
|
|
16761
|
+
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);
|
|
16762
|
+
const facts = rows.map((r) => ({
|
|
16763
|
+
text: `${r.key} \u2014 ${r.count} incident${r.count === 1 ? "" : "s"}, latest ${r.latest}: ${r.sampleMsg}`,
|
|
16764
|
+
provenance: Provenance26.OBSERVED
|
|
16765
|
+
}));
|
|
16766
|
+
return {
|
|
16767
|
+
heading: `Incidents across the system \u2014 ${incidents.length} recorded`,
|
|
16768
|
+
facts
|
|
16769
|
+
};
|
|
16770
|
+
}
|
|
16771
|
+
function buildOverviewSections(graph, incidents) {
|
|
16772
|
+
const nodeByType = /* @__PURE__ */ new Map();
|
|
16773
|
+
const services = [];
|
|
16774
|
+
graph.forEachNode((_id, attrs) => {
|
|
16775
|
+
const node = attrs;
|
|
16776
|
+
nodeByType.set(node.type, (nodeByType.get(node.type) ?? 0) + 1);
|
|
16777
|
+
if (node.type === NodeType32.ServiceNode) services.push(node.id);
|
|
16778
|
+
});
|
|
16779
|
+
const edgeByProv = /* @__PURE__ */ new Map();
|
|
16780
|
+
graph.forEachEdge((_id, attrs) => {
|
|
16781
|
+
const p = attrs.provenance;
|
|
16782
|
+
edgeByProv.set(p, (edgeByProv.get(p) ?? 0) + 1);
|
|
16783
|
+
});
|
|
16784
|
+
const sections = [];
|
|
16785
|
+
const count = (t) => nodeByType.get(t) ?? 0;
|
|
16786
|
+
const shapeFacts = [
|
|
16787
|
+
{ text: `${graph.order} nodes, ${graph.size} edges` },
|
|
16788
|
+
{
|
|
16789
|
+
text: `${count(NodeType32.ServiceNode)} services, ${count(NodeType32.FileNode)} files, ${count(NodeType32.SymbolNode)} symbols, ${count(NodeType32.DatabaseNode)} databases`
|
|
16790
|
+
}
|
|
16791
|
+
];
|
|
16792
|
+
for (const p of [Provenance26.EXTRACTED, Provenance26.OBSERVED, Provenance26.INFERRED, Provenance26.STALE]) {
|
|
16793
|
+
const n = edgeByProv.get(p) ?? 0;
|
|
16794
|
+
if (n > 0) shapeFacts.push({ text: `${n} ${p} edge${n === 1 ? "" : "s"}`, provenance: p });
|
|
16795
|
+
}
|
|
16796
|
+
sections.push({ heading: "System shape", facts: shapeFacts });
|
|
16797
|
+
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);
|
|
16798
|
+
if (depCounts.length > 0) {
|
|
16799
|
+
sections.push({
|
|
16800
|
+
heading: "Busiest services (by direct dependencies)",
|
|
16801
|
+
facts: depCounts.map((r) => ({
|
|
16802
|
+
text: `${r.s} \u2014 ${r.n} direct dependenc${r.n === 1 ? "y" : "ies"}`
|
|
16803
|
+
}))
|
|
16804
|
+
});
|
|
16805
|
+
}
|
|
16806
|
+
if (incidents && incidents.length > 0) {
|
|
16807
|
+
const incCount = /* @__PURE__ */ new Map();
|
|
16808
|
+
for (const ev of incidents) {
|
|
16809
|
+
const key = ev.affectedNode || serviceId14(ev.service);
|
|
16810
|
+
incCount.set(key, (incCount.get(key) ?? 0) + 1);
|
|
16811
|
+
}
|
|
16812
|
+
const top = [...incCount.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, MAX_FACTS_PER_SECTION);
|
|
16813
|
+
sections.push({
|
|
16814
|
+
heading: `Nodes with incidents \u2014 ${incidents.length} recorded`,
|
|
16815
|
+
facts: top.map(([k, n]) => ({
|
|
16816
|
+
text: `${k} \u2014 ${n} incident${n === 1 ? "" : "s"}`,
|
|
16817
|
+
provenance: Provenance26.OBSERVED
|
|
16818
|
+
}))
|
|
16819
|
+
});
|
|
16820
|
+
}
|
|
16821
|
+
const div = computeDivergences(graph);
|
|
16822
|
+
sections.push({
|
|
16823
|
+
heading: "Divergences",
|
|
16824
|
+
facts: [
|
|
16825
|
+
{
|
|
16826
|
+
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.`
|
|
16827
|
+
}
|
|
16828
|
+
]
|
|
16829
|
+
});
|
|
16830
|
+
return sections;
|
|
16831
|
+
}
|
|
16832
|
+
function buildGlobalSections(intent, graph, incidents) {
|
|
16833
|
+
switch (intent) {
|
|
16834
|
+
case "divergence":
|
|
16835
|
+
return [buildGlobalDivergenceSection(graph)];
|
|
16836
|
+
case "incidents":
|
|
16837
|
+
return [buildGlobalIncidentsSection(incidents)];
|
|
16838
|
+
case "overview":
|
|
16839
|
+
return buildOverviewSections(graph, incidents);
|
|
16840
|
+
default:
|
|
16841
|
+
return null;
|
|
16842
|
+
}
|
|
16843
|
+
}
|
|
16844
|
+
var SECTION_ORDER = {
|
|
16845
|
+
"root-cause": ["root-cause", "incidents", "blast", "observed", "divergence"],
|
|
16846
|
+
"blast-radius": ["blast", "dependencies", "observed", "incidents"],
|
|
16847
|
+
dependencies: ["dependencies", "observed", "blast"],
|
|
16848
|
+
observed: ["observed", "dependencies", "incidents"],
|
|
16849
|
+
incidents: ["incidents", "root-cause", "observed"],
|
|
16850
|
+
divergence: ["divergence", "observed", "dependencies", "incidents"],
|
|
16851
|
+
overview: ["dependencies", "observed", "blast", "incidents", "divergence"]
|
|
16852
|
+
};
|
|
16853
|
+
function buildSection(kind, graph, node, incidents, now) {
|
|
16854
|
+
switch (kind) {
|
|
16855
|
+
case "root-cause":
|
|
16856
|
+
return buildRootCauseSection(graph, node, incidents, now);
|
|
16857
|
+
case "dependencies":
|
|
16858
|
+
return buildDependenciesSection(graph, node);
|
|
16859
|
+
case "observed":
|
|
16860
|
+
return buildObservedSection(graph, node);
|
|
16861
|
+
case "blast":
|
|
16862
|
+
return buildBlastSection(graph, node);
|
|
16863
|
+
case "incidents":
|
|
16864
|
+
return buildIncidentsSection(node, incidents);
|
|
16865
|
+
case "divergence":
|
|
16866
|
+
return buildDivergenceSection(graph, node);
|
|
16867
|
+
}
|
|
16868
|
+
}
|
|
16869
|
+
function summarizeGlobal(intent, sections) {
|
|
16870
|
+
const lead = sections[0];
|
|
16871
|
+
switch (intent) {
|
|
16872
|
+
case "divergence": {
|
|
16873
|
+
const none = lead?.facts[0]?.text.startsWith("None") ?? true;
|
|
16874
|
+
return none ? "No divergences across the graph \u2014 declared code and observed runtime agree." : `${lead.heading} across the graph, highest-confidence first below.`;
|
|
16875
|
+
}
|
|
16876
|
+
case "incidents": {
|
|
16877
|
+
const none = lead?.facts[0]?.text.startsWith("None") ?? true;
|
|
16878
|
+
return none ? "No incidents recorded across the system \u2014 the OBSERVED incident store is empty." : `${lead.heading}, aggregated by node below.`;
|
|
16879
|
+
}
|
|
16880
|
+
case "overview":
|
|
16881
|
+
default:
|
|
16882
|
+
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.`;
|
|
16883
|
+
}
|
|
16884
|
+
}
|
|
16885
|
+
function summarize(question, intent, matched, primary, sections, scope) {
|
|
16886
|
+
if (scope === "global") {
|
|
16887
|
+
return summarizeGlobal(intent, sections);
|
|
16888
|
+
}
|
|
16889
|
+
if (!primary) {
|
|
16890
|
+
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.`;
|
|
16891
|
+
}
|
|
16892
|
+
const lead = sections[0];
|
|
16893
|
+
const others = matched.slice(1);
|
|
16894
|
+
const alsoNote = others.length ? ` Also matched: ${others.map((m) => m.nodeId).join(", ")}.` : "";
|
|
16895
|
+
let core;
|
|
16896
|
+
switch (intent) {
|
|
16897
|
+
case "root-cause": {
|
|
16898
|
+
const rc = sections.find((s) => s.heading === "Root cause (navigation)");
|
|
16899
|
+
if (rc) {
|
|
16900
|
+
core = rc.facts[0]?.text ?? "";
|
|
16901
|
+
} else if (lead) {
|
|
16902
|
+
core = `No root cause surfaced for ${primary} \u2014 it may be healthy, or the failure isn't recorded. Nearest context: ${lead.heading.toLowerCase()}.`;
|
|
16903
|
+
} else {
|
|
16904
|
+
core = `No root cause surfaced for ${primary} \u2014 it may be healthy.`;
|
|
16905
|
+
}
|
|
16906
|
+
break;
|
|
16907
|
+
}
|
|
16908
|
+
case "blast-radius":
|
|
16909
|
+
core = lead ? `${lead.heading} of ${primary}.` : `${primary} has no dependents \u2014 nothing else would break if it failed.`;
|
|
16910
|
+
break;
|
|
16911
|
+
case "dependencies":
|
|
16912
|
+
core = lead ? `${primary}: ${lead.heading.toLowerCase()} listed below.` : `${primary} has no declared dependencies in the graph.`;
|
|
16913
|
+
break;
|
|
16914
|
+
case "observed":
|
|
16915
|
+
core = lead ? `${primary} at runtime: ${lead.facts.length} OBSERVED fact${lead.facts.length === 1 ? "" : "s"}.` : `No runtime traffic OBSERVED for ${primary}.`;
|
|
16916
|
+
break;
|
|
16917
|
+
case "incidents":
|
|
16918
|
+
core = lead ? `${primary}: ${lead.heading.toLowerCase()}.` : `No incidents recorded against ${primary}.`;
|
|
16919
|
+
break;
|
|
16920
|
+
case "divergence":
|
|
16921
|
+
core = lead ? `${lead.heading} involving ${primary}.` : `No divergences involve ${primary} \u2014 declared and observed agree here.`;
|
|
16922
|
+
break;
|
|
16923
|
+
default:
|
|
16924
|
+
core = `${primary}: ${sections.length} view${sections.length === 1 ? "" : "s"} of its fused local context below.`;
|
|
16925
|
+
}
|
|
16926
|
+
return `${core}${alsoNote}`;
|
|
16927
|
+
}
|
|
16928
|
+
async function askGraph(graph, question, opts = {}) {
|
|
16929
|
+
const now = opts.now ?? Date.now();
|
|
16930
|
+
const maxNodes = opts.maxNodes ?? DEFAULT_MAX_NODES;
|
|
16931
|
+
const intent = classifyIntent(question);
|
|
16932
|
+
const matched = await resolveEntities(graph, question, opts.searchIndex, maxNodes);
|
|
16933
|
+
const primary = matched[0]?.nodeId;
|
|
16934
|
+
const sections = [];
|
|
16935
|
+
let scope;
|
|
16936
|
+
if (primary) {
|
|
16937
|
+
scope = "node";
|
|
16938
|
+
for (const kind of SECTION_ORDER[intent]) {
|
|
16939
|
+
const s = buildSection(kind, graph, primary, opts.incidents, now);
|
|
16940
|
+
if (s && s.facts.length > 0) sections.push(s);
|
|
16941
|
+
}
|
|
16942
|
+
} else {
|
|
16943
|
+
const global = buildGlobalSections(intent, graph, opts.incidents);
|
|
16944
|
+
if (global) {
|
|
16945
|
+
scope = "global";
|
|
16946
|
+
for (const s of global) if (s.facts.length > 0) sections.push(s);
|
|
16947
|
+
}
|
|
16948
|
+
}
|
|
16949
|
+
const answer = summarize(question, intent, matched, primary, sections, scope);
|
|
16950
|
+
const provSet = /* @__PURE__ */ new Set();
|
|
16951
|
+
for (const s of sections) for (const f of s.facts) if (f.provenance) provSet.add(f.provenance);
|
|
16952
|
+
const confidence = sections[0]?.facts[0]?.confidence;
|
|
16953
|
+
return AskResultSchema.parse({
|
|
16954
|
+
question,
|
|
16955
|
+
intent,
|
|
16956
|
+
matched,
|
|
16957
|
+
...primary ? { primaryNode: primary } : {},
|
|
16958
|
+
...scope ? { scope } : {},
|
|
16959
|
+
sections,
|
|
16960
|
+
answer,
|
|
16961
|
+
...confidence !== void 0 ? { confidence } : {},
|
|
16962
|
+
provenance: [...provSet]
|
|
16963
|
+
});
|
|
16964
|
+
}
|
|
16965
|
+
|
|
16334
16966
|
// src/streaming.ts
|
|
16335
16967
|
var SSE_HEARTBEAT_MS = 3e4;
|
|
16336
16968
|
var SSE_BACKPRESSURE_CAP = 1e3;
|
|
@@ -16713,14 +17345,14 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
|
|
|
16713
17345
|
}
|
|
16714
17346
|
|
|
16715
17347
|
// src/connectors/index.ts
|
|
16716
|
-
import { NodeType as
|
|
17348
|
+
import { NodeType as NodeType33, parseFileId as parseFileId2, Provenance as Provenance27 } from "@neat.is/types";
|
|
16717
17349
|
var NO_ENV = "unknown";
|
|
16718
17350
|
function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
16719
17351
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
16720
17352
|
const sites = [];
|
|
16721
17353
|
for (const edgeId of graph.inboundEdges(targetNodeId)) {
|
|
16722
17354
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
16723
|
-
if (edge.provenance !==
|
|
17355
|
+
if (edge.provenance !== Provenance27.EXTRACTED) continue;
|
|
16724
17356
|
const parsed = parseFileId2(edge.source);
|
|
16725
17357
|
if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
|
|
16726
17358
|
const site = { relPath: edge.evidence.file };
|
|
@@ -16732,7 +17364,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
|
16732
17364
|
function routeCallSiteFor(graph, targetNodeId) {
|
|
16733
17365
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
16734
17366
|
const attrs = graph.getNodeAttributes(targetNodeId);
|
|
16735
|
-
if (attrs.type !==
|
|
17367
|
+
if (attrs.type !== NodeType33.RouteNode || !attrs.path) return void 0;
|
|
16736
17368
|
const site = { relPath: attrs.path };
|
|
16737
17369
|
if (attrs.line !== void 0) site.line = attrs.line;
|
|
16738
17370
|
return site;
|
|
@@ -17437,7 +18069,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
17437
18069
|
}
|
|
17438
18070
|
|
|
17439
18071
|
// src/connectors/railway/index.ts
|
|
17440
|
-
import { EdgeType as EdgeType27, NodeType as
|
|
18072
|
+
import { EdgeType as EdgeType27, NodeType as NodeType34, serviceId as serviceId15 } from "@neat.is/types";
|
|
17441
18073
|
|
|
17442
18074
|
// src/connectors/railway/client.ts
|
|
17443
18075
|
var DEFAULT_RAILWAY_API_URL = "https://backboard.railway.com/graphql/v2";
|
|
@@ -17587,7 +18219,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
17587
18219
|
const out = [];
|
|
17588
18220
|
graph.forEachNode((_id, attrs) => {
|
|
17589
18221
|
const node = attrs;
|
|
17590
|
-
if (node.type !==
|
|
18222
|
+
if (node.type !== NodeType34.RouteNode) return;
|
|
17591
18223
|
const route = attrs;
|
|
17592
18224
|
if (route.service !== serviceName) return;
|
|
17593
18225
|
out.push({
|
|
@@ -17696,7 +18328,7 @@ function createRailwayResolveTarget(config) {
|
|
|
17696
18328
|
if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
|
|
17697
18329
|
const peerName = config.serviceNameById[signal.targetName];
|
|
17698
18330
|
if (!peerName) return null;
|
|
17699
|
-
return { targetNodeId:
|
|
18331
|
+
return { targetNodeId: serviceId15(peerName), serviceName, edgeType: EdgeType27.CONNECTS_TO };
|
|
17700
18332
|
}
|
|
17701
18333
|
return null;
|
|
17702
18334
|
};
|
|
@@ -17878,7 +18510,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
17878
18510
|
}
|
|
17879
18511
|
|
|
17880
18512
|
// src/connectors/firebase/resolve.ts
|
|
17881
|
-
import { NodeType as
|
|
18513
|
+
import { NodeType as NodeType35, EdgeType as EdgeType28 } from "@neat.is/types";
|
|
17882
18514
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
17883
18515
|
switch (resourceType) {
|
|
17884
18516
|
case "cloud_function":
|
|
@@ -17893,7 +18525,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
17893
18525
|
const entries = [];
|
|
17894
18526
|
graph.forEachNode((_id, attrs) => {
|
|
17895
18527
|
const node = attrs;
|
|
17896
|
-
if (node.type !==
|
|
18528
|
+
if (node.type !== NodeType35.RouteNode) return;
|
|
17897
18529
|
const route = attrs;
|
|
17898
18530
|
if (route.service !== serviceName) return;
|
|
17899
18531
|
entries.push({
|
|
@@ -17948,7 +18580,7 @@ function createFirebaseConnector(graph, serviceMap) {
|
|
|
17948
18580
|
}
|
|
17949
18581
|
|
|
17950
18582
|
// src/connectors/cloudflare/connector.ts
|
|
17951
|
-
import { EdgeType as EdgeType29, NodeType as
|
|
18583
|
+
import { EdgeType as EdgeType29, NodeType as NodeType36, fileId as fileId5, infraId as infraId22 } from "@neat.is/types";
|
|
17952
18584
|
|
|
17953
18585
|
// src/connectors/cloudflare/client.ts
|
|
17954
18586
|
import { randomUUID } from "crypto";
|
|
@@ -18107,7 +18739,7 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
18107
18739
|
graph.forEachNode((id, attrs) => {
|
|
18108
18740
|
if (found) return;
|
|
18109
18741
|
const a = attrs;
|
|
18110
|
-
if (a.type ===
|
|
18742
|
+
if (a.type === NodeType36.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
18111
18743
|
found = id;
|
|
18112
18744
|
}
|
|
18113
18745
|
});
|
|
@@ -18119,7 +18751,7 @@ function findMatchingRouteNode(graph, serviceName, method, path72) {
|
|
|
18119
18751
|
graph.forEachNode((id, attrs) => {
|
|
18120
18752
|
if (found) return;
|
|
18121
18753
|
const a = attrs;
|
|
18122
|
-
if (a.type !==
|
|
18754
|
+
if (a.type !== NodeType36.RouteNode || a.service !== serviceName) return;
|
|
18123
18755
|
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
18124
18756
|
const routeMethod = (a.method ?? "").toUpperCase();
|
|
18125
18757
|
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
@@ -18518,14 +19150,14 @@ function mapLogEntriesToSignals2(entries) {
|
|
|
18518
19150
|
}
|
|
18519
19151
|
|
|
18520
19152
|
// src/connectors/cloud-run/resolve.ts
|
|
18521
|
-
import { EdgeType as EdgeType31, NodeType as
|
|
19153
|
+
import { EdgeType as EdgeType31, NodeType as NodeType37, infraId as infraId24 } from "@neat.is/types";
|
|
18522
19154
|
var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
|
|
18523
19155
|
function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
|
|
18524
19156
|
let found = null;
|
|
18525
19157
|
graph.forEachNode((_id, attrs) => {
|
|
18526
19158
|
if (found) return;
|
|
18527
19159
|
const node = attrs;
|
|
18528
|
-
if (node.type !==
|
|
19160
|
+
if (node.type !== NodeType37.RouteNode) return;
|
|
18529
19161
|
const route = attrs;
|
|
18530
19162
|
if (route.service !== serviceName || !route.pathTemplate) return;
|
|
18531
19163
|
if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
|
|
@@ -18596,7 +19228,7 @@ function createCloudRunConnector(graph, config = {}) {
|
|
|
18596
19228
|
}
|
|
18597
19229
|
|
|
18598
19230
|
// src/connectors/render/index.ts
|
|
18599
|
-
import { EdgeType as EdgeType32, NodeType as
|
|
19231
|
+
import { EdgeType as EdgeType32, NodeType as NodeType38 } from "@neat.is/types";
|
|
18600
19232
|
|
|
18601
19233
|
// src/connectors/render/types.ts
|
|
18602
19234
|
function readRenderToken(credentials) {
|
|
@@ -18672,7 +19304,7 @@ function buildRenderRouteIndex(graph, serviceName) {
|
|
|
18672
19304
|
const out = [];
|
|
18673
19305
|
graph.forEachNode((_id, attrs) => {
|
|
18674
19306
|
const node = attrs;
|
|
18675
|
-
if (node.type !==
|
|
19307
|
+
if (node.type !== NodeType38.RouteNode) return;
|
|
18676
19308
|
const route = attrs;
|
|
18677
19309
|
if (route.service !== serviceName) return;
|
|
18678
19310
|
out.push({
|
|
@@ -19156,7 +19788,7 @@ function mapBuildsToSignals(builds, serviceName) {
|
|
|
19156
19788
|
}
|
|
19157
19789
|
|
|
19158
19790
|
// src/connectors/eas/resolve.ts
|
|
19159
|
-
import { EdgeType as EdgeType34, NodeType as
|
|
19791
|
+
import { EdgeType as EdgeType34, NodeType as NodeType39, parseFileId as parseFileId3 } from "@neat.is/types";
|
|
19160
19792
|
var NO_ENV2 = "unknown";
|
|
19161
19793
|
var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
|
|
19162
19794
|
var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
|
|
@@ -19184,7 +19816,7 @@ function findConfigNode(graph, basenames, serviceName) {
|
|
|
19184
19816
|
graph.forEachNode((id, attrs) => {
|
|
19185
19817
|
if (scoped) return;
|
|
19186
19818
|
const node = attrs;
|
|
19187
|
-
if (node.type !==
|
|
19819
|
+
if (node.type !== NodeType39.ConfigNode) return;
|
|
19188
19820
|
if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
|
|
19189
19821
|
if (anyMatch === null) anyMatch = id;
|
|
19190
19822
|
if (configNodeService(graph, id) === serviceName) scoped = id;
|
|
@@ -20257,6 +20889,18 @@ function registerRoutes(scope, ctx) {
|
|
|
20257
20889
|
matches: matches.slice(0, safeLimit)
|
|
20258
20890
|
};
|
|
20259
20891
|
});
|
|
20892
|
+
scope.get("/graph/ask", async (req, reply) => {
|
|
20893
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
20894
|
+
if (!proj) return;
|
|
20895
|
+
const question = (req.query.q ?? "").trim();
|
|
20896
|
+
if (!question) return reply.code(400).send({ error: "query parameter `q` is required" });
|
|
20897
|
+
const epath = errorsPathFor(proj);
|
|
20898
|
+
const incidents = epath ? await readErrorEvents(epath) : [];
|
|
20899
|
+
return askGraph(proj.graph, question, {
|
|
20900
|
+
...proj.searchIndex ? { searchIndex: proj.searchIndex } : {},
|
|
20901
|
+
incidents
|
|
20902
|
+
});
|
|
20903
|
+
});
|
|
20260
20904
|
scope.get(
|
|
20261
20905
|
"/graph/diff",
|
|
20262
20906
|
async (req, reply) => {
|
|
@@ -20796,4 +21440,4 @@ export {
|
|
|
20796
21440
|
deprovisionConnector,
|
|
20797
21441
|
buildApi
|
|
20798
21442
|
};
|
|
20799
|
-
//# sourceMappingURL=chunk-
|
|
21443
|
+
//# sourceMappingURL=chunk-HD5X5TWY.js.map
|