@merittdev/horus 0.7.0 → 0.8.0
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 +232 -33
- 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.
|
|
65883
|
+
var HORUS_VERSION = true ? "0.8.0" : "dev";
|
|
65884
65884
|
var PINNED_SOURCE_VERSION = "1.0.7";
|
|
65885
65885
|
|
|
65886
65886
|
// ../../packages/core/src/evidence.ts
|
|
@@ -71139,7 +71139,9 @@ var SourceCodeProvider = class {
|
|
|
71139
71139
|
const exactSymbols = exactRows.map((row) => ({
|
|
71140
71140
|
id: String(row[0] ?? ""),
|
|
71141
71141
|
name: String(row[1] ?? ""),
|
|
71142
|
-
filePath: String(row[2] ?? "")
|
|
71142
|
+
filePath: String(row[2] ?? ""),
|
|
71143
|
+
score: 1
|
|
71144
|
+
// an exact-name Cypher hit is authoritative
|
|
71143
71145
|
}));
|
|
71144
71146
|
const exactIds = new Set(exactSymbols.map((s5) => s5.id));
|
|
71145
71147
|
const ql = query.toLowerCase();
|
|
@@ -71154,7 +71156,7 @@ var SourceCodeProvider = class {
|
|
|
71154
71156
|
semantic.sort((a2, b5) => b5.rank - a2.rank || b5.r.score - a2.r.score);
|
|
71155
71157
|
const combined = [
|
|
71156
71158
|
...exactSymbols,
|
|
71157
|
-
...semantic.map(({ r }) => ({ id: r.nodeId, name: r.name, filePath: r.filePath }))
|
|
71159
|
+
...semantic.map(({ r }) => ({ id: r.nodeId, name: r.name, filePath: r.filePath, score: r.score }))
|
|
71158
71160
|
];
|
|
71159
71161
|
return this.hydrateLines(combined.slice(0, limit));
|
|
71160
71162
|
}
|
|
@@ -71210,6 +71212,7 @@ var SourceCodeProvider = class {
|
|
|
71210
71212
|
const isDead = Boolean(node?.[8]);
|
|
71211
71213
|
const content = node?.[9];
|
|
71212
71214
|
const snippet = typeof content === "string" ? content.slice(0, 600) : void 0;
|
|
71215
|
+
const sourceBody = typeof content === "string" ? content.slice(0, 2e4) : void 0;
|
|
71213
71216
|
const callees = calleeRows.map((row) => this.cypherRowToSymbol(row, 0, 1, 2, 3, 4));
|
|
71214
71217
|
const callers = callerRows.map((row) => this.cypherRowToSymbol(row, 0, 1, 2, 3, 4));
|
|
71215
71218
|
const usesType = usesTypeRows.map((row) => this.cypherRowToSymbol(row, 0, 1, 2, 3));
|
|
@@ -71236,6 +71239,7 @@ var SourceCodeProvider = class {
|
|
|
71236
71239
|
return {
|
|
71237
71240
|
symbol,
|
|
71238
71241
|
snippet,
|
|
71242
|
+
...sourceBody !== void 0 ? { sourceBody } : {},
|
|
71239
71243
|
callers,
|
|
71240
71244
|
callees,
|
|
71241
71245
|
imports,
|
|
@@ -71780,8 +71784,12 @@ function shortTs(iso) {
|
|
|
71780
71784
|
}
|
|
71781
71785
|
function buildErrorAnalysisBody(q2, field = "event_code", mapping = MERITT_FIELD_MAPPING) {
|
|
71782
71786
|
const filters = [];
|
|
71783
|
-
|
|
71784
|
-
|
|
71787
|
+
if (q2.eventCode === void 0) {
|
|
71788
|
+
const minLevel = q2.level !== void 0 && levelToValue(q2.level) > 50 ? q2.level : "error";
|
|
71789
|
+
filters.push(buildLevelFilter(mapping, minLevel));
|
|
71790
|
+
} else if (q2.level !== void 0 && levelToValue(q2.level) > 50) {
|
|
71791
|
+
filters.push(buildLevelFilter(mapping, q2.level));
|
|
71792
|
+
}
|
|
71785
71793
|
if (q2.from !== void 0 || q2.to !== void 0) {
|
|
71786
71794
|
const rangeClause = {};
|
|
71787
71795
|
if (q2.from !== void 0) rangeClause["gte"] = q2.from;
|
|
@@ -89233,18 +89241,58 @@ async function createLocalDb(opts) {
|
|
|
89233
89241
|
} catch {
|
|
89234
89242
|
}
|
|
89235
89243
|
assertEmbeddedAssetsPresent();
|
|
89236
|
-
const
|
|
89237
|
-
|
|
89238
|
-
|
|
89239
|
-
|
|
89240
|
-
|
|
89241
|
-
db,
|
|
89242
|
-
|
|
89243
|
-
|
|
89244
|
-
|
|
89244
|
+
const releaseLock = await acquireDbLock(dataDir);
|
|
89245
|
+
try {
|
|
89246
|
+
const client = new Ve3(dataDir);
|
|
89247
|
+
await client.waitReady;
|
|
89248
|
+
await applyEmbeddedMigrations(client);
|
|
89249
|
+
const db = drizzle2(client, { schema: schema_exports });
|
|
89250
|
+
return {
|
|
89251
|
+
db,
|
|
89252
|
+
sql: {
|
|
89253
|
+
end: async () => {
|
|
89254
|
+
await client.close();
|
|
89255
|
+
releaseLock();
|
|
89256
|
+
}
|
|
89245
89257
|
}
|
|
89246
|
-
}
|
|
89258
|
+
};
|
|
89259
|
+
} catch (e) {
|
|
89260
|
+
releaseLock();
|
|
89261
|
+
throw e;
|
|
89262
|
+
}
|
|
89263
|
+
}
|
|
89264
|
+
async function acquireDbLock(dataDir) {
|
|
89265
|
+
const lockPath = `${dataDir}.lock`;
|
|
89266
|
+
const STALE_MS = 6e4;
|
|
89267
|
+
const TIMEOUT_MS = 3e4;
|
|
89268
|
+
const start2 = Date.now();
|
|
89269
|
+
const noop3 = () => {
|
|
89247
89270
|
};
|
|
89271
|
+
for (; ; ) {
|
|
89272
|
+
try {
|
|
89273
|
+
const fd = (0, import_node_fs4.openSync)(lockPath, "wx");
|
|
89274
|
+
(0, import_node_fs4.writeSync)(fd, `${process.pid} ${Date.now()}`);
|
|
89275
|
+
(0, import_node_fs4.closeSync)(fd);
|
|
89276
|
+
return () => {
|
|
89277
|
+
try {
|
|
89278
|
+
(0, import_node_fs4.unlinkSync)(lockPath);
|
|
89279
|
+
} catch {
|
|
89280
|
+
}
|
|
89281
|
+
};
|
|
89282
|
+
} catch (e) {
|
|
89283
|
+
if (e.code !== "EEXIST") return noop3;
|
|
89284
|
+
try {
|
|
89285
|
+
if (Date.now() - (0, import_node_fs4.statSync)(lockPath).mtimeMs > STALE_MS) {
|
|
89286
|
+
(0, import_node_fs4.unlinkSync)(lockPath);
|
|
89287
|
+
continue;
|
|
89288
|
+
}
|
|
89289
|
+
} catch {
|
|
89290
|
+
continue;
|
|
89291
|
+
}
|
|
89292
|
+
if (Date.now() - start2 > TIMEOUT_MS) return noop3;
|
|
89293
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
89294
|
+
}
|
|
89295
|
+
}
|
|
89248
89296
|
}
|
|
89249
89297
|
var EMBEDDED_PGLITE_ASSETS = ["pglite.wasm", "pglite.data", "initdb.wasm"];
|
|
89250
89298
|
function assertEmbeddedAssetsPresent() {
|
|
@@ -89841,6 +89889,34 @@ function renderReport(r) {
|
|
|
89841
89889
|
if (r.input.repo) lines.push(`Repo: ${r.input.repo}`);
|
|
89842
89890
|
if (r.input.service) lines.push(`Service: ${r.input.service}`);
|
|
89843
89891
|
if (r.input.since) lines.push(`Since: ${r.input.since}`);
|
|
89892
|
+
if (r.behavioral) {
|
|
89893
|
+
lines.push("");
|
|
89894
|
+
lines.push("## Summary");
|
|
89895
|
+
lines.push(r.summary);
|
|
89896
|
+
lines.push("");
|
|
89897
|
+
lines.push(`## Flow${r.behavioral.entry ? ` \u2014 ${r.behavioral.entry.name}` : ""}`);
|
|
89898
|
+
if (r.behavioral.steps.length === 0) {
|
|
89899
|
+
lines.push("(no execution flow could be reconstructed from the source graph)");
|
|
89900
|
+
} else {
|
|
89901
|
+
r.behavioral.steps.slice(0, 20).forEach((s5, i3) => {
|
|
89902
|
+
lines.push(` ${i3 + 1}. ${s5.name} (${formatSymbolLocation(s5.filePath, s5.startLine, s5.endLine)})`);
|
|
89903
|
+
});
|
|
89904
|
+
if (r.behavioral.steps.length > 20) lines.push(` \u2026 and ${r.behavioral.steps.length - 20} more`);
|
|
89905
|
+
}
|
|
89906
|
+
if (r.behavioral.externalCalls.length > 0 || r.behavioral.persistence.length > 0) lines.push("");
|
|
89907
|
+
if (r.behavioral.externalCalls.length > 0) lines.push(`External calls: ${r.behavioral.externalCalls.join(", ")}`);
|
|
89908
|
+
if (r.behavioral.persistence.length > 0) lines.push(`Persistence: ${r.behavioral.persistence.join(", ")}`);
|
|
89909
|
+
lines.push("");
|
|
89910
|
+
if (r.nextActions.length > 0) {
|
|
89911
|
+
lines.push("## Next actions");
|
|
89912
|
+
for (const a2 of r.nextActions) lines.push(`- ${a2}`);
|
|
89913
|
+
lines.push("");
|
|
89914
|
+
}
|
|
89915
|
+
lines.push(
|
|
89916
|
+
'\u2014 Behavioral walkthrough, not a fault hunt. Phrase it as a symptom (e.g. "X failing") for an incident investigation.'
|
|
89917
|
+
);
|
|
89918
|
+
return lines.join("\n");
|
|
89919
|
+
}
|
|
89844
89920
|
lines.push(`Confidence: ${r.confidence.toFixed(2)}`);
|
|
89845
89921
|
const caveat = runtimeSourceCaveat(r);
|
|
89846
89922
|
if (caveat) lines.push(` \u21B3 ${caveat}`);
|
|
@@ -91795,19 +91871,21 @@ function seedRole(s5) {
|
|
|
91795
91871
|
if (/util|helper/i.test(hay)) return "util";
|
|
91796
91872
|
return "code";
|
|
91797
91873
|
}
|
|
91798
|
-
function scoreSeed(s5, index2, hintTokens, changedFiles) {
|
|
91874
|
+
function scoreSeed(s5, index2, hintTokens, changedFiles, hintHasCode) {
|
|
91799
91875
|
const hay = `${s5.name} ${s5.filePath}`.toLowerCase();
|
|
91800
91876
|
let score = 0;
|
|
91801
|
-
if (PREFER.test(hay)) score += 3;
|
|
91877
|
+
if (PREFER.test(hay)) score += hintHasCode ? 0 : 3;
|
|
91802
91878
|
if (DEMOTE.test(hay)) score -= 3;
|
|
91803
91879
|
if (isTypeLikeName(s5.name)) score -= 4;
|
|
91804
91880
|
if (s5.startLine !== void 0 && s5.endLine !== void 0 && s5.endLine - s5.startLine <= 3) {
|
|
91805
91881
|
score -= 3;
|
|
91806
91882
|
}
|
|
91807
91883
|
if (changedFiles !== void 0 && changedFiles.has(s5.filePath)) score += 5;
|
|
91808
|
-
if (/\.(resolver|controller|service)\.[jt]sx?$/i.test(s5.filePath)) score += 2;
|
|
91884
|
+
if (/\.(resolver|controller|service)\.[jt]sx?$/i.test(s5.filePath)) score += hintHasCode ? 0 : 2;
|
|
91809
91885
|
if (/(^|\/)scripts?\//i.test(s5.filePath)) score -= 2;
|
|
91810
|
-
score += Math.max(0, 5 - index2) * 0.1;
|
|
91886
|
+
score += Math.max(0, 5 - index2) * (hintHasCode ? 0.8 : 0.1);
|
|
91887
|
+
if (s5.score !== void 0) score += s5.score * (hintHasCode ? 8 : 1.5);
|
|
91888
|
+
if (/\.[jt]sx?$/i.test(s5.name)) score -= 5;
|
|
91811
91889
|
if (hintTokens !== void 0 && hintTokens.length > 0) {
|
|
91812
91890
|
let hintBoost = 0;
|
|
91813
91891
|
for (const tok of hintTokens) {
|
|
@@ -91820,10 +91898,10 @@ function scoreSeed(s5, index2, hintTokens, changedFiles) {
|
|
|
91820
91898
|
}
|
|
91821
91899
|
return score;
|
|
91822
91900
|
}
|
|
91823
|
-
function rankSeeds(seeds, hintTokens, changedFiles) {
|
|
91901
|
+
function rankSeeds(seeds, hintTokens, changedFiles, hintHasCode) {
|
|
91824
91902
|
return seeds.map((symbol, i3) => ({
|
|
91825
91903
|
symbol,
|
|
91826
|
-
score: scoreSeed(symbol, i3, hintTokens, changedFiles),
|
|
91904
|
+
score: scoreSeed(symbol, i3, hintTokens, changedFiles, hintHasCode),
|
|
91827
91905
|
role: seedRole(symbol),
|
|
91828
91906
|
i: i3
|
|
91829
91907
|
})).sort((a2, b5) => b5.score === a2.score ? a2.i - b5.i : b5.score - a2.score).map(({ symbol, score, role }) => ({ symbol, score, role }));
|
|
@@ -92307,6 +92385,51 @@ function looksExplanatory(hint) {
|
|
|
92307
92385
|
}
|
|
92308
92386
|
return false;
|
|
92309
92387
|
}
|
|
92388
|
+
function looksPerformance(hint) {
|
|
92389
|
+
return /\b(slow|slowness|sluggish|laggy|\blag\b|latency|perf|performance|throughput|degraded|degradation|response\s*time|p9[59])\b/i.test(
|
|
92390
|
+
hint
|
|
92391
|
+
);
|
|
92392
|
+
}
|
|
92393
|
+
function buildBehavioralWalkthrough(hint, top, ctx, flows) {
|
|
92394
|
+
const shortFile = (f5) => f5.replace(/^.*?((?:src|app|lib|packages)\/.*)$/, "$1");
|
|
92395
|
+
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);
|
|
92396
|
+
const flow = flows.slice().sort((a2, b5) => b5.steps.length - a2.steps.length)[0];
|
|
92397
|
+
const rawSteps = flow && flow.steps.length > 1 ? flow.steps : top ? [top, ...ctx?.callees ?? []] : ctx?.callees ?? [];
|
|
92398
|
+
const steps = rawSteps.filter((s5) => !isNoise(s5));
|
|
92399
|
+
const entry2 = steps[0] ?? top ?? null;
|
|
92400
|
+
const dedup = (xs) => {
|
|
92401
|
+
const seen = /* @__PURE__ */ new Set();
|
|
92402
|
+
return xs.filter((s5) => {
|
|
92403
|
+
const k3 = `${s5.name}:${s5.filePath}`;
|
|
92404
|
+
if (seen.has(k3)) return false;
|
|
92405
|
+
seen.add(k3);
|
|
92406
|
+
return true;
|
|
92407
|
+
});
|
|
92408
|
+
};
|
|
92409
|
+
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;
|
|
92410
|
+
const EXTERNAL_RE = /(^|[._])(request|publish|enqueue|dispatch|emit|notify)\b|http|axios|\bapi\b|webhook|\bqueue\b|processor|producer|gateway|\.client\b/i;
|
|
92411
|
+
const pool = dedup([...steps, ...ctx?.callees ?? []]).filter((s5) => !isNoise(s5));
|
|
92412
|
+
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)})`);
|
|
92413
|
+
const persistNames = new Set(persistence.map((p5) => p5.split(" (")[0]));
|
|
92414
|
+
const externalCalls = dedup(
|
|
92415
|
+
pool.filter((s5) => (EXTERNAL_RE.test(s5.name) || EXTERNAL_RE.test(s5.filePath)) && !persistNames.has(s5.name))
|
|
92416
|
+
).slice(0, 6).map((s5) => `${s5.name} (${shortFile(s5.filePath)})`);
|
|
92417
|
+
const lines = [];
|
|
92418
|
+
if (entry2) {
|
|
92419
|
+
const loc = entry2.startLine ? `${shortFile(entry2.filePath)}:${entry2.startLine}` : shortFile(entry2.filePath);
|
|
92420
|
+
lines.push(`Entry point: \`${entry2.name}\` (${loc}).`);
|
|
92421
|
+
}
|
|
92422
|
+
if (steps.length > 1) {
|
|
92423
|
+
const chain = steps.slice(0, 12).map((s5) => s5.name).join(" \u2192 ");
|
|
92424
|
+
lines.push(`Flow: ${chain}${steps.length > 12 ? " \u2192 \u2026" : ""}${flow ? ` (${flow.steps.length} steps)` : ""}.`);
|
|
92425
|
+
} else if (entry2) {
|
|
92426
|
+
lines.push(`No multi-step execution flow was indexed for \`${entry2.name}\` \u2014 showing its direct calls.`);
|
|
92427
|
+
}
|
|
92428
|
+
if (externalCalls.length > 0) lines.push(`Calls out to: ${externalCalls.join(", ")}.`);
|
|
92429
|
+
if (persistence.length > 0) lines.push(`Persists via: ${persistence.join(", ")}.`);
|
|
92430
|
+
if (lines.length === 0) lines.push("No execution flow could be reconstructed for this query from the source graph.");
|
|
92431
|
+
return { question: hint, entry: entry2, steps, externalCalls, persistence, narrative: lines.join("\n") };
|
|
92432
|
+
}
|
|
92310
92433
|
function formatRegressionCitation(recentChanges, changes, seedFile) {
|
|
92311
92434
|
const matchesSeed = (f5) => seedFile === void 0 || f5 === seedFile || seedFile.endsWith(f5) || f5.endsWith(seedFile);
|
|
92312
92435
|
const all = recentChanges?.commits ?? [];
|
|
@@ -92366,13 +92489,14 @@ async function investigate(input, deps) {
|
|
|
92366
92489
|
const changedFilePaths = recentChanges && recentChanges.changedFiles.length > 0 ? new Set(recentChanges.changedFiles) : void 0;
|
|
92367
92490
|
const rawSeeds = code ? await code.searchSymbols(hint, 5) : [];
|
|
92368
92491
|
const hintTokens = [...new Set(tokenize(hint))];
|
|
92369
|
-
const
|
|
92492
|
+
const hintHasCode = /\b(?=[A-Z0-9_]*[0-9_])[A-Z][A-Z0-9_]{3,}\b/.test(hint);
|
|
92493
|
+
const ranked = rankSeeds(rawSeeds, hintTokens, changedFilePaths, hintHasCode);
|
|
92370
92494
|
let seeds = ranked.map((r) => r.symbol);
|
|
92371
92495
|
let top = seeds[0];
|
|
92372
92496
|
if (top && code && isTypeLikeName(top.name)) {
|
|
92373
92497
|
const base2 = executableBaseName(top.name);
|
|
92374
92498
|
if (base2) {
|
|
92375
|
-
const altTop = rankSeeds(await code.searchSymbols(base2, 5), hintTokens).find(
|
|
92499
|
+
const altTop = rankSeeds(await code.searchSymbols(base2, 5), hintTokens, void 0, hintHasCode).find(
|
|
92376
92500
|
(r) => !isTypeLikeName(r.symbol.name)
|
|
92377
92501
|
)?.symbol;
|
|
92378
92502
|
if (altTop && altTop.id !== top.id) {
|
|
@@ -92432,7 +92556,8 @@ async function investigate(input, deps) {
|
|
|
92432
92556
|
"slow"
|
|
92433
92557
|
]);
|
|
92434
92558
|
const meaningfulHintTokens = hintTokens.filter((t2) => !GENERIC_HINT_TOKENS.has(t2));
|
|
92435
|
-
const seedIsLowConfidence = top !== void 0 && logReseed === null &&
|
|
92559
|
+
const seedIsLowConfidence = top !== void 0 && logReseed === null && (top.score ?? 0) < 0.5 && // a strong exact-content/colocated source match is authoritative (gap 6)
|
|
92560
|
+
meaningfulHintTokens.length > 0 && !meaningfulHintTokens.some((t2) => seedHay.includes(t2));
|
|
92436
92561
|
if (!top && !degradedNoSource) {
|
|
92437
92562
|
const report2 = {
|
|
92438
92563
|
id: globalThis.crypto.randomUUID(),
|
|
@@ -92569,6 +92694,8 @@ async function investigate(input, deps) {
|
|
|
92569
92694
|
const directLogEvIds = [];
|
|
92570
92695
|
const ambientLogEvIds = [];
|
|
92571
92696
|
const directSignatures = [];
|
|
92697
|
+
const directEvIdByKey = /* @__PURE__ */ new Map();
|
|
92698
|
+
const seedEmittedJoins = [];
|
|
92572
92699
|
const entityEvIds = [];
|
|
92573
92700
|
const entitySummaries = [];
|
|
92574
92701
|
let logsCollected = false;
|
|
@@ -92643,13 +92770,17 @@ async function investigate(input, deps) {
|
|
|
92643
92770
|
if (relevanceClass === "direct") {
|
|
92644
92771
|
directLogEvIds.push(ev.id);
|
|
92645
92772
|
directSignatures.push({ key: s5.key, count: s5.count, message: s5.sampleMessage ?? "" });
|
|
92773
|
+
if (!directEvIdByKey.has(s5.key)) directEvIdByKey.set(s5.key, ev.id);
|
|
92646
92774
|
} else ambientLogEvIds.push(ev.id);
|
|
92647
92775
|
}
|
|
92648
|
-
const seedSnippet = ctx?.snippet !== void 0 && ctx.snippet !== null ? ctx.snippet : void 0;
|
|
92776
|
+
const seedSnippet = (ctx?.sourceBody !== void 0 && ctx.sourceBody !== null ? ctx.sourceBody : void 0) ?? (ctx?.snippet !== void 0 && ctx.snippet !== null ? ctx.snippet : void 0);
|
|
92649
92777
|
const sourceCodes = extractEventCodes(input.hint, top?.name, seedSnippet);
|
|
92778
|
+
const seedEmittedCodes = new Set(
|
|
92779
|
+
seedSnippet !== void 0 ? extractEventCodes(seedSnippet) : []
|
|
92780
|
+
);
|
|
92650
92781
|
const joinedCodes = new Set(directSignatures.map((d3) => d3.key));
|
|
92651
92782
|
for (const code2 of sourceCodes) {
|
|
92652
|
-
|
|
92783
|
+
const alreadyJoined = joinedCodes.has(code2);
|
|
92653
92784
|
joinedCodes.add(code2);
|
|
92654
92785
|
try {
|
|
92655
92786
|
const codeAnalysis = await deps.logs.analyzeErrors({
|
|
@@ -92659,6 +92790,21 @@ async function investigate(input, deps) {
|
|
|
92659
92790
|
});
|
|
92660
92791
|
const sig = codeAnalysis.signatures.find((s5) => s5.key === code2);
|
|
92661
92792
|
if (sig === void 0 || sig.count <= 0) continue;
|
|
92793
|
+
if (alreadyJoined) {
|
|
92794
|
+
if (seedEmittedCodes.has(code2)) {
|
|
92795
|
+
const existingEvId = directEvIdByKey.get(code2);
|
|
92796
|
+
if (existingEvId !== void 0) {
|
|
92797
|
+
seedEmittedJoins.push({
|
|
92798
|
+
code: code2,
|
|
92799
|
+
count: sig.count,
|
|
92800
|
+
message: sig.sampleMessage ?? "",
|
|
92801
|
+
evId: existingEvId,
|
|
92802
|
+
isNew: sig.isNew ?? false
|
|
92803
|
+
});
|
|
92804
|
+
}
|
|
92805
|
+
}
|
|
92806
|
+
continue;
|
|
92807
|
+
}
|
|
92662
92808
|
const links = top !== void 0 ? top.startLine !== void 0 && top.startLine > 0 ? { symbolId: top.id, file: top.filePath, line: top.startLine } : { symbolId: top.id, file: top.filePath } : {};
|
|
92663
92809
|
const ev = mkEv(
|
|
92664
92810
|
"log",
|
|
@@ -92677,7 +92823,8 @@ async function investigate(input, deps) {
|
|
|
92677
92823
|
sampleMessage: sig.sampleMessage ?? null,
|
|
92678
92824
|
relevanceClass: "direct",
|
|
92679
92825
|
relevanceReason: `exact event_code match to the source signal (${code2})`,
|
|
92680
|
-
crossSignalJoin: true
|
|
92826
|
+
crossSignalJoin: true,
|
|
92827
|
+
seedEmitted: seedEmittedCodes.has(code2)
|
|
92681
92828
|
},
|
|
92682
92829
|
links,
|
|
92683
92830
|
sig.lastSeen || void 0,
|
|
@@ -92692,6 +92839,16 @@ async function investigate(input, deps) {
|
|
|
92692
92839
|
count: sig.count,
|
|
92693
92840
|
message: sig.sampleMessage ?? ""
|
|
92694
92841
|
});
|
|
92842
|
+
if (!directEvIdByKey.has(code2)) directEvIdByKey.set(code2, ev.id);
|
|
92843
|
+
if (seedEmittedCodes.has(code2)) {
|
|
92844
|
+
seedEmittedJoins.push({
|
|
92845
|
+
code: code2,
|
|
92846
|
+
count: sig.count,
|
|
92847
|
+
message: sig.sampleMessage ?? "",
|
|
92848
|
+
evId: ev.id,
|
|
92849
|
+
isNew: sig.isNew ?? false
|
|
92850
|
+
});
|
|
92851
|
+
}
|
|
92695
92852
|
} catch {
|
|
92696
92853
|
}
|
|
92697
92854
|
}
|
|
@@ -93277,6 +93434,29 @@ async function investigate(input, deps) {
|
|
|
93277
93434
|
metadata: { blastRadius }
|
|
93278
93435
|
});
|
|
93279
93436
|
}
|
|
93437
|
+
if (seedEmittedJoins.length > 0 && top && seedEv) {
|
|
93438
|
+
const ordered = [...seedEmittedJoins].sort((a2, b5) => b5.count - a2.count);
|
|
93439
|
+
const lead = ordered[0];
|
|
93440
|
+
if (lead !== void 0) {
|
|
93441
|
+
const sourceEvidenceIds = [
|
|
93442
|
+
.../* @__PURE__ */ new Set([...ordered.map((j3) => j3.evId), seedEv.id])
|
|
93443
|
+
];
|
|
93444
|
+
const volumeBoost = Math.min(0.1, Math.log10(Math.max(1, lead.count)) * 0.025);
|
|
93445
|
+
const baseScore = clamp015(0.6 + volumeBoost);
|
|
93446
|
+
const msgClause = lead.message ? ` \u2014 "${lead.message.replace(/\s+/g, " ").trim().slice(0, 90)}"` : "";
|
|
93447
|
+
causeInputs.push({
|
|
93448
|
+
id: "cause:seed-emitted-error",
|
|
93449
|
+
title: `Runtime error ${lead.code} (${lead.count}x in Elasticsearch) is raised by ${top.name} \u2014 the likely failure${msgClause}`.slice(
|
|
93450
|
+
0,
|
|
93451
|
+
220
|
|
93452
|
+
),
|
|
93453
|
+
category: "error-correlation",
|
|
93454
|
+
sourceEvidenceIds,
|
|
93455
|
+
baseScore,
|
|
93456
|
+
metadata: { blastRadius, seedEmitted: true, code: lead.code, count: lead.count }
|
|
93457
|
+
});
|
|
93458
|
+
}
|
|
93459
|
+
}
|
|
93280
93460
|
if (analysis !== null && analysis.signatures.length > 0 && queueHits.length > 0 && directLogEvIds.length > 0) {
|
|
93281
93461
|
const firstQueue = queueHits[0];
|
|
93282
93462
|
const queueLabel = firstQueue !== void 0 ? `"${firstQueue.queueName}" (${firstQueue.producerSymbol ?? "unknown"} -> ${firstQueue.workerSymbol ?? "unknown"})` : "the queue path";
|
|
@@ -93395,16 +93575,34 @@ async function investigate(input, deps) {
|
|
|
93395
93575
|
const seedDisclaimer = seedIsLowConfidence && top ? `\u26A0 No symbol closely matched "${hint}" \u2014 "${top.name}" is a low-confidence closest match (semantic). Refine with an exact symbol or error code to target precisely. ` : "";
|
|
93396
93576
|
let summary = headlineCause ? headlineLinked ? `${seedDisclaimer}${banner2}Investigation of "${hint}" ${scope}. Top suspected cause: ${headlineCause.title}.` : `${seedDisclaimer}${banner2}Investigation of "${hint}" ${scope}. No cause is structurally linked to the seed; the strongest co-occurring signal (a lead to verify, not a diagnosis) is: ${headlineCause.title}.` : `${seedDisclaimer}${banner2}Investigation of "${hint}" ${scope}. No dominant suspected cause emerged from the available ${degradedNoSource ? "runtime" : "structural"} evidence.`;
|
|
93397
93577
|
const hasIncidentSignal = directLogEvIds.length > 0 || latencyMetricEvIds.length > 0 || queueMetricEvIds.length > 0 || dataAnomalyEvIds.length > 0 || queueFailureSignals.length > 0 || findings2.some((f5) => f5.kind === "anomaly") || headlineCause !== void 0;
|
|
93398
|
-
const explanatoryHint = looksExplanatory(hint)
|
|
93578
|
+
const explanatoryHint = looksExplanatory(hint);
|
|
93579
|
+
let behavioral;
|
|
93399
93580
|
if (explanatoryHint) {
|
|
93400
|
-
|
|
93401
|
-
|
|
93581
|
+
let flowPool = flows;
|
|
93582
|
+
const richestSeedFlow = flows.slice().sort((a2, b5) => b5.steps.length - a2.steps.length)[0];
|
|
93583
|
+
if (code && (richestSeedFlow === void 0 || richestSeedFlow.steps.length <= 1) && ctx?.callers?.length) {
|
|
93584
|
+
const ENTRY_RE = /controller|resolver|route|handler|command|consumer|listener|gateway|cron|scheduler|webhook|middleware/i;
|
|
93585
|
+
const entryCallers = ctx.callers.filter((c4) => ENTRY_RE.test(c4.name) || ENTRY_RE.test(c4.filePath)).slice(0, 3);
|
|
93586
|
+
for (const c4 of entryCallers) {
|
|
93587
|
+
try {
|
|
93588
|
+
flowPool = flowPool.concat(await code.flowsFor(c4.id));
|
|
93589
|
+
} catch {
|
|
93590
|
+
}
|
|
93591
|
+
}
|
|
93592
|
+
}
|
|
93593
|
+
behavioral = buildBehavioralWalkthrough(hint, top, ctx ?? void 0, flowPool);
|
|
93594
|
+
const noisy = hasIncidentSignal ? ' (note: live error signals also exist in this codebase \u2014 rephrase as a symptom like "X failing" for a fault hunt)' : "";
|
|
93595
|
+
summary = `${seedDisclaimer}${banner2}"${hint}" reads as a "how does it work" question \u2014 here is the code path${noisy}:
|
|
93596
|
+
|
|
93597
|
+
` + behavioral.narrative;
|
|
93598
|
+
}
|
|
93599
|
+
if (!explanatoryHint && looksPerformance(hint) && deps.metrics == null && seedIsLowConfidence) {
|
|
93600
|
+
summary = `${banner2}"${hint}" reads as a latency/performance question, but no metrics connector (Grafana) is configured \u2014 response-time and error-rate trends can't be assessed from source structure alone${top ? ` (the closest structural match, ${top.name}, is a low-confidence guess, not a diagnosis)` : ""}. Connect a metrics source (\`horus connect grafana\`) and re-run, or name the specific slow operation.`;
|
|
93402
93601
|
}
|
|
93403
93602
|
const nextActions = buildNextActions(top, ctx, impact, queueHits, changes, input);
|
|
93404
|
-
if (explanatoryHint) {
|
|
93405
|
-
const explainTarget = top?.name ?? input.service ?? hint;
|
|
93603
|
+
if (explanatoryHint && behavioral?.entry) {
|
|
93406
93604
|
nextActions.unshift(
|
|
93407
|
-
`
|
|
93605
|
+
`Trace the full call graph of the entry point: \`horus explain ${behavioral.entry.name}\`.`
|
|
93408
93606
|
);
|
|
93409
93607
|
}
|
|
93410
93608
|
if (degradedNoSource) {
|
|
@@ -93436,6 +93634,7 @@ async function investigate(input, deps) {
|
|
|
93436
93634
|
ownership: ownershipEstimate,
|
|
93437
93635
|
causeChains: causeChains.length > 0 ? causeChains : void 0,
|
|
93438
93636
|
...recentChanges !== void 0 ? { recentChanges } : {},
|
|
93637
|
+
...behavioral !== void 0 ? { behavioral } : {},
|
|
93439
93638
|
...degradedNoSource ? { degraded: { sourceIntelligence: false, reason: "source-intelligence host unreachable" } } : {}
|
|
93440
93639
|
};
|
|
93441
93640
|
const connectorFlags = deps.connectors ? {
|