@merittdev/horus 0.8.3 → 0.8.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +126 -18
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -65880,7 +65880,7 @@ init_cjs_shims();
|
|
|
65880
65880
|
|
|
65881
65881
|
// ../../packages/core/src/version.ts
|
|
65882
65882
|
init_cjs_shims();
|
|
65883
|
-
var HORUS_VERSION = true ? "0.8.
|
|
65883
|
+
var HORUS_VERSION = true ? "0.8.4" : "dev";
|
|
65884
65884
|
var PINNED_SOURCE_VERSION = "1.5.0";
|
|
65885
65885
|
|
|
65886
65886
|
// ../../packages/core/src/evidence.ts
|
|
@@ -71867,7 +71867,14 @@ function buildErrorAnalysisBody(q2, field = "event_code", mapping = MERITT_FIELD
|
|
|
71867
71867
|
sample: {
|
|
71868
71868
|
top_hits: {
|
|
71869
71869
|
size: 1,
|
|
71870
|
-
_source: [
|
|
71870
|
+
_source: [
|
|
71871
|
+
mapping.messageField,
|
|
71872
|
+
mapping.levelField,
|
|
71873
|
+
"component",
|
|
71874
|
+
"log_logger",
|
|
71875
|
+
"context",
|
|
71876
|
+
"detail"
|
|
71877
|
+
],
|
|
71871
71878
|
sort: [{ [mapping.timestampField]: { order: "desc" } }]
|
|
71872
71879
|
}
|
|
71873
71880
|
}
|
|
@@ -71881,7 +71888,21 @@ function bucketsOf(node) {
|
|
|
71881
71888
|
const n4 = node;
|
|
71882
71889
|
return n4?.["buckets"] ?? [];
|
|
71883
71890
|
}
|
|
71884
|
-
function
|
|
71891
|
+
function readLevel(src, levelField) {
|
|
71892
|
+
const raw = getField(src, levelField) ?? src[levelField];
|
|
71893
|
+
if (typeof raw === "number" && Number.isFinite(raw)) return valueToLevel(raw);
|
|
71894
|
+
if (typeof raw === "string") {
|
|
71895
|
+
const v4 = raw.trim().toLowerCase();
|
|
71896
|
+
if (v4 === "fatal" || v4 === "error" || v4 === "warn" || v4 === "info" || v4 === "debug" || v4 === "trace") {
|
|
71897
|
+
return v4;
|
|
71898
|
+
}
|
|
71899
|
+
if (v4 === "warning") return "warn";
|
|
71900
|
+
const n4 = Number(v4);
|
|
71901
|
+
if (Number.isFinite(n4)) return valueToLevel(n4);
|
|
71902
|
+
}
|
|
71903
|
+
return void 0;
|
|
71904
|
+
}
|
|
71905
|
+
function parseErrorAnalysis(resp, window2, messageField = "message", levelField) {
|
|
71885
71906
|
const res = resp;
|
|
71886
71907
|
const aggs = res["aggregations"] ?? {};
|
|
71887
71908
|
const hits = res["hits"];
|
|
@@ -71898,12 +71919,14 @@ function parseErrorAnalysis(resp, window2, messageField = "message") {
|
|
|
71898
71919
|
const sampleMessage = typeof rawMsg === "string" ? rawMsg : typeof sampleSrc["message"] === "string" ? sampleSrc["message"] : void 0;
|
|
71899
71920
|
const ctx = sampleSrc["context"];
|
|
71900
71921
|
const sampleContext = ctx !== null && typeof ctx === "object" && !Array.isArray(ctx) ? ctx : void 0;
|
|
71922
|
+
const level = levelField !== void 0 ? readLevel(sampleSrc, levelField) : void 0;
|
|
71901
71923
|
return {
|
|
71902
71924
|
key: String(b5["key"] ?? "") || "(none)",
|
|
71903
71925
|
count: typeof b5["doc_count"] === "number" ? b5["doc_count"] : 0,
|
|
71904
71926
|
firstSeen: typeof first === "string" ? first : "",
|
|
71905
71927
|
lastSeen: typeof last === "string" ? last : "",
|
|
71906
71928
|
services,
|
|
71929
|
+
...level !== void 0 ? { level } : {},
|
|
71907
71930
|
sampleMessage,
|
|
71908
71931
|
sampleComponent: typeof sampleSrc["component"] === "string" ? sampleSrc["component"] : typeof sampleSrc["log_logger"] === "string" ? sampleSrc["log_logger"] : void 0,
|
|
71909
71932
|
...sampleContext !== void 0 ? { sampleContext } : {}
|
|
@@ -72182,7 +72205,12 @@ var ElasticsearchLogsProvider = class {
|
|
|
72182
72205
|
this.resolveIndex(q2),
|
|
72183
72206
|
buildErrorAnalysisBody(current, sigField, this.mapping)
|
|
72184
72207
|
);
|
|
72185
|
-
const analysis = parseErrorAnalysis(
|
|
72208
|
+
const analysis = parseErrorAnalysis(
|
|
72209
|
+
curRes,
|
|
72210
|
+
{ from, to },
|
|
72211
|
+
this.mapping.messageField,
|
|
72212
|
+
this.mapping.levelField
|
|
72213
|
+
);
|
|
72186
72214
|
const fromMs = Date.parse(from);
|
|
72187
72215
|
const toMs = Date.parse(to);
|
|
72188
72216
|
if (!Number.isNaN(fromMs) && !Number.isNaN(toMs) && toMs > fromMs) {
|
|
@@ -92248,6 +92276,24 @@ function clamp015(n4) {
|
|
|
92248
92276
|
if (n4 > 1) return 1;
|
|
92249
92277
|
return n4;
|
|
92250
92278
|
}
|
|
92279
|
+
var SEV_ERROR = 2;
|
|
92280
|
+
var SEV_WARN = 1;
|
|
92281
|
+
var SEV_INFO = 0;
|
|
92282
|
+
function seedEmittedSeverityTier(level, code, message) {
|
|
92283
|
+
const lv = level?.toLowerCase();
|
|
92284
|
+
if (lv === "error" || lv === "fatal" || lv === "critical") return SEV_ERROR;
|
|
92285
|
+
if (lv === "warn" || lv === "warning") return SEV_WARN;
|
|
92286
|
+
if (lv === "info" || lv === "debug" || lv === "trace") return SEV_INFO;
|
|
92287
|
+
const m5 = /^([A-Z])_/.exec(code);
|
|
92288
|
+
const prefix = m5?.[1];
|
|
92289
|
+
if (prefix === "W") return SEV_WARN;
|
|
92290
|
+
if (prefix === "D" || prefix === "I" || prefix === "T") return SEV_INFO;
|
|
92291
|
+
if (prefix === "E" || prefix === "F" || prefix === "C") return SEV_ERROR;
|
|
92292
|
+
const text2 = `${code} ${message ?? ""}`.toLowerCase();
|
|
92293
|
+
if (/\b(skip|skipping|skipped|deprecat|info|debug|trace|notice)\b/.test(text2)) return SEV_INFO;
|
|
92294
|
+
if (/\bwarn(ing)?\b/.test(text2)) return SEV_WARN;
|
|
92295
|
+
return SEV_ERROR;
|
|
92296
|
+
}
|
|
92251
92297
|
function confidenceCeilingForCause(score) {
|
|
92252
92298
|
const s5 = clamp015(score);
|
|
92253
92299
|
if (s5 < 0.2) return 0.6;
|
|
@@ -92438,7 +92484,7 @@ function looksPerformance(hint) {
|
|
|
92438
92484
|
hint
|
|
92439
92485
|
);
|
|
92440
92486
|
}
|
|
92441
|
-
function buildBehavioralWalkthrough(hint, top, ctx, flows) {
|
|
92487
|
+
function buildBehavioralWalkthrough(hint, top, ctx, flows, classMethods = []) {
|
|
92442
92488
|
const shortFile = (f5) => f5.replace(/^.*?((?:src|app|lib|packages)\/.*)$/, "$1");
|
|
92443
92489
|
const isNoise = (s5) => /logger|logging|\.log\b|telemetry/i.test(s5.filePath) || /^(forcontext|getlogger|child|debug|info|warn|error|log|trace|tracer)$/i.test(s5.name);
|
|
92444
92490
|
const flow = flows.slice().sort((a2, b5) => b5.steps.length - a2.steps.length)[0];
|
|
@@ -92456,7 +92502,7 @@ function buildBehavioralWalkthrough(hint, top, ctx, flows) {
|
|
|
92456
92502
|
};
|
|
92457
92503
|
const PERSIST_RE = /(^|[._])(save|insert|upsert|persist|bulkwrite|find|findone|findmany|aggregate|delete|remove)\b|repositor|\brepo\b|\.model|entity|prisma|mongoose|typeorm|knex|\bdao\b|datasource/i;
|
|
92458
92504
|
const EXTERNAL_RE = /(^|[._])(request|publish|enqueue|dispatch|emit|notify)\b|http|axios|\bapi\b|webhook|\bqueue\b|processor|producer|gateway|\.client\b/i;
|
|
92459
|
-
const pool = dedup([...steps, ...ctx?.callees ?? []]).filter((s5) => !isNoise(s5));
|
|
92505
|
+
const pool = dedup([...steps, ...ctx?.callees ?? [], ...classMethods]).filter((s5) => !isNoise(s5));
|
|
92460
92506
|
const persistence = dedup(pool.filter((s5) => PERSIST_RE.test(s5.name) || PERSIST_RE.test(s5.filePath))).slice(0, 6).map((s5) => `${s5.name} (${shortFile(s5.filePath)})`);
|
|
92461
92507
|
const persistNames = new Set(persistence.map((p5) => p5.split(" (")[0]));
|
|
92462
92508
|
const externalCalls = dedup(
|
|
@@ -92467,9 +92513,15 @@ function buildBehavioralWalkthrough(hint, top, ctx, flows) {
|
|
|
92467
92513
|
const loc = entry2.startLine ? `${shortFile(entry2.filePath)}:${entry2.startLine}` : shortFile(entry2.filePath);
|
|
92468
92514
|
lines.push(`Entry point: \`${entry2.name}\` (${loc}).`);
|
|
92469
92515
|
}
|
|
92516
|
+
const methodList = dedup(classMethods.filter((s5) => !isNoise(s5)));
|
|
92470
92517
|
if (steps.length > 1) {
|
|
92471
92518
|
const chain = steps.slice(0, 12).map((s5) => s5.name).join(" \u2192 ");
|
|
92472
92519
|
lines.push(`Flow: ${chain}${steps.length > 12 ? " \u2192 \u2026" : ""}${flow ? ` (${flow.steps.length} steps)` : ""}.`);
|
|
92520
|
+
} else if (entry2 && methodList.length > 0) {
|
|
92521
|
+
const names = methodList.slice(0, 12).map((s5) => s5.name).join(", ");
|
|
92522
|
+
lines.push(
|
|
92523
|
+
`\`${entry2.name}\` is a class \u2014 its methods are the entry points: ${names}${methodList.length > 12 ? ", \u2026" : ""}. Trace one with \`horus explain <method>\`.`
|
|
92524
|
+
);
|
|
92473
92525
|
} else if (entry2) {
|
|
92474
92526
|
lines.push(`No multi-step execution flow was indexed for \`${entry2.name}\` \u2014 showing its direct calls.`);
|
|
92475
92527
|
}
|
|
@@ -92478,6 +92530,28 @@ function buildBehavioralWalkthrough(hint, top, ctx, flows) {
|
|
|
92478
92530
|
if (lines.length === 0) lines.push("No execution flow could be reconstructed for this query from the source graph.");
|
|
92479
92531
|
return { question: hint, entry: entry2, steps, externalCalls, persistence, narrative: lines.join("\n") };
|
|
92480
92532
|
}
|
|
92533
|
+
function isClassSymbol(sym) {
|
|
92534
|
+
return sym !== void 0 && sym.id.split(":", 1)[0]?.toLowerCase() === "class";
|
|
92535
|
+
}
|
|
92536
|
+
async function fetchClassMethods(code, cls) {
|
|
92537
|
+
const safe = (s5) => s5.replace(/["\\]/g, "");
|
|
92538
|
+
const name2 = safe(cls.name);
|
|
92539
|
+
if (!name2) return [];
|
|
92540
|
+
const file = safe(cls.filePath);
|
|
92541
|
+
try {
|
|
92542
|
+
const res = await code.cypher(
|
|
92543
|
+
`MATCH (m:Method) WHERE m.class_name = "${name2}"` + (file ? ` AND m.file_path = "${file}"` : "") + " RETURN m.id, m.name, m.file_path, m.start_line ORDER BY m.start_line LIMIT 40"
|
|
92544
|
+
);
|
|
92545
|
+
return res.rows.map((r) => ({
|
|
92546
|
+
id: String(r[0] ?? ""),
|
|
92547
|
+
name: String(r[1] ?? ""),
|
|
92548
|
+
filePath: String(r[2] ?? ""),
|
|
92549
|
+
...typeof r[3] === "number" ? { startLine: r[3] } : {}
|
|
92550
|
+
})).filter((s5) => s5.id !== "" && s5.name !== "");
|
|
92551
|
+
} catch {
|
|
92552
|
+
return [];
|
|
92553
|
+
}
|
|
92554
|
+
}
|
|
92481
92555
|
function formatRegressionCitation(recentChanges, changes, seedFile) {
|
|
92482
92556
|
const matchesSeed = (f5) => seedFile === void 0 || f5 === seedFile || seedFile.endsWith(f5) || f5.endsWith(seedFile);
|
|
92483
92557
|
const all = recentChanges?.commits ?? [];
|
|
@@ -92847,7 +92921,8 @@ async function investigate(input, deps) {
|
|
|
92847
92921
|
count: sig.count,
|
|
92848
92922
|
message: sig.sampleMessage ?? "",
|
|
92849
92923
|
evId: existingEvId,
|
|
92850
|
-
isNew: sig.isNew ?? false
|
|
92924
|
+
isNew: sig.isNew ?? false,
|
|
92925
|
+
level: sig.level ?? null
|
|
92851
92926
|
});
|
|
92852
92927
|
}
|
|
92853
92928
|
}
|
|
@@ -92894,7 +92969,8 @@ async function investigate(input, deps) {
|
|
|
92894
92969
|
count: sig.count,
|
|
92895
92970
|
message: sig.sampleMessage ?? "",
|
|
92896
92971
|
evId: ev.id,
|
|
92897
|
-
isNew: sig.isNew ?? false
|
|
92972
|
+
isNew: sig.isNew ?? false,
|
|
92973
|
+
level: sig.level ?? null
|
|
92898
92974
|
});
|
|
92899
92975
|
}
|
|
92900
92976
|
} catch {
|
|
@@ -93161,7 +93237,8 @@ async function investigate(input, deps) {
|
|
|
93161
93237
|
const matchesHint = serviceFilter.length === 0 && hintIsPerf && hintTokens.some(
|
|
93162
93238
|
(t2) => t2.length >= 4 && (panelLower.includes(t2) || labelVals.some((v4) => v4.includes(t2)))
|
|
93163
93239
|
);
|
|
93164
|
-
|
|
93240
|
+
const genericPerf = serviceFilter.length === 0 && hintIsPerf && f5.anomaly === "latency-spike";
|
|
93241
|
+
if (matchesService || matchesHint || genericPerf) latencyMetricEvIds.push(ev.id);
|
|
93165
93242
|
} else if (f5.anomaly === "queue-growth") {
|
|
93166
93243
|
const panelNorm = f5.panelTitle.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
93167
93244
|
const labelNorms = Object.values(f5.labels).map(
|
|
@@ -93483,25 +93560,45 @@ async function investigate(input, deps) {
|
|
|
93483
93560
|
});
|
|
93484
93561
|
}
|
|
93485
93562
|
if (seedEmittedJoins.length > 0 && top && seedEv) {
|
|
93486
|
-
const ordered = [...seedEmittedJoins].sort((a2, b5) =>
|
|
93563
|
+
const ordered = [...seedEmittedJoins].sort((a2, b5) => {
|
|
93564
|
+
const ta = seedEmittedSeverityTier(a2.level, a2.code, a2.message);
|
|
93565
|
+
const tb = seedEmittedSeverityTier(b5.level, b5.code, b5.message);
|
|
93566
|
+
if (ta !== tb) return tb - ta;
|
|
93567
|
+
return b5.count - a2.count;
|
|
93568
|
+
});
|
|
93487
93569
|
const lead = ordered[0];
|
|
93488
93570
|
if (lead !== void 0) {
|
|
93489
93571
|
const sourceEvidenceIds = [
|
|
93490
93572
|
.../* @__PURE__ */ new Set([...ordered.map((j3) => j3.evId), seedEv.id])
|
|
93491
93573
|
];
|
|
93574
|
+
const tier = seedEmittedSeverityTier(lead.level, lead.code, lead.message);
|
|
93492
93575
|
const volumeBoost = Math.min(0.1, Math.log10(Math.max(1, lead.count)) * 0.025);
|
|
93493
|
-
const baseScore = clamp015(0.6 + volumeBoost);
|
|
93494
93576
|
const msgClause = lead.message ? ` \u2014 "${lead.message.replace(/\s+/g, " ").trim().slice(0, 90)}"` : "";
|
|
93577
|
+
let title;
|
|
93578
|
+
let baseScore;
|
|
93579
|
+
if (tier === SEV_ERROR) {
|
|
93580
|
+
title = `Runtime error ${lead.code} (${lead.count}x in Elasticsearch) is raised by ${top.name} \u2014 the likely failure${msgClause}`;
|
|
93581
|
+
baseScore = clamp015(0.6 + volumeBoost);
|
|
93582
|
+
} else if (tier === SEV_WARN) {
|
|
93583
|
+
title = `Warning ${lead.code} (${lead.count}x in Elasticsearch) is raised by ${top.name}${msgClause}`;
|
|
93584
|
+
baseScore = clamp015(0.42 + volumeBoost);
|
|
93585
|
+
} else {
|
|
93586
|
+
title = `Diagnostic signal ${lead.code} (${lead.count}x in Elasticsearch) is logged by ${top.name} \u2014 informational, not an error${msgClause}`;
|
|
93587
|
+
baseScore = clamp015(0.3 + volumeBoost);
|
|
93588
|
+
}
|
|
93495
93589
|
causeInputs.push({
|
|
93496
93590
|
id: "cause:seed-emitted-error",
|
|
93497
|
-
title:
|
|
93498
|
-
0,
|
|
93499
|
-
220
|
|
93500
|
-
),
|
|
93591
|
+
title: title.slice(0, 220),
|
|
93501
93592
|
category: "error-correlation",
|
|
93502
93593
|
sourceEvidenceIds,
|
|
93503
93594
|
baseScore,
|
|
93504
|
-
metadata: {
|
|
93595
|
+
metadata: {
|
|
93596
|
+
blastRadius,
|
|
93597
|
+
seedEmitted: true,
|
|
93598
|
+
code: lead.code,
|
|
93599
|
+
count: lead.count,
|
|
93600
|
+
severity: tier === SEV_ERROR ? "error" : tier === SEV_WARN ? "warn" : "info"
|
|
93601
|
+
}
|
|
93505
93602
|
});
|
|
93506
93603
|
}
|
|
93507
93604
|
}
|
|
@@ -93609,7 +93706,11 @@ async function investigate(input, deps) {
|
|
|
93609
93706
|
const seedLinkedEvIds = /* @__PURE__ */ new Set([
|
|
93610
93707
|
...directLogEvIds,
|
|
93611
93708
|
...seedEv ? [seedEv.id] : [],
|
|
93612
|
-
...impactEv ? [impactEv.id] : []
|
|
93709
|
+
...impactEv ? [impactEv.id] : [],
|
|
93710
|
+
// GAP B: for a performance hint, a latency/error-rate anomaly is the structural answer,
|
|
93711
|
+
// not co-occurring noise — let the metric-latency cause headline rather than yielding to
|
|
93712
|
+
// a higher-volume but lower-severity log code.
|
|
93713
|
+
...looksPerformance(hint) ? latencyMetricEvIds : []
|
|
93613
93714
|
]);
|
|
93614
93715
|
const isLinkedToSeed = (ids2) => ids2.some((id) => seedLinkedEvIds.has(id));
|
|
93615
93716
|
const topLinkedCause = rankedCauses.find(
|
|
@@ -93638,7 +93739,14 @@ async function investigate(input, deps) {
|
|
|
93638
93739
|
}
|
|
93639
93740
|
}
|
|
93640
93741
|
}
|
|
93641
|
-
|
|
93742
|
+
let classMethods = [];
|
|
93743
|
+
if (code && isClassSymbol(top)) {
|
|
93744
|
+
const richest = flowPool.slice().sort((a2, b5) => b5.steps.length - a2.steps.length)[0];
|
|
93745
|
+
if (richest === void 0 || richest.steps.length <= 1) {
|
|
93746
|
+
classMethods = await fetchClassMethods(code, top);
|
|
93747
|
+
}
|
|
93748
|
+
}
|
|
93749
|
+
behavioral = buildBehavioralWalkthrough(hint, top, ctx ?? void 0, flowPool, classMethods);
|
|
93642
93750
|
const noisy = hasIncidentSignal ? ' (note: live error signals also exist in this codebase \u2014 rephrase as a symptom like "X failing" for a fault hunt)' : "";
|
|
93643
93751
|
summary = `${seedDisclaimer}${banner2}"${hint}" reads as a "how does it work" question \u2014 here is the code path${noisy}:
|
|
93644
93752
|
|