@merittdev/horus 0.5.0 → 0.6.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 +33 -14
  2. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -62439,7 +62439,7 @@ init_cjs_shims();
62439
62439
 
62440
62440
  // ../../packages/core/src/version.ts
62441
62441
  init_cjs_shims();
62442
- var HORUS_VERSION = true ? "0.5.0" : "dev";
62442
+ var HORUS_VERSION = true ? "0.6.0" : "dev";
62443
62443
  var PINNED_SOURCE_VERSION = "1.0.7";
62444
62444
 
62445
62445
  // ../../packages/core/src/evidence.ts
@@ -82986,15 +82986,20 @@ function looksExplanatory(hint) {
82986
82986
  }
82987
82987
  return false;
82988
82988
  }
82989
- function formatRegressionCitation(recentChanges, changes) {
82990
- const commits = (recentChanges?.commits ?? []).slice(0, 2);
82989
+ function formatRegressionCitation(recentChanges, changes, seedFile) {
82990
+ const matchesSeed = (f) => seedFile === void 0 || f === seedFile || seedFile.endsWith(f) || f.endsWith(seedFile);
82991
+ const all = recentChanges?.commits ?? [];
82992
+ const seedCommits = seedFile ? all.filter((c) => c.files.some(matchesSeed)) : all;
82993
+ const commits = seedCommits.slice(0, 2);
82991
82994
  const commitParts = commits.map((c) => {
82992
82995
  const sha = c.shortSha || c.sha.slice(0, 7);
82993
82996
  const subject = (c.subject ?? "").replace(/\s+/g, " ").trim();
82994
82997
  return subject ? `${sha} "${subject.slice(0, 50)}"` : sha;
82995
82998
  });
82996
82999
  const commitClause = commitParts.length > 0 ? ` (${commitParts.join(", ")})` : "";
82997
- const modified = changes?.modified ?? [];
83000
+ const modified = (changes?.modified ?? []).filter(
83001
+ (m) => matchesSeed((m.after ?? m.before).filePath ?? "")
83002
+ );
82998
83003
  const symbolNames = [];
82999
83004
  for (const m of modified) {
83000
83005
  const name = m.after?.name ?? m.before?.name;
@@ -83878,13 +83883,15 @@ async function investigate(input, deps) {
83878
83883
  const gitChangedFiles = recentChanges?.changedFiles ?? [];
83879
83884
  const seedFile = top?.filePath;
83880
83885
  const seedInChanges = seedFile !== void 0 && gitChangedFiles.length > 0 && gitChangedFiles.some((f) => f === seedFile || seedFile.endsWith(f) || f.endsWith(seedFile));
83881
- const citation = formatRegressionCitation(recentChanges, changes);
83886
+ const citation = formatRegressionCitation(recentChanges, changes, seedFile);
83887
+ const regressionTitle = top && seedInChanges ? `Recent change${citation.commitClause} to ${top.name}${citation.symbolClause} in ${input.since}..HEAD may have introduced the regression` : top ? `Changes shipped in ${input.since}..HEAD but none touched ${top.name} \u2014 a regression here is unlikely to be a code change to the seed itself (check upstream deps, data, or config)` : `Recent change${citation.commitClause}${citation.symbolClause} in ${input.since}..HEAD may have introduced the regression`;
83882
83888
  causeInputs.push({
83883
83889
  id: "cause:deployment-regression",
83884
- title: top ? `Recent change${citation.commitClause} to ${top.name}${citation.symbolClause} in ${input.since}..HEAD may have introduced the regression` : `Recent change${citation.commitClause}${citation.symbolClause} in ${input.since}..HEAD may have introduced the regression`,
83890
+ title: regressionTitle,
83885
83891
  category: "deployment-regression",
83886
83892
  sourceEvidenceIds: [changeEvId, ...seedEv ? [seedEv.id] : []],
83887
- baseScore: clamp015((seedInChanges ? 0.45 : 0.25) + (queueHits.length > 0 ? 0.05 : 0)),
83893
+ // A regression NOT backed by a seed-touching commit must not score like one that is.
83894
+ baseScore: clamp015((seedInChanges ? 0.45 : 0.18) + (queueHits.length > 0 ? 0.05 : 0)),
83888
83895
  metadata: { blastRadius }
83889
83896
  });
83890
83897
  }
@@ -83997,14 +84004,25 @@ async function investigate(input, deps) {
83997
84004
  ambientLogEvIds.length > 0 ? new Set(ambientLogEvIds) : void 0
83998
84005
  );
83999
84006
  const seedResolved = seeds.length === 0 ? 0 : seedIsLowConfidence ? 0.3 : 1;
84000
- const confidence = clamp015(0.5 * evidenceConfidence + 0.5 * seedResolved);
84007
+ let confidence = clamp015(0.5 * evidenceConfidence + 0.5 * seedResolved);
84001
84008
  const area = ctx?.community?.name ?? top?.filePath ?? (input.service ?? "runtime evidence");
84002
84009
  const topCause = rankedCauses[0];
84003
- const headlineCause = topCause && topCause.finalScore >= 0.2 ? topCause : void 0;
84010
+ const seedLinkedEvIds = /* @__PURE__ */ new Set([
84011
+ ...directLogEvIds,
84012
+ ...seedEv ? [seedEv.id] : [],
84013
+ ...impactEv ? [impactEv.id] : []
84014
+ ]);
84015
+ const isLinkedToSeed = (ids2) => ids2.some((id) => seedLinkedEvIds.has(id));
84016
+ const topLinkedCause = rankedCauses.find(
84017
+ (c) => c.finalScore >= 0.2 && isLinkedToSeed(c.sourceEvidenceIds)
84018
+ );
84019
+ const headlineCause = topLinkedCause ?? (topCause && topCause.finalScore >= 0.2 ? topCause : void 0);
84020
+ const headlineLinked = headlineCause !== void 0 && isLinkedToSeed(headlineCause.sourceEvidenceIds);
84021
+ if (headlineCause && !headlineLinked) confidence = Math.min(confidence, 0.6);
84004
84022
  const banner2 = degradedNoSource ? "Runtime-only (source intelligence unavailable). " : "";
84005
84023
  const scope = top ? `resolved to ${label} (${area})` : `over runtime evidence (${area})`;
84006
84024
  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. ` : "";
84007
- let summary = headlineCause ? `${seedDisclaimer}${banner2}Investigation of "${hint}" ${scope}. Top suspected cause: ${headlineCause.title}.` : `${seedDisclaimer}${banner2}Investigation of "${hint}" ${scope}. No dominant suspected cause emerged from the available ${degradedNoSource ? "runtime" : "structural"} evidence.`;
84025
+ 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.`;
84008
84026
  const hasIncidentSignal = directLogEvIds.length > 0 || latencyMetricEvIds.length > 0 || queueMetricEvIds.length > 0 || dataAnomalyEvIds.length > 0 || queueFailureSignals.length > 0 || findings2.some((f) => f.kind === "anomaly") || headlineCause !== void 0;
84009
84027
  const explanatoryHint = looksExplanatory(hint) && !hasIncidentSignal;
84010
84028
  if (explanatoryHint) {
@@ -85133,8 +85151,7 @@ function scoreInvestigation(r) {
85133
85151
  const total = r.hypotheses.length;
85134
85152
  const resolved = r.hypotheses.filter((h) => h.verdict !== "unconfirmed").length;
85135
85153
  const discriminationValue = total > 0 ? resolved / total : 0;
85136
- const supported = r.hypotheses.filter((h) => h.verdict === "supported");
85137
- const rootCauseValue = supported.length > 0 ? Math.max(...supported.map((h) => h.confidence)) : 0;
85154
+ const rootCauseValue = r.confidence;
85138
85155
  const completenessValue = 1 - Math.min(1, r.gapAnalysis.gaps.length / 6);
85139
85156
  const actionabilityValue = r.nextActions.length > 0 ? 1 : 0;
85140
85157
  const components = [
@@ -85151,10 +85168,10 @@ function scoreInvestigation(r) {
85151
85168
  note: "fraction of hypotheses ruled in or out (vs left unconfirmed)"
85152
85169
  },
85153
85170
  {
85154
- dimension: "root-cause confidence",
85171
+ dimension: "diagnostic calibration",
85155
85172
  value: rootCauseValue,
85156
85173
  weight: 0.25,
85157
- note: "confidence in a supported root-cause candidate"
85174
+ note: "calibrated confidence in the diagnosis \u2014 a seed-linked cause scores high; honest uncertainty is not punished"
85158
85175
  },
85159
85176
  {
85160
85177
  dimension: "evidence completeness",
@@ -100810,6 +100827,8 @@ Ask a follow-up: horus ask ${report.id} "<question>"`));
100810
100827
  } finally {
100811
100828
  await sql2.end();
100812
100829
  if (mongo) await mongo.close();
100830
+ if (postgres2) await postgres2.close();
100831
+ if (redisState) await redisState.close();
100813
100832
  if (queue) await queue.close();
100814
100833
  }
100815
100834
  return 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@merittdev/horus",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Local-first, source-aware production-incident investigation engine",
5
5
  "type": "module",
6
6
  "bin": {