@merittdev/horus 0.17.1 → 0.18.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.
Files changed (2) hide show
  1. package/dist/index.cjs +153 -3
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -3057,8 +3057,8 @@ var init_version = __esm({
3057
3057
  "../../packages/core/src/version.ts"() {
3058
3058
  "use strict";
3059
3059
  init_cjs_shims();
3060
- HORUS_VERSION = true ? "0.17.1" : "dev";
3061
- PINNED_SOURCE_VERSION = "2.0.2";
3060
+ HORUS_VERSION = true ? "0.18.0" : "dev";
3061
+ PINNED_SOURCE_VERSION = "2.1.0";
3062
3062
  }
3063
3063
  });
3064
3064
 
@@ -72992,7 +72992,12 @@ function shortTs(iso) {
72992
72992
  function buildErrorAnalysisBody(q2, field = "event_code", mapping = MERITT_FIELD_MAPPING) {
72993
72993
  const filters = [];
72994
72994
  if (q2.eventCode === void 0) {
72995
- const minLevel = q2.level !== void 0 && levelToValue(q2.level) > 50 ? q2.level : "error";
72995
+ const hasText = (q2.text ?? "").trim().length > 0;
72996
+ let minLevel = "error";
72997
+ if (q2.level !== void 0) {
72998
+ if (levelToValue(q2.level) > 50) minLevel = q2.level;
72999
+ else if (hasText && levelToValue(q2.level) >= 40) minLevel = q2.level;
73000
+ }
72996
73001
  filters.push(buildLevelFilter(mapping, minLevel));
72997
73002
  } else if (q2.level !== void 0 && levelToValue(q2.level) > 50) {
72998
73003
  filters.push(buildLevelFilter(mapping, q2.level));
@@ -94400,6 +94405,103 @@ function detectDataFlowCauseAcross(contexts) {
94400
94405
  return null;
94401
94406
  }
94402
94407
 
94408
+ // ../../packages/engine/src/hint-matched-signal.ts
94409
+ init_cjs_shims();
94410
+ var GENERIC_MATCH_STOP = /* @__PURE__ */ new Set([
94411
+ "product",
94412
+ "products",
94413
+ "order",
94414
+ "orders",
94415
+ "sync",
94416
+ "syncing",
94417
+ "synced",
94418
+ "fetch",
94419
+ "fetching",
94420
+ "api",
94421
+ "data",
94422
+ "service",
94423
+ "services",
94424
+ "update",
94425
+ "updating",
94426
+ "request",
94427
+ "requests",
94428
+ "response",
94429
+ "error",
94430
+ "errors",
94431
+ "get",
94432
+ "getting",
94433
+ "save",
94434
+ "saving",
94435
+ "load",
94436
+ "loading",
94437
+ "create",
94438
+ "creating",
94439
+ "delete",
94440
+ "failing",
94441
+ "failed",
94442
+ "failure",
94443
+ "broken",
94444
+ "value",
94445
+ "values",
94446
+ "record",
94447
+ "records",
94448
+ "found",
94449
+ "not",
94450
+ "with",
94451
+ "the",
94452
+ "and",
94453
+ "for",
94454
+ "from",
94455
+ "into",
94456
+ "this",
94457
+ "that",
94458
+ "when",
94459
+ "returns",
94460
+ "returning",
94461
+ "stale",
94462
+ "wrong",
94463
+ "issue",
94464
+ "issues",
94465
+ "problem",
94466
+ "slow",
94467
+ "down",
94468
+ "production"
94469
+ ]);
94470
+ function distinctiveHintTokens(meaningfulHintTokens) {
94471
+ return [...new Set(meaningfulHintTokens.map((t2) => t2.toLowerCase()))].filter(
94472
+ (t2) => t2.length >= 4 && !GENERIC_MATCH_STOP.has(t2)
94473
+ );
94474
+ }
94475
+ function codeSegments(code) {
94476
+ return code.toLowerCase().split(/[^a-z]+/).filter((s5) => s5.length >= 4);
94477
+ }
94478
+ function selectHintMatchedSignal(signals, meaningfulHintTokens) {
94479
+ const distinctive = distinctiveHintTokens(meaningfulHintTokens);
94480
+ if (distinctive.length === 0) return null;
94481
+ let best = null;
94482
+ for (const s5 of signals) {
94483
+ if (!s5.key || s5.key === "(none)" || s5.count < 2) continue;
94484
+ const codeHit = codeSegments(s5.key).some((seg) => distinctive.includes(seg));
94485
+ const msg = (s5.message ?? "").toLowerCase();
94486
+ const msgHits = distinctive.filter((t2) => msg.includes(t2)).length;
94487
+ if (!codeHit && msgHits < 2) continue;
94488
+ const score = (codeHit ? 1e3 : 0) + msgHits * 10 + Math.min(9, Math.log10(Math.max(1, s5.count)));
94489
+ if (best === null || score > best.score) {
94490
+ best = {
94491
+ code: s5.key,
94492
+ count: s5.count,
94493
+ message: s5.message ?? "",
94494
+ via: codeHit ? "code" : "message",
94495
+ score
94496
+ };
94497
+ }
94498
+ }
94499
+ if (best === null) return null;
94500
+ const { score: _score, ...result } = best;
94501
+ void _score;
94502
+ return result;
94503
+ }
94504
+
94403
94505
  // ../../packages/engine/src/hypotheses.ts
94404
94506
  init_cjs_shims();
94405
94507
  function generateHypotheses(evidence2, _correlation, ctx) {
@@ -98558,6 +98660,54 @@ async function investigate(input, deps) {
98558
98660
  }
98559
98661
  }
98560
98662
  }
98663
+ if (deps.logs && typeof deps.logs.analyzeErrors === "function" && seedEmittedJoins.length === 0 && !sourceImpactHint && meaningfulHintTokens.length > 0) {
98664
+ try {
98665
+ const warnAnalysis = await deps.logs.analyzeErrors({
98666
+ service: input.service,
98667
+ from: logWindowFrom(input.logsSince ?? input.since),
98668
+ level: "warn",
98669
+ // Plain message match (OR semantics) — NOT broadText, whose phrase-match would
98670
+ // require the hint words to appear contiguously (e.g. "Sale with link not found"
98671
+ // wouldn't match the phrase "sale links found"). selectHintMatchedSignal then
98672
+ // applies the precision gate on the returned signatures.
98673
+ text: meaningfulHintTokens.filter((t2) => t2.length >= 4).join(" ")
98674
+ });
98675
+ const picked = selectHintMatchedSignal(warnAnalysis.signatures ?? [], meaningfulHintTokens);
98676
+ if (picked) {
98677
+ const tier = seedEmittedSeverityTier(null, picked.code, picked.message);
98678
+ const volumeBoost = Math.min(0.1, Math.log10(Math.max(1, picked.count)) * 0.03);
98679
+ const msgClause = picked.message ? ` \u2014 "${picked.message.replace(/\s+/g, " ").trim().slice(0, 80)}"` : "";
98680
+ const ev = mkEv(
98681
+ "log",
98682
+ `event_code ${picked.code}: ${picked.count}x in Elasticsearch \u2014 matches the reported symptom`.slice(0, 220),
98683
+ {
98684
+ signature: picked.code,
98685
+ count: picked.count,
98686
+ sampleMessage: picked.message || null,
98687
+ relevanceClass: "direct",
98688
+ relevanceReason: `event_code ${picked.code} matches the reported symptom (not structurally linked to the seed)`,
98689
+ hintMatched: true
98690
+ },
98691
+ {},
98692
+ void 0,
98693
+ 0.75
98694
+ );
98695
+ logEvIds.push(ev.id);
98696
+ causeInputs.push({
98697
+ id: "cause:hint-matched-signal",
98698
+ title: `Runtime signal ${picked.code} (${picked.count}x in Elasticsearch) matches the reported symptom but isn't structurally linked to ${top?.name ?? "the seed"}${msgClause} \u2014 verify`.slice(0, 220),
98699
+ category: "error-correlation",
98700
+ sourceEvidenceIds: [ev.id],
98701
+ // A signal whose event_code/message names the symptom is stronger evidence than a
98702
+ // diffuse recent commit that merely touched the seed's file (order-of-trust), so it
98703
+ // ranks above the broad-commit regression floor (~0.54). Still below a seed-LINKED error.
98704
+ baseScore: clamp015((tier === SEV_ERROR ? 0.6 : 0.56) + volumeBoost),
98705
+ metadata: { blastRadius, hintMatched: true, code: picked.code, count: picked.count }
98706
+ });
98707
+ }
98708
+ } catch {
98709
+ }
98710
+ }
98561
98711
  if (top && seedEv && ctx && code && !sourceImpactHint) {
98562
98712
  const scanIds = [
98563
98713
  .../* @__PURE__ */ new Set([...seeds.slice(1, 4).map((s5) => s5.id), ...(ctx.callees ?? []).slice(0, 4).map((c4) => c4.id)])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@merittdev/horus",
3
- "version": "0.17.1",
3
+ "version": "0.18.0",
4
4
  "description": "Local-first, source-aware production-incident investigation engine",
5
5
  "type": "module",
6
6
  "bin": {