@merittdev/horus 0.8.5 → 0.8.7

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.
Files changed (2) hide show
  1. package/dist/index.cjs +87 -23
  2. 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.5" : "dev";
65883
+ var HORUS_VERSION = true ? "0.8.7" : "dev";
65884
65884
  var PINNED_SOURCE_VERSION = "1.5.0";
65885
65885
 
65886
65886
  // ../../packages/core/src/evidence.ts
@@ -91925,6 +91925,8 @@ function correlate(evidence2) {
91925
91925
  init_cjs_shims();
91926
91926
  var PREFER = /resolver|controller|\bservice\b|route|router|handler|gateway|processor|worker|consumer|repository|usecase|use-case|endpoint/i;
91927
91927
  var DEMOTE = /\butil|helper|\bscript|backfill|legacy|migration|seeder|fixture|\bmock\b|\btest\b|\bspec\b/i;
91928
+ var PRESENTATION_NAME = /^(build|format|render|compose|serialize)[A-Z].*(Message|Markup|Html|Text|Label|Summary|Notification|String)$/;
91929
+ var PREDICATE_NAME = /^(is|has|can|should|was|were|are|does|did|needs|allows?|must)[A-Z]/;
91928
91930
  var TYPE_SUFFIX = /(Result|Input|Response|Payload|Args|Dto|Props|Edge|Connection)$/;
91929
91931
  var INTERFACE_PREFIX = /^I(?=[A-Z][a-z])/;
91930
91932
  var TYPE_LIKE_NAME = new RegExp(`(?:${TYPE_SUFFIX.source})|(?:${INTERFACE_PREFIX.source})`);
@@ -91952,8 +91954,9 @@ function scoreSeed(s5, index2, hintTokens, changedFiles, hintHasCode) {
91952
91954
  let score = 0;
91953
91955
  if (PREFER.test(hay)) score += hintHasCode ? 0 : 3;
91954
91956
  if (DEMOTE.test(hay)) score -= 3;
91957
+ if (PRESENTATION_NAME.test(s5.name)) score -= 3;
91955
91958
  if (isTypeLikeName(s5.name)) score -= 4;
91956
- if (s5.startLine !== void 0 && s5.endLine !== void 0 && s5.endLine - s5.startLine <= 3) {
91959
+ if (s5.startLine !== void 0 && s5.endLine !== void 0 && s5.endLine - s5.startLine <= 3 && !PREDICATE_NAME.test(s5.name)) {
91957
91960
  score -= 3;
91958
91961
  }
91959
91962
  if (changedFiles !== void 0 && changedFiles.has(s5.filePath)) score += 5;
@@ -92279,6 +92282,15 @@ function clamp015(n4) {
92279
92282
  var SEV_ERROR = 2;
92280
92283
  var SEV_WARN = 1;
92281
92284
  var SEV_INFO = 0;
92285
+ var GENERIC_SYMBOL_NAMES = /* @__PURE__ */ new Set([
92286
+ "",
92287
+ "constructor",
92288
+ "unknown",
92289
+ "unknown-producer",
92290
+ "unknown-worker",
92291
+ "anonymous",
92292
+ "default"
92293
+ ]);
92282
92294
  function seedEmittedSeverityTier(level, code, message) {
92283
92295
  const lv = level?.toLowerCase();
92284
92296
  if (lv === "error" || lv === "fatal" || lv === "critical") return SEV_ERROR;
@@ -92471,10 +92483,21 @@ function looksDiffable(since) {
92471
92483
  function looksExplanatory(hint) {
92472
92484
  const h3 = hint.trim();
92473
92485
  if (h3.length === 0) return false;
92486
+ if (/^\s*(explain|describe|walk\s+me\s+through|tell\s+me\s+(?:about|how))\b/i.test(h3)) {
92487
+ return true;
92488
+ }
92489
+ if (/\b(recently\s+changed|what\s+changed|changed\s+recently|regress|regression)\b/i.test(h3)) {
92490
+ return false;
92491
+ }
92474
92492
  if (/\b(how|what|why|when|where)\b.*\b(work|works|working|do|does|done|happen|happens|flow|flows|behave|called|invoked|triggered|used)\b/i.test(h3)) {
92475
92493
  return true;
92476
92494
  }
92477
- if (/^\s*(how|what|why|when|where|explain|describe|walk\s+me\s+through|tell\s+me\s+(?:about|how))\b/i.test(h3)) {
92495
+ if (/\b(fail|fails|failed|failing|failure|break|breaks|breaking|broke|broken|crash|crashes|crashing|down|slow|stuck|hang|hangs|hung|timeout|timing\s*out|wrong|exception|error|errors|leak|leaking|stopped|throwing|throws|reject|rejected|denied|not\s+work|doesn'?t\s+work|isn'?t\s+work|can'?t|cannot|caus(?:e|es|ing)|misbehav)/i.test(
92496
+ h3
92497
+ )) {
92498
+ return false;
92499
+ }
92500
+ if (/^\s*(how|what|why|when|where)\b/i.test(h3)) {
92478
92501
  return true;
92479
92502
  }
92480
92503
  return false;
@@ -92729,16 +92752,33 @@ async function investigate(input, deps) {
92729
92752
  } catch {
92730
92753
  edges = [];
92731
92754
  }
92732
- const symbolNames = /* @__PURE__ */ new Set([
92733
- top.name,
92734
- ...ctx.callers.map((s5) => s5.name),
92735
- ...ctx.callees.map((s5) => s5.name)
92736
- ]);
92755
+ const symbolNames = new Set(
92756
+ [top.name, ...ctx.callers.map((s5) => s5.name), ...ctx.callees.map((s5) => s5.name)].filter(
92757
+ (n4) => n4 && !GENERIC_SYMBOL_NAMES.has(n4)
92758
+ )
92759
+ );
92737
92760
  queueHits = edges.filter((e) => {
92738
92761
  const bySymbol = e.producerSymbol !== null && symbolNames.has(e.producerSymbol) || e.workerSymbol !== null && symbolNames.has(e.workerSymbol);
92739
92762
  const byFile = e.producerFile === top.filePath || e.workerFile === top.filePath;
92740
92763
  return bySymbol || byFile;
92741
92764
  });
92765
+ if (queueHits.length > 0 && typeof code.searchSymbols === "function") {
92766
+ const resolvesInRepo = async (name2) => {
92767
+ if (!name2 || GENERIC_SYMBOL_NAMES.has(name2)) return false;
92768
+ try {
92769
+ return (await code.searchSymbols(name2, 3)).some((s5) => s5.name === name2);
92770
+ } catch {
92771
+ return true;
92772
+ }
92773
+ };
92774
+ const validated2 = [];
92775
+ for (const e of queueHits) {
92776
+ if (await resolvesInRepo(e.producerSymbol) || await resolvesInRepo(e.workerSymbol)) {
92777
+ validated2.push(e);
92778
+ }
92779
+ }
92780
+ queueHits = validated2;
92781
+ }
92742
92782
  if (input.since !== void 0 && looksDiffable(input.since)) {
92743
92783
  try {
92744
92784
  changes = await code.detectChanges({ base: input.since, compare: "HEAD" });
@@ -92922,7 +92962,10 @@ async function investigate(input, deps) {
92922
92962
  message: sig.sampleMessage ?? "",
92923
92963
  evId: existingEvId,
92924
92964
  isNew: sig.isNew ?? false,
92925
- level: sig.level ?? null
92965
+ level: sig.level ?? null,
92966
+ raisedBySeed: true,
92967
+ // literal is in the seed's own body
92968
+ raiserName: top?.name ?? ""
92926
92969
  });
92927
92970
  }
92928
92971
  }
@@ -92970,7 +93013,10 @@ async function investigate(input, deps) {
92970
93013
  message: sig.sampleMessage ?? "",
92971
93014
  evId: ev.id,
92972
93015
  isNew: sig.isNew ?? false,
92973
- level: sig.level ?? null
93016
+ level: sig.level ?? null,
93017
+ raisedBySeed: true,
93018
+ // literal is in the seed's own body
93019
+ raiserName: top?.name ?? ""
92974
93020
  });
92975
93021
  }
92976
93022
  } catch {
@@ -92980,17 +93026,28 @@ async function investigate(input, deps) {
92980
93026
  const GAP_D_MAX_RESOLVE = 12;
92981
93027
  const seedScope = /* @__PURE__ */ new Set([top.id, ...(ctx?.callees ?? []).map((c4) => c4.id)]);
92982
93028
  const alreadyEmitted = new Set(seedEmittedJoins.map((j3) => j3.code));
92983
- const candidates = analysis.signatures.filter(
93029
+ const eligible = analysis.signatures.filter(
92984
93030
  (s5) => s5.key !== "" && s5.key !== "(none)" && s5.count > 0 && !alreadyEmitted.has(s5.key)
92985
- ).sort((a2, b5) => b5.count - a2.count).slice(0, GAP_D_MAX_RESOLVE);
93031
+ );
93032
+ const meaningful = meaningfulHintTokens.filter((t2) => t2.length >= 4);
93033
+ const hintRelevant = (s5) => meaningful.some(
93034
+ (t2) => s5.key.toLowerCase().includes(t2) || (s5.sampleMessage ?? "").toLowerCase().includes(t2)
93035
+ );
93036
+ const candidates = [
93037
+ .../* @__PURE__ */ new Set([
93038
+ ...[...eligible].sort((a2, b5) => b5.count - a2.count).slice(0, GAP_D_MAX_RESOLVE),
93039
+ ...eligible.filter(hintRelevant).slice(0, GAP_D_MAX_RESOLVE)
93040
+ ])
93041
+ ].slice(0, GAP_D_MAX_RESOLVE * 2);
92986
93042
  for (const sig of candidates) {
92987
- let raiser;
93043
+ let raisers = [];
92988
93044
  try {
92989
- raiser = (await deps.code.searchSymbols(sig.key, 1))[0];
93045
+ raisers = await deps.code.searchSymbols(sig.key, 3);
92990
93046
  } catch {
92991
93047
  continue;
92992
93048
  }
92993
- if (raiser === void 0 || !seedScope.has(raiser.id)) continue;
93049
+ const raiser = raisers.find((r) => r.id === top.id) ?? raisers.find((r) => seedScope.has(r.id));
93050
+ if (raiser === void 0) continue;
92994
93051
  let evId = directEvIdByKey.get(sig.key);
92995
93052
  if (evId === void 0) {
92996
93053
  const where = raiser.id === top.id ? "the seed" : "a callee of the seed";
@@ -93033,7 +93090,9 @@ async function investigate(input, deps) {
93033
93090
  message: sig.sampleMessage ?? "",
93034
93091
  evId,
93035
93092
  isNew: sig.isNew ?? false,
93036
- level: sig.level ?? null
93093
+ level: sig.level ?? null,
93094
+ raisedBySeed: raiser.id === top.id,
93095
+ raiserName: raiser.name
93037
93096
  });
93038
93097
  }
93039
93098
  }
@@ -93622,6 +93681,7 @@ async function investigate(input, deps) {
93622
93681
  }
93623
93682
  if (seedEmittedJoins.length > 0 && top && seedEv) {
93624
93683
  const ordered = [...seedEmittedJoins].sort((a2, b5) => {
93684
+ if (a2.raisedBySeed !== b5.raisedBySeed) return a2.raisedBySeed ? -1 : 1;
93625
93685
  const ta = seedEmittedSeverityTier(a2.level, a2.code, a2.message);
93626
93686
  const tb = seedEmittedSeverityTier(b5.level, b5.code, b5.message);
93627
93687
  if (ta !== tb) return tb - ta;
@@ -93635,17 +93695,21 @@ async function investigate(input, deps) {
93635
93695
  const tier = seedEmittedSeverityTier(lead.level, lead.code, lead.message);
93636
93696
  const volumeBoost = Math.min(0.1, Math.log10(Math.max(1, lead.count)) * 0.025);
93637
93697
  const msgClause = lead.message ? ` \u2014 "${lead.message.replace(/\s+/g, " ").trim().slice(0, 90)}"` : "";
93698
+ const bySeed = lead.raisedBySeed;
93699
+ const raiser = bySeed ? top.name : lead.raiserName || top.name;
93700
+ const depClause = bySeed ? "" : `, a dependency of ${top.name}`;
93701
+ const calleePenalty = bySeed ? 0 : 0.18;
93638
93702
  let title;
93639
93703
  let baseScore;
93640
93704
  if (tier === SEV_ERROR) {
93641
- title = `Runtime error ${lead.code} (${lead.count}x in Elasticsearch) is raised by ${top.name} \u2014 the likely failure${msgClause}`;
93642
- baseScore = clamp015(0.6 + volumeBoost);
93705
+ title = bySeed ? `Runtime error ${lead.code} (${lead.count}x in Elasticsearch) is raised by ${raiser} \u2014 the likely failure${msgClause}` : `Runtime error ${lead.code} (${lead.count}x in Elasticsearch) surfaces on ${raiser}${depClause}${msgClause}`;
93706
+ baseScore = clamp015(0.6 + volumeBoost - calleePenalty);
93643
93707
  } else if (tier === SEV_WARN) {
93644
- title = `Warning ${lead.code} (${lead.count}x in Elasticsearch) is raised by ${top.name}${msgClause}`;
93645
- baseScore = clamp015(0.42 + volumeBoost);
93708
+ title = `Warning ${lead.code} (${lead.count}x in Elasticsearch) ${bySeed ? "is raised by" : "surfaces on"} ${raiser}${depClause}${msgClause}`;
93709
+ baseScore = clamp015(0.42 + volumeBoost - calleePenalty);
93646
93710
  } else {
93647
- title = `Diagnostic signal ${lead.code} (${lead.count}x in Elasticsearch) is logged by ${top.name} \u2014 informational, not an error${msgClause}`;
93648
- baseScore = clamp015(0.3 + volumeBoost);
93711
+ title = `Diagnostic signal ${lead.code} (${lead.count}x in Elasticsearch) is logged by ${raiser}${depClause} \u2014 informational, not an error${msgClause}`;
93712
+ baseScore = clamp015(0.3 + volumeBoost - calleePenalty);
93649
93713
  }
93650
93714
  causeInputs.push({
93651
93715
  id: "cause:seed-emitted-error",
@@ -93785,7 +93849,7 @@ async function investigate(input, deps) {
93785
93849
  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. ` : "";
93786
93850
  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.`;
93787
93851
  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;
93788
- const explanatoryHint = looksExplanatory(hint);
93852
+ const explanatoryHint = looksExplanatory(hint) && input.since === void 0;
93789
93853
  let behavioral;
93790
93854
  if (explanatoryHint) {
93791
93855
  let flowPool = flows;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@merittdev/horus",
3
- "version": "0.8.5",
3
+ "version": "0.8.7",
4
4
  "description": "Local-first, source-aware production-incident investigation engine",
5
5
  "type": "module",
6
6
  "bin": {