@merittdev/horus 0.13.2 → 0.14.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 +700 -325
  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.13.2" : "dev";
65883
+ var HORUS_VERSION = true ? "0.14.0" : "dev";
65884
65884
  var PINNED_SOURCE_VERSION = "2.0.2";
65885
65885
 
65886
65886
  // ../../packages/core/src/evidence.ts
@@ -74634,21 +74634,21 @@ function analyzeQueueSignals(q2) {
74634
74634
  if (q2.failedBreakdown && q2.failedBreakdown.length > 0 && q2.failed > 0) {
74635
74635
  const top = q2.failedBreakdown[0];
74636
74636
  const sampleTotal = q2.failedBreakdown.reduce((sum, b5) => sum + b5.count, 0);
74637
- const pct = Math.round(top.count / Math.max(sampleTotal, 1) * 100);
74638
- if (pct >= BREAKDOWN_PCT_THRESHOLD) {
74637
+ const pct2 = Math.round(top.count / Math.max(sampleTotal, 1) * 100);
74638
+ if (pct2 >= BREAKDOWN_PCT_THRESHOLD) {
74639
74639
  const stale = isStaleFailure(top.lastFailedAgeMs);
74640
74640
  const ageStr = top.lastFailedAgeMs !== void 0 ? `, last failed ${fmtMs(top.lastFailedAgeMs)} ago${stale ? " [STALE]" : ""}` : "";
74641
74641
  signals.push({
74642
74642
  queueName: q2.queueName,
74643
74643
  kind: "failed-breakdown",
74644
- title: `${q2.queueName}: ${pct}% of recently sampled failures (${top.count}/${sampleTotal} sampled) are "${top.reason}"${ageStr}`,
74644
+ title: `${q2.queueName}: ${pct2}% of recently sampled failures (${top.count}/${sampleTotal} sampled) are "${top.reason}"${ageStr}`,
74645
74645
  // Demote a stale-only breakdown so a days-old error isn't read as live (HOR-217).
74646
74646
  relevance: stale ? 0.5 : 0.82,
74647
74647
  payload: {
74648
74648
  queueName: q2.queueName,
74649
74649
  topReason: top.reason,
74650
74650
  topCount: top.count,
74651
- topPct: pct,
74651
+ topPct: pct2,
74652
74652
  totalFailed: q2.failed,
74653
74653
  topLastFailedAgeMs: top.lastFailedAgeMs ?? null,
74654
74654
  topStale: stale,
@@ -76748,7 +76748,7 @@ function sql(strings, ...params) {
76748
76748
  return new SQL([new StringChunk(str3)]);
76749
76749
  }
76750
76750
  sql2.raw = raw;
76751
- function join18(chunks, separator) {
76751
+ function join20(chunks, separator) {
76752
76752
  const result = [];
76753
76753
  for (const [i3, chunk] of chunks.entries()) {
76754
76754
  if (i3 > 0 && separator !== void 0) {
@@ -76758,7 +76758,7 @@ function sql(strings, ...params) {
76758
76758
  }
76759
76759
  return new SQL(result);
76760
76760
  }
76761
- sql2.join = join18;
76761
+ sql2.join = join20;
76762
76762
  function identifier(value) {
76763
76763
  return new Name(value);
76764
76764
  }
@@ -80349,7 +80349,7 @@ var PgSelectQueryBuilderBase = class extends TypedQueryBuilder {
80349
80349
  return (table, on2) => {
80350
80350
  const baseTableName = this.tableName;
80351
80351
  const tableName = getTableLikeName(table);
80352
- if (typeof tableName === "string" && this.config.joins?.some((join18) => join18.alias === tableName)) {
80352
+ if (typeof tableName === "string" && this.config.joins?.some((join20) => join20.alias === tableName)) {
80353
80353
  throw new Error(`Alias "${tableName}" is already used in this query`);
80354
80354
  }
80355
80355
  if (!this.isPartialSelect) {
@@ -81361,7 +81361,7 @@ var PgUpdateBase = class extends QueryPromise {
81361
81361
  createJoin(joinType) {
81362
81362
  return (table, on2) => {
81363
81363
  const tableName = getTableLikeName(table);
81364
- if (typeof tableName === "string" && this.config.joins.some((join18) => join18.alias === tableName)) {
81364
+ if (typeof tableName === "string" && this.config.joins.some((join20) => join20.alias === tableName)) {
81365
81365
  throw new Error(`Alias "${tableName}" is already used in this query`);
81366
81366
  }
81367
81367
  if (typeof on2 === "function") {
@@ -81457,10 +81457,10 @@ var PgUpdateBase = class extends QueryPromise {
81457
81457
  const fromFields = this.getTableLikeFields(this.config.from);
81458
81458
  fields[tableName] = fromFields;
81459
81459
  }
81460
- for (const join18 of this.config.joins) {
81461
- const tableName2 = getTableLikeName(join18.table);
81462
- if (typeof tableName2 === "string" && !is(join18.table, SQL)) {
81463
- const fromFields = this.getTableLikeFields(join18.table);
81460
+ for (const join20 of this.config.joins) {
81461
+ const tableName2 = getTableLikeName(join20.table);
81462
+ if (typeof tableName2 === "string" && !is(join20.table, SQL)) {
81463
+ const fromFields = this.getTableLikeFields(join20.table);
81464
81464
  fields[tableName2] = fromFields;
81465
81465
  }
81466
81466
  }
@@ -93552,16 +93552,27 @@ function rankCauses(inputs, ctx, limit = 3) {
93552
93552
  });
93553
93553
  return scored.slice(0, limit);
93554
93554
  }
93555
- function selectHeadlineCause(rankedCauses, isLinkedToSeed) {
93555
+ function selectHeadlineCause(rankedCauses, isLinkedToSeed, rerank) {
93556
93556
  let topCause;
93557
- let topLinkedCause;
93558
93557
  for (const c4 of rankedCauses) {
93559
93558
  if (topCause === void 0 || c4.finalScore > topCause.finalScore) topCause = c4;
93560
- if (c4.finalScore >= 0.2 && isLinkedToSeed(c4.sourceEvidenceIds) && (topLinkedCause === void 0 || c4.finalScore > topLinkedCause.finalScore)) {
93561
- topLinkedCause = c4;
93562
- }
93563
93559
  }
93564
- const headlineCause = topLinkedCause ?? (topCause && topCause.finalScore >= 0.2 ? topCause : void 0);
93560
+ const pickEligible = (set) => {
93561
+ if (set.length === 0) return void 0;
93562
+ if (rerank) {
93563
+ const top = rerank(set).find((c4) => set.includes(c4));
93564
+ if (top) return top;
93565
+ }
93566
+ let best = set[0];
93567
+ for (const c4 of set) if (c4.finalScore > best.finalScore) best = c4;
93568
+ return best;
93569
+ };
93570
+ const linkedEligible = rankedCauses.filter(
93571
+ (c4) => c4.finalScore >= 0.2 && isLinkedToSeed(c4.sourceEvidenceIds)
93572
+ );
93573
+ const allEligible = rankedCauses.filter((c4) => c4.finalScore >= 0.2);
93574
+ const topLinkedCause = pickEligible(linkedEligible);
93575
+ const headlineCause = topLinkedCause ?? pickEligible(allEligible);
93565
93576
  const headlineLinked = headlineCause !== void 0 && isLinkedToSeed(headlineCause.sourceEvidenceIds);
93566
93577
  return { topCause, topLinkedCause, headlineCause, headlineLinked };
93567
93578
  }
@@ -96300,7 +96311,7 @@ function formatRegressionCitation(recentChanges, changes, seedFile) {
96300
96311
  return { commitClause, symbolClause };
96301
96312
  }
96302
96313
  async function investigate(input, deps) {
96303
- const { code, db } = deps;
96314
+ const { code, db, rerank } = deps;
96304
96315
  const hint = input.hint.trim();
96305
96316
  const intent = classifyIntent(hint, input.since);
96306
96317
  const namedSymbols = parseNamedSymbols(hint);
@@ -97806,7 +97817,7 @@ async function investigate(input, deps) {
97806
97817
  ...looksPerformance(hint) ? latencyMetricEvIds : []
97807
97818
  ]);
97808
97819
  const isLinkedToSeed = (ids2) => ids2.some((id) => seedLinkedEvIds.has(id));
97809
- const { headlineCause, headlineLinked } = selectHeadlineCause(rankedCauses, isLinkedToSeed);
97820
+ const { headlineCause, headlineLinked } = selectHeadlineCause(rankedCauses, isLinkedToSeed, rerank);
97810
97821
  if (headlineCause && !headlineLinked && !sourceImpactHint) confidence = Math.min(confidence, 0.6);
97811
97822
  const banner2 = degradedNoSource ? "Runtime-only (source intelligence unavailable). " : "";
97812
97823
  const scope = top ? seedIsLowConfidence ? "could not be confidently localized from source alone" : `resolved to ${label} (${area})` : `over runtime evidence (${area})`;
@@ -99380,8 +99391,8 @@ function renderMemoryView(v4) {
99380
99391
  }
99381
99392
  const own = v4.ownedAreas.ownership;
99382
99393
  if (own != null && own.file != null) {
99383
- const pct = (own.confidence * 100).toFixed(0);
99384
- lines.push(`Likely maintainer: ${own.likelyMaintainer ?? "(unknown)"} (${pct}% confidence)`);
99394
+ const pct2 = (own.confidence * 100).toFixed(0);
99395
+ lines.push(`Likely maintainer: ${own.likelyMaintainer ?? "(unknown)"} (${pct2}% confidence)`);
99385
99396
  if (own.mostActiveRecent != null) {
99386
99397
  lines.push(`Most active recently: ${own.mostActiveRecent}`);
99387
99398
  }
@@ -99561,9 +99572,9 @@ function renderOwnership(o5) {
99561
99572
  }
99562
99573
  lines.push("File : " + o5.file);
99563
99574
  lines.push("");
99564
- const pct = (o5.confidence * 100).toFixed(0);
99575
+ const pct2 = (o5.confidence * 100).toFixed(0);
99565
99576
  lines.push(
99566
- "Likely maintainer : " + (o5.likelyMaintainer ?? "(unknown)") + " (" + pct + "% confidence)"
99577
+ "Likely maintainer : " + (o5.likelyMaintainer ?? "(unknown)") + " (" + pct2 + "% confidence)"
99567
99578
  );
99568
99579
  lines.push("Most active recently: " + (o5.mostActiveRecent ?? "(unknown)"));
99569
99580
  lines.push("");
@@ -100446,10 +100457,10 @@ function renderOnboarding(g4) {
100446
100457
  lines.push("## Who owns this area");
100447
100458
  lines.push("");
100448
100459
  if (g4.ownership != null && g4.ownership.file != null) {
100449
- const pct = (g4.ownership.confidence * 100).toFixed(0);
100460
+ const pct2 = (g4.ownership.confidence * 100).toFixed(0);
100450
100461
  lines.push(`File: ${g4.ownership.file}`);
100451
100462
  lines.push(
100452
- `Likely maintainer: ${g4.ownership.likelyMaintainer ?? "(unknown)"} (${pct}% confidence)`
100463
+ `Likely maintainer: ${g4.ownership.likelyMaintainer ?? "(unknown)"} (${pct2}% confidence)`
100453
100464
  );
100454
100465
  } else {
100455
100466
  lines.push(
@@ -102784,27 +102795,27 @@ function extractQueueGraph(input) {
102784
102795
  }
102785
102796
  }
102786
102797
  const producers = [];
102787
- for (const pc58 of input.producerClasses) {
102788
- const loopVarEnums = loopVarEnumsFor(pc58.content);
102789
- for (const m5 of pc58.content.matchAll(INJECT_QUEUE_RE)) {
102798
+ for (const pc59 of input.producerClasses) {
102799
+ const loopVarEnums = loopVarEnumsFor(pc59.content);
102800
+ for (const m5 of pc59.content.matchAll(INJECT_QUEUE_RE)) {
102790
102801
  const queue = m5[1] ?? "";
102791
- if (queue) producers.push({ queue, symbol: pc58.name, file: pc58.filePath });
102802
+ if (queue) producers.push({ queue, symbol: pc59.name, file: pc59.filePath });
102792
102803
  }
102793
- for (const m5 of pc58.content.matchAll(NEW_BULL_RE)) {
102804
+ for (const m5 of pc59.content.matchAll(NEW_BULL_RE)) {
102794
102805
  if (m5[2] !== "Queue") continue;
102795
102806
  const queues2 = resolveArg(m5[3] ?? "", constMap, enums, loopVarEnums);
102796
102807
  for (const queue of queues2) {
102797
102808
  producers.push({
102798
102809
  queue,
102799
- symbol: (m5[1] ?? pc58.name) || baseName(pc58.filePath),
102800
- file: pc58.filePath
102810
+ symbol: (m5[1] ?? pc59.name) || baseName(pc59.filePath),
102811
+ file: pc59.filePath
102801
102812
  });
102802
102813
  }
102803
102814
  }
102804
- for (const m5 of pc58.content.matchAll(QUEUE_NAME_CONST_RE)) {
102815
+ for (const m5 of pc59.content.matchAll(QUEUE_NAME_CONST_RE)) {
102805
102816
  const ident = m5[1] ?? "";
102806
102817
  const queue = m5[2] ?? "";
102807
- if (queue) producers.push({ queue, symbol: ident || baseName(pc58.filePath), file: pc58.filePath });
102818
+ if (queue) producers.push({ queue, symbol: ident || baseName(pc59.filePath), file: pc59.filePath });
102808
102819
  }
102809
102820
  }
102810
102821
  const workers = [];
@@ -116553,6 +116564,479 @@ function renderFreshness(f5) {
116553
116564
  // ../../packages/cli/src/lib/investigation-runner.ts
116554
116565
  init_cjs_shims();
116555
116566
  var import_picocolors16 = __toESM(require_picocolors(), 1);
116567
+ var import_node_fs19 = require("fs");
116568
+ var import_node_path21 = require("path");
116569
+ var import_node_os8 = require("os");
116570
+
116571
+ // ../../packages/eval/src/index.ts
116572
+ init_cjs_shims();
116573
+
116574
+ // ../../packages/eval/src/types.ts
116575
+ init_cjs_shims();
116576
+ var CORPUS_VERSION = "v1";
116577
+
116578
+ // ../../packages/eval/src/corpus.ts
116579
+ init_cjs_shims();
116580
+ function toUsableReport(blob) {
116581
+ if (blob === null || typeof blob !== "object") return null;
116582
+ const r = blob;
116583
+ if (!Array.isArray(r.suspectedCauses)) return null;
116584
+ const seeds = Array.isArray(r.seeds) ? r.seeds.map((s5) => s5 && typeof s5 === "object" && "name" in s5 ? String(s5.name) : "").filter(Boolean) : [];
116585
+ return {
116586
+ summary: typeof r.summary === "string" ? r.summary : "",
116587
+ confidence: typeof r.confidence === "number" && Number.isFinite(r.confidence) ? r.confidence : 0,
116588
+ seeds,
116589
+ causes: r.suspectedCauses
116590
+ };
116591
+ }
116592
+ function pickHeadlineCause(causes) {
116593
+ let best = null;
116594
+ for (const c4 of causes) {
116595
+ if (best === null || c4.finalScore > best.finalScore || c4.finalScore === best.finalScore && c4.id < best.id) {
116596
+ best = c4;
116597
+ }
116598
+ }
116599
+ return best;
116600
+ }
116601
+ function flattenFactors(cause) {
116602
+ const sums = /* @__PURE__ */ new Map();
116603
+ for (const e of cause.explanations ?? []) {
116604
+ sums.set(e.factor, (sums.get(e.factor) ?? 0) + e.delta);
116605
+ }
116606
+ return [...sums.entries()].map(([factor, delta]) => ({ factor, delta })).sort((a2, b5) => a2.factor < b5.factor ? -1 : a2.factor > b5.factor ? 1 : 0);
116607
+ }
116608
+ function projectHeadline(cause) {
116609
+ return {
116610
+ id: cause.id,
116611
+ category: cause.category,
116612
+ finalScore: cause.finalScore,
116613
+ confidence: cause.confidence,
116614
+ band: cause.band,
116615
+ sourceEvidenceIds: [...cause.sourceEvidenceIds ?? []].sort(),
116616
+ factors: flattenFactors(cause)
116617
+ };
116618
+ }
116619
+ function buildCorpus(labels, resolveReport) {
116620
+ const current = dedupeToCurrentVerdict(labels);
116621
+ const evaluated = current.length;
116622
+ const rows = [];
116623
+ const quarantined = [];
116624
+ const unjoinable = [];
116625
+ for (const label of current) {
116626
+ if (label.investigationId === null) {
116627
+ unjoinable.push({ investigationId: null, reason: "null-investigation" });
116628
+ continue;
116629
+ }
116630
+ if (!isOutcomeResolved(label.resolved) || !isOutcomeSource(label.source)) {
116631
+ quarantined.push({
116632
+ investigationId: label.investigationId,
116633
+ resolved: String(label.resolved),
116634
+ source: String(label.source),
116635
+ reason: !isOutcomeResolved(label.resolved) ? `invalid resolved "${String(label.resolved)}"` : `invalid source "${String(label.source)}"`
116636
+ });
116637
+ continue;
116638
+ }
116639
+ const blob = resolveReport(label.investigationId);
116640
+ if (blob === null || blob === void 0) {
116641
+ unjoinable.push({ investigationId: label.investigationId, reason: "missing-report" });
116642
+ continue;
116643
+ }
116644
+ const report = toUsableReport(blob);
116645
+ if (report === null) {
116646
+ unjoinable.push({ investigationId: label.investigationId, reason: "legacy-report" });
116647
+ continue;
116648
+ }
116649
+ const isConfirm = label.source === "confirm";
116650
+ const headline = pickHeadlineCause(report.causes);
116651
+ rows.push({
116652
+ investigationId: label.investigationId,
116653
+ project: label.project,
116654
+ at: label.at instanceof Date ? label.at.toISOString() : String(label.at),
116655
+ target: label.resolved,
116656
+ source: label.source,
116657
+ weak: isConfirm,
116658
+ confirmedCause: isConfirm ? null : label.confirmedCause ?? null,
116659
+ summary: report.summary,
116660
+ reportConfidence: report.confidence,
116661
+ seeds: report.seeds,
116662
+ causeCount: report.causes.length,
116663
+ headlineCause: headline ? projectHeadline(headline) : null
116664
+ });
116665
+ }
116666
+ const classBalance = { yes: 0, partly: 0, no: 0 };
116667
+ const bySource = { feedback: 0, confirm: 0 };
116668
+ for (const r of rows) {
116669
+ classBalance[r.target] += 1;
116670
+ bySource[r.source] += 1;
116671
+ }
116672
+ return { rows, evaluated, quarantined, unjoinable, classBalance, bySource };
116673
+ }
116674
+ function corpusFilename(version3 = CORPUS_VERSION) {
116675
+ return `corpus-${version3}.jsonl`;
116676
+ }
116677
+ function sortRows(rows) {
116678
+ return [...rows].sort((a2, b5) => {
116679
+ if (a2.investigationId !== b5.investigationId) return a2.investigationId < b5.investigationId ? -1 : 1;
116680
+ if (a2.at !== b5.at) return a2.at < b5.at ? -1 : 1;
116681
+ return 0;
116682
+ });
116683
+ }
116684
+ function fnv1a(s5) {
116685
+ let h3 = 2166136261;
116686
+ for (let i3 = 0; i3 < s5.length; i3++) {
116687
+ h3 ^= s5.charCodeAt(i3);
116688
+ h3 = Math.imul(h3, 16777619);
116689
+ }
116690
+ return h3 >>> 0;
116691
+ }
116692
+ function holdoutSplit(rows, holdoutPct = 20) {
116693
+ const train = [];
116694
+ const holdout = [];
116695
+ for (const r of rows) {
116696
+ if (fnv1a(r.investigationId) % 100 < holdoutPct) holdout.push(r.investigationId);
116697
+ else train.push(r.investigationId);
116698
+ }
116699
+ train.sort();
116700
+ holdout.sort();
116701
+ return { holdoutPct, train, holdout };
116702
+ }
116703
+ function serializeCorpus(build, version3 = CORPUS_VERSION) {
116704
+ const sorted = sortRows(build.rows);
116705
+ const jsonl = sorted.map((r) => JSON.stringify(r)).join("\n") + (sorted.length > 0 ? "\n" : "");
116706
+ const manifest = {
116707
+ version: version3,
116708
+ generatedAt: null,
116709
+ rows: sorted.length,
116710
+ evaluated: build.evaluated,
116711
+ quarantined: build.quarantined.length,
116712
+ unjoinable: build.unjoinable.length,
116713
+ classBalance: build.classBalance,
116714
+ bySource: build.bySource,
116715
+ holdout: holdoutSplit(sorted)
116716
+ };
116717
+ return { filename: corpusFilename(version3), jsonl, manifest };
116718
+ }
116719
+
116720
+ // ../../packages/eval/src/baseline.ts
116721
+ init_cjs_shims();
116722
+ function computeBaseline(labels) {
116723
+ const summary = summarizeOutcomeLabels(labels);
116724
+ const byProjectMap = /* @__PURE__ */ new Map();
116725
+ for (const l2 of labels) {
116726
+ const key = l2.project ?? null;
116727
+ const arr = byProjectMap.get(key);
116728
+ if (arr) arr.push(l2);
116729
+ else byProjectMap.set(key, [l2]);
116730
+ }
116731
+ const byProject = [...byProjectMap.entries()].map(([project, slice]) => {
116732
+ const s5 = summarizeOutcomeLabels(slice);
116733
+ return {
116734
+ project,
116735
+ n: s5.evaluated,
116736
+ strictHitRate: s5.accuracy,
116737
+ weightedHitRate: s5.weightedScore
116738
+ };
116739
+ }).sort((a2, b5) => {
116740
+ if (a2.project === b5.project) return 0;
116741
+ if (a2.project === null) return 1;
116742
+ if (b5.project === null) return -1;
116743
+ return a2.project < b5.project ? -1 : 1;
116744
+ });
116745
+ return {
116746
+ n: summary.evaluated,
116747
+ strictHitRate: summary.accuracy,
116748
+ weightedHitRate: summary.weightedScore,
116749
+ classBalance: summary.counts,
116750
+ bySource: summary.bySource,
116751
+ byProject
116752
+ };
116753
+ }
116754
+ function mean(xs) {
116755
+ if (xs.length === 0) return 0;
116756
+ return xs.reduce((a2, b5) => a2 + b5, 0) / xs.length;
116757
+ }
116758
+ function variance(xs, m5) {
116759
+ if (xs.length === 0) return 0;
116760
+ return xs.reduce((a2, b5) => a2 + (b5 - m5) * (b5 - m5), 0) / xs.length;
116761
+ }
116762
+ function featureSeparation(rows) {
116763
+ const usable = rows.filter(
116764
+ (r) => !r.weak && r.headlineCause !== null && (r.target === "yes" || r.target === "no")
116765
+ );
116766
+ const factorSet = /* @__PURE__ */ new Set();
116767
+ for (const r of usable) {
116768
+ for (const f5 of r.headlineCause.factors) factorSet.add(f5.factor);
116769
+ }
116770
+ const factors = [...factorSet].sort();
116771
+ const out2 = factors.map((factor) => {
116772
+ const yes = [];
116773
+ const no = [];
116774
+ for (const r of usable) {
116775
+ const f5 = r.headlineCause.factors.find((x4) => x4.factor === factor);
116776
+ const delta = f5 ? f5.delta : 0;
116777
+ if (r.target === "yes") yes.push(delta);
116778
+ else no.push(delta);
116779
+ }
116780
+ const meanYes = mean(yes);
116781
+ const meanNo = mean(no);
116782
+ const varYes = variance(yes, meanYes);
116783
+ const varNo = variance(no, meanNo);
116784
+ const separation = meanYes - meanNo;
116785
+ const all = yes.concat(no);
116786
+ const overallMean = mean(all);
116787
+ const pooledVar = variance(all, overallMean);
116788
+ const pooledStd = Math.sqrt(pooledVar);
116789
+ const effectSize = pooledStd === 0 ? 0 : separation / pooledStd;
116790
+ const cv = overallMean === 0 ? 0 : pooledStd / Math.abs(overallMean);
116791
+ return {
116792
+ factor,
116793
+ nYes: yes.length,
116794
+ nNo: no.length,
116795
+ meanYes,
116796
+ meanNo,
116797
+ varYes,
116798
+ varNo,
116799
+ separation,
116800
+ effectSize,
116801
+ cv
116802
+ };
116803
+ });
116804
+ out2.sort((a2, b5) => {
116805
+ const d3 = Math.abs(b5.effectSize) - Math.abs(a2.effectSize);
116806
+ if (d3 !== 0) return d3;
116807
+ return a2.factor < b5.factor ? -1 : a2.factor > b5.factor ? 1 : 0;
116808
+ });
116809
+ return { evaluated: usable.length, factors: out2 };
116810
+ }
116811
+
116812
+ // ../../packages/eval/src/reranker.ts
116813
+ init_cjs_shims();
116814
+ var RERANKER_VERSION = "v1";
116815
+ var MIN_TRAIN_INVESTIGATIONS = 25;
116816
+ var SCALAR_KEYS = ["finalScore", "confidence", "baseScore", "evidenceCount", "affectedCount"];
116817
+ function tokenize4(s5) {
116818
+ return new Set(
116819
+ s5.toLowerCase().split(/[^a-z0-9]+/).filter((t2) => t2.length >= 3)
116820
+ );
116821
+ }
116822
+ function matchesConfirmedCause(confirmedCause, cause) {
116823
+ const a2 = tokenize4(confirmedCause);
116824
+ const b5 = tokenize4(`${cause.title} ${cause.category}`);
116825
+ if (a2.size === 0 || b5.size === 0) return 0;
116826
+ let overlap = 0;
116827
+ for (const t2 of a2) if (b5.has(t2)) overlap += 1;
116828
+ return overlap / a2.size;
116829
+ }
116830
+ function collectFeatureKeys(investigations2) {
116831
+ const factors = /* @__PURE__ */ new Set();
116832
+ for (const inv of investigations2) {
116833
+ for (const c4 of inv.candidates) {
116834
+ for (const e of c4.explanations ?? []) factors.add(e.factor);
116835
+ }
116836
+ }
116837
+ return [...SCALAR_KEYS, ...[...factors].sort().map((f5) => `f:${f5}`)];
116838
+ }
116839
+ function extractRawFeatures(cause, featureKeys) {
116840
+ const factorDelta = /* @__PURE__ */ new Map();
116841
+ for (const e of cause.explanations ?? []) {
116842
+ factorDelta.set(e.factor, (factorDelta.get(e.factor) ?? 0) + e.delta);
116843
+ }
116844
+ return featureKeys.map((k3) => {
116845
+ switch (k3) {
116846
+ case "finalScore":
116847
+ return cause.finalScore;
116848
+ case "confidence":
116849
+ return cause.confidence;
116850
+ case "baseScore":
116851
+ return cause.baseScore;
116852
+ case "evidenceCount":
116853
+ return cause.sourceEvidenceIds?.length ?? 0;
116854
+ case "affectedCount":
116855
+ return cause.affectedNodeIds?.length ?? 0;
116856
+ default:
116857
+ return factorDelta.get(k3.slice(2)) ?? 0;
116858
+ }
116859
+ });
116860
+ }
116861
+ function baselineTop(candidates) {
116862
+ let best = null;
116863
+ for (const c4 of candidates) {
116864
+ if (best === null || c4.finalScore > best.finalScore || c4.finalScore === best.finalScore && c4.id < best.id) {
116865
+ best = c4;
116866
+ }
116867
+ }
116868
+ return best;
116869
+ }
116870
+ function deriveLabels(inv) {
116871
+ const cands = inv.candidates;
116872
+ if (cands.length === 0) return { relevantId: null, examples: [] };
116873
+ if (inv.confirmedCause && inv.confirmedCause.trim() !== "") {
116874
+ let bestId = null;
116875
+ let bestScore = 0;
116876
+ for (const c4 of cands) {
116877
+ const m5 = matchesConfirmedCause(inv.confirmedCause, c4);
116878
+ if (m5 > bestScore) {
116879
+ bestScore = m5;
116880
+ bestId = c4.id;
116881
+ }
116882
+ }
116883
+ if (bestId !== null && bestScore >= 0.34) {
116884
+ return {
116885
+ relevantId: bestId,
116886
+ examples: cands.map((c4) => ({ cause: c4, label: c4.id === bestId ? 1 : 0 }))
116887
+ };
116888
+ }
116889
+ }
116890
+ const top = baselineTop(cands);
116891
+ if (top === null) return { relevantId: null, examples: [] };
116892
+ if (inv.target === "yes") {
116893
+ return { relevantId: top.id, examples: cands.map((c4) => ({ cause: c4, label: c4.id === top.id ? 1 : 0 })) };
116894
+ }
116895
+ if (inv.target === "no") {
116896
+ return { relevantId: null, examples: [{ cause: top, label: 0 }] };
116897
+ }
116898
+ return { relevantId: null, examples: [] };
116899
+ }
116900
+ function sigmoid(z4) {
116901
+ if (z4 >= 0) return 1 / (1 + Math.exp(-z4));
116902
+ const e = Math.exp(z4);
116903
+ return e / (1 + e);
116904
+ }
116905
+ function fitLogistic(X2, y5, opts = {}) {
116906
+ const epochs = opts.epochs ?? 400;
116907
+ const lr = opts.lr ?? 0.1;
116908
+ const l2 = opts.l2 ?? 1e-3;
116909
+ const dim = X2[0]?.length ?? 0;
116910
+ const weights = new Array(dim).fill(0);
116911
+ let bias = 0;
116912
+ const n4 = X2.length;
116913
+ if (n4 === 0) return { weights, bias };
116914
+ for (let epoch = 0; epoch < epochs; epoch++) {
116915
+ const gradW = new Array(dim).fill(0);
116916
+ let gradB = 0;
116917
+ for (let i3 = 0; i3 < n4; i3++) {
116918
+ const xi = X2[i3];
116919
+ let z4 = bias;
116920
+ for (let j3 = 0; j3 < dim; j3++) z4 += weights[j3] * xi[j3];
116921
+ const err2 = sigmoid(z4) - y5[i3];
116922
+ for (let j3 = 0; j3 < dim; j3++) gradW[j3] = gradW[j3] + err2 * xi[j3];
116923
+ gradB += err2;
116924
+ }
116925
+ for (let j3 = 0; j3 < dim; j3++) weights[j3] = weights[j3] - lr * (gradW[j3] / n4 + l2 * weights[j3]);
116926
+ bias -= lr * (gradB / n4);
116927
+ }
116928
+ return { weights, bias };
116929
+ }
116930
+ function scoreVector(weights, bias, x4) {
116931
+ let z4 = bias;
116932
+ for (let j3 = 0; j3 < weights.length; j3++) z4 += weights[j3] * x4[j3];
116933
+ return z4;
116934
+ }
116935
+ function standardizeParams(X2) {
116936
+ const dim = X2[0]?.length ?? 0;
116937
+ const mean2 = new Array(dim).fill(0);
116938
+ const std = new Array(dim).fill(0);
116939
+ if (X2.length === 0) return { mean: mean2, std };
116940
+ for (const row of X2) for (let j3 = 0; j3 < dim; j3++) mean2[j3] = mean2[j3] + row[j3];
116941
+ for (let j3 = 0; j3 < dim; j3++) mean2[j3] = mean2[j3] / X2.length;
116942
+ for (const row of X2) for (let j3 = 0; j3 < dim; j3++) std[j3] = std[j3] + (row[j3] - mean2[j3]) ** 2;
116943
+ for (let j3 = 0; j3 < dim; j3++) std[j3] = Math.sqrt(std[j3] / X2.length) || 1;
116944
+ return { mean: mean2, std };
116945
+ }
116946
+ function applyStandardize(x4, mean2, std) {
116947
+ return x4.map((v4, j3) => (v4 - mean2[j3]) / std[j3]);
116948
+ }
116949
+ function top1HitRate(set, score) {
116950
+ if (set.length === 0) return 0;
116951
+ let hits = 0;
116952
+ for (const { inv, relevantId } of set) {
116953
+ let bestId = null;
116954
+ let bestScore = -Infinity;
116955
+ for (const c4 of inv.candidates) {
116956
+ const s5 = score(c4);
116957
+ if (s5 > bestScore || s5 === bestScore && bestId !== null && c4.id < bestId) {
116958
+ bestScore = s5;
116959
+ bestId = c4.id;
116960
+ }
116961
+ }
116962
+ if (bestId === relevantId) hits += 1;
116963
+ }
116964
+ return hits / set.length;
116965
+ }
116966
+ function trainReranker(investigations2, holdoutIds, opts = {}) {
116967
+ const labeled = investigations2.map((inv) => ({ inv, derived: deriveLabels(inv) })).filter((x4) => x4.derived.examples.length > 0);
116968
+ const scorable = labeled.filter((x4) => x4.derived.relevantId !== null && x4.inv.candidates.length >= 2);
116969
+ if (scorable.length < MIN_TRAIN_INVESTIGATIONS) {
116970
+ return {
116971
+ ok: false,
116972
+ reason: "insufficient-corpus",
116973
+ detail: `need \u2265${MIN_TRAIN_INVESTIGATIONS} labeled investigations with a known-correct candidate; have ${scorable.length}`,
116974
+ labeledInvestigations: scorable.length
116975
+ };
116976
+ }
116977
+ const featureKeys = collectFeatureKeys(investigations2);
116978
+ const trainSet = scorable.filter((x4) => !holdoutIds.has(x4.inv.investigationId));
116979
+ const holdoutSet = scorable.filter((x4) => holdoutIds.has(x4.inv.investigationId));
116980
+ if (holdoutSet.length === 0 || trainSet.length === 0) {
116981
+ return {
116982
+ ok: false,
116983
+ reason: "degenerate-holdout",
116984
+ detail: `train/holdout split left ${trainSet.length} train / ${holdoutSet.length} holdout`,
116985
+ labeledInvestigations: scorable.length
116986
+ };
116987
+ }
116988
+ const rawX = [];
116989
+ const y5 = [];
116990
+ for (const { inv } of trainSet) {
116991
+ for (const ex of deriveLabels(inv).examples) {
116992
+ rawX.push(extractRawFeatures(ex.cause, featureKeys));
116993
+ y5.push(ex.label);
116994
+ }
116995
+ }
116996
+ if (rawX.length === 0) {
116997
+ return { ok: false, reason: "no-derivable-labels", detail: "no training examples after split", labeledInvestigations: scorable.length };
116998
+ }
116999
+ const { mean: mean2, std } = standardizeParams(rawX);
117000
+ const X2 = rawX.map((row) => applyStandardize(row, mean2, std));
117001
+ const { weights, bias } = fitLogistic(X2, y5, opts);
117002
+ const modelScore = (c4) => scoreVector(weights, bias, applyStandardize(extractRawFeatures(c4, featureKeys), mean2, std));
117003
+ const holdoutScored = holdoutSet.map((x4) => ({ inv: x4.inv, relevantId: x4.derived.relevantId }));
117004
+ const baselineHitRate = top1HitRate(holdoutScored, (c4) => c4.finalScore);
117005
+ const rerankedHitRate = top1HitRate(holdoutScored, modelScore);
117006
+ return {
117007
+ ok: true,
117008
+ version: RERANKER_VERSION,
117009
+ featureKeys,
117010
+ mean: mean2,
117011
+ std,
117012
+ weights,
117013
+ bias,
117014
+ holdout: {
117015
+ n: holdoutScored.length,
117016
+ baselineHitRate,
117017
+ rerankedHitRate,
117018
+ delta: rerankedHitRate - baselineHitRate
117019
+ },
117020
+ trainExamples: rawX.length,
117021
+ trainInvestigations: trainSet.length
117022
+ };
117023
+ }
117024
+ function applyReranker(model, candidates) {
117025
+ const scored = candidates.map((c4, i3) => ({
117026
+ c: c4,
117027
+ i: i3,
117028
+ s: scoreVector(model.weights, model.bias, applyStandardize(extractRawFeatures(c4, model.featureKeys), model.mean, model.std))
117029
+ }));
117030
+ scored.sort((a2, b5) => b5.s - a2.s || a2.i - b5.i);
117031
+ return scored.map((x4) => x4.c);
117032
+ }
117033
+ function isRerankerModel(blob) {
117034
+ if (blob === null || typeof blob !== "object") return false;
117035
+ const m5 = blob;
117036
+ return Array.isArray(m5.featureKeys) && Array.isArray(m5.weights) && Array.isArray(m5.mean) && Array.isArray(m5.std) && typeof m5.bias === "number" && m5.weights.length === m5.featureKeys.length;
117037
+ }
117038
+
117039
+ // ../../packages/cli/src/lib/investigation-runner.ts
116556
117040
  async function buildInvestigationContext(renv, opts) {
116557
117041
  const log = opts.log ?? ((l2) => console.error(l2));
116558
117042
  let code = codeForEnv(renv);
@@ -116611,8 +117095,21 @@ function databaseUrlOrThrow(url) {
116611
117095
  if (!url) throw new Error("No database URL configured for the investigation store.");
116612
117096
  return url;
116613
117097
  }
117098
+ function loadReranker() {
117099
+ const flag = process.env.HORUS_RERANK;
117100
+ if (!flag || flag === "0" || flag.toLowerCase() === "false") return void 0;
117101
+ try {
117102
+ const blob = JSON.parse((0, import_node_fs19.readFileSync)((0, import_node_path21.join)((0, import_node_os8.homedir)(), ".horus", "reranker.json"), "utf8"));
117103
+ if (!isRerankerModel(blob)) return void 0;
117104
+ if ((blob.holdout?.delta ?? 0) <= 0) return void 0;
117105
+ return (causes) => applyReranker(blob, causes);
117106
+ } catch {
117107
+ return void 0;
117108
+ }
117109
+ }
116614
117110
  async function runOneInvestigation(input, ctx, opts = {}) {
116615
117111
  const { renv } = ctx;
117112
+ const rerank = loadReranker();
116616
117113
  const investigation = investigate(
116617
117114
  {
116618
117115
  hint: input.hint,
@@ -116646,7 +117143,10 @@ async function runOneInvestigation(input, ctx, opts = {}) {
116646
117143
  axiom: !!renv.connectors.axiom,
116647
117144
  redis: !!renv.connectors.redis?.url,
116648
117145
  queue: !!ctx.queue
116649
- }
117146
+ },
117147
+ // HOR-404: inject the learned reranker only when enabled + proven (loadReranker gates both);
117148
+ // CONTEXT-ONLY — it reorders eligible causes, never touches confidence/verdict/ceilings.
117149
+ ...rerank ? { rerank } : {}
116650
117150
  }
116651
117151
  );
116652
117152
  const timeoutMs = opts.timeoutMs ?? (Number(process.env.HORUS_INVESTIGATE_TIMEOUT_S) || 120) * 1e3;
@@ -117980,8 +118480,8 @@ async function runOwner(query, opts) {
117980
118480
 
117981
118481
  // ../../packages/cli/src/commands/postmortem.ts
117982
118482
  init_cjs_shims();
117983
- var import_node_fs19 = require("fs");
117984
- var import_node_path21 = require("path");
118483
+ var import_node_fs20 = require("fs");
118484
+ var import_node_path22 = require("path");
117985
118485
  var import_picocolors30 = __toESM(require_picocolors(), 1);
117986
118486
  async function runPostmortem(id, opts) {
117987
118487
  const log = opts.write ?? ((line2) => console.log(line2));
@@ -118092,14 +118592,14 @@ _AI summary unavailable: ${validationErrors?.[0] ?? "provider error"}_
118092
118592
  }
118093
118593
  }
118094
118594
  if (opts.output) {
118095
- const outputPath = (0, import_node_path21.resolve)(opts.output);
118096
- if ((0, import_node_fs19.existsSync)(outputPath) && !opts.force) {
118595
+ const outputPath = (0, import_node_path22.resolve)(opts.output);
118596
+ if ((0, import_node_fs20.existsSync)(outputPath) && !opts.force) {
118097
118597
  log(import_picocolors30.default.red("\u2717") + " " + outputPath + " already exists");
118098
118598
  log(import_picocolors30.default.dim(" pass --force to overwrite"));
118099
118599
  return 1;
118100
118600
  }
118101
118601
  try {
118102
- (0, import_node_fs19.writeFileSync)(outputPath, content, "utf8");
118602
+ (0, import_node_fs20.writeFileSync)(outputPath, content, "utf8");
118103
118603
  } catch (err2) {
118104
118604
  log(import_picocolors30.default.red("\u2717 Could not write " + outputPath + ": ") + err2.message);
118105
118605
  return 1;
@@ -119683,7 +120183,7 @@ async function runMemoryAccuracy(opts) {
119683
120183
  )
119684
120184
  );
119685
120185
  } else {
119686
- const pct = (n4) => `${(n4 * 100).toFixed(0)}%`;
120186
+ const pct2 = (n4) => `${(n4 * 100).toFixed(0)}%`;
119687
120187
  console.log(import_picocolors34.default.bold(`Investigation accuracy \u2014 ${project}`) + import_picocolors34.default.dim(` \xB7 window: ${window2}`));
119688
120188
  if (summary.evaluated === 0) {
119689
120189
  console.log(
@@ -119693,7 +120193,7 @@ async function runMemoryAccuracy(opts) {
119693
120193
  );
119694
120194
  } else {
119695
120195
  console.log(
119696
- ` Pointed at the cause: ${import_picocolors34.default.green(pct(summary.accuracy))} ` + import_picocolors34.default.dim(`(weighted ${pct(summary.weightedScore)}) over ${summary.evaluated} investigation(s)`)
120196
+ ` Pointed at the cause: ${import_picocolors34.default.green(pct2(summary.accuracy))} ` + import_picocolors34.default.dim(`(weighted ${pct2(summary.weightedScore)}) over ${summary.evaluated} investigation(s)`)
119697
120197
  );
119698
120198
  console.log(
119699
120199
  ` Verdicts: ${import_picocolors34.default.green(`${summary.counts.yes} yes`)}, ${import_picocolors34.default.yellow(`${summary.counts.partly} partly`)}, ${import_picocolors34.default.red(`${summary.counts.no} no`)}`
@@ -120497,12 +120997,12 @@ async function runState(opts) {
120497
120997
  // ../../packages/cli/src/commands/init.ts
120498
120998
  init_cjs_shims();
120499
120999
  var import_picocolors39 = __toESM(require_picocolors(), 1);
120500
- var import_node_path22 = require("path");
121000
+ var import_node_path23 = require("path");
120501
121001
  async function runInit(opts) {
120502
121002
  try {
120503
121003
  const cwd = process.cwd();
120504
- const root = opts.path ? (0, import_node_path22.resolve)(opts.path) : findRepoRoot(cwd) ?? cwd;
120505
- const name2 = opts.name ?? (0, import_node_path22.basename)(root);
121004
+ const root = opts.path ? (0, import_node_path23.resolve)(opts.path) : findRepoRoot(cwd) ?? cwd;
121005
+ const name2 = opts.name ?? (0, import_node_path23.basename)(root);
120506
121006
  const envName = opts.env ?? "production";
120507
121007
  const repo = { name: name2, path: root };
120508
121008
  if (opts.source) repo["source"] = { hostUrl: opts.source };
@@ -121888,12 +122388,12 @@ function redactUrl(raw) {
121888
122388
  // ../../packages/cli/src/commands/stop.ts
121889
122389
  init_cjs_shims();
121890
122390
  var import_node_child_process10 = require("child_process");
121891
- var import_node_fs20 = require("fs");
122391
+ var import_node_fs21 = require("fs");
121892
122392
  var import_node_util6 = require("util");
121893
- var import_node_path23 = require("path");
122393
+ var import_node_path24 = require("path");
121894
122394
  var import_picocolors45 = __toESM(require_picocolors(), 1);
121895
122395
  var execFileAsync3 = (0, import_node_util6.promisify)(import_node_child_process10.execFile);
121896
- var unlinkAsync = (0, import_node_util6.promisify)(import_node_fs20.unlink);
122396
+ var unlinkAsync = (0, import_node_util6.promisify)(import_node_fs21.unlink);
121897
122397
  var SPAWNED_HOST_FILE2 = "spawned-host.json";
121898
122398
  var START_TIME_TOLERANCE_S = 60;
121899
122399
  var STOP_WAIT_MS = 5e3;
@@ -122035,11 +122535,11 @@ function isOwnHostProcess(pid, port, root, args2) {
122035
122535
  if (hostArgvRegex(port).test(args2)) return true;
122036
122536
  if (!hostArgvAnyPortRegex().test(args2)) return false;
122037
122537
  const rec = readSourceHostPid(root);
122038
- return rec !== null && rec.pid === pid && Number.isFinite(rec.port) && rec.port === port && !!rec.repoPath && (0, import_node_path23.resolve)(rec.repoPath) === (0, import_node_path23.resolve)(root);
122538
+ return rec !== null && rec.pid === pid && Number.isFinite(rec.port) && rec.port === port && !!rec.repoPath && (0, import_node_path24.resolve)(rec.repoPath) === (0, import_node_path24.resolve)(root);
122039
122539
  }
122040
122540
  async function cleanupSpawnedRecord(root) {
122041
122541
  try {
122042
- await unlinkAsync((0, import_node_path23.join)(root, HORUS_DIR, SPAWNED_HOST_FILE2));
122542
+ await unlinkAsync((0, import_node_path24.join)(root, HORUS_DIR, SPAWNED_HOST_FILE2));
122043
122543
  } catch {
122044
122544
  }
122045
122545
  }
@@ -122710,8 +123210,8 @@ async function runProvidersDoctorCommand(opts) {
122710
123210
 
122711
123211
  // ../../packages/cli/src/commands/generate-config.ts
122712
123212
  init_cjs_shims();
122713
- var import_node_fs21 = require("fs");
122714
- var import_node_path24 = require("path");
123213
+ var import_node_fs22 = require("fs");
123214
+ var import_node_path25 = require("path");
122715
123215
  var import_picocolors49 = __toESM(require_picocolors(), 1);
122716
123216
  function configTemplate(name2, repoPath) {
122717
123217
  return `export default {
@@ -122760,17 +123260,17 @@ async function runGenerateConfig(opts) {
122760
123260
  const defaults2 = localConfigPath2 != null ? projectDefaults(localConfigPath2) : null;
122761
123261
  const hasLocalConfig = defaults2 != null;
122762
123262
  const defaultOut = hasLocalConfig ? "horus.config.example.js" : "horus.config.js";
122763
- const outPath = (0, import_node_path24.resolve)(cwd, opts.out ?? defaultOut);
123263
+ const outPath = (0, import_node_path25.resolve)(cwd, opts.out ?? defaultOut);
122764
123264
  const name2 = opts.name ?? defaults2?.name ?? "my-project";
122765
123265
  const repoPath = opts.repo ?? defaults2?.repoPath ?? `/path/to/${name2}`;
122766
- if ((0, import_node_fs21.existsSync)(outPath) && !opts.force) {
123266
+ if ((0, import_node_fs22.existsSync)(outPath) && !opts.force) {
122767
123267
  log(`${import_picocolors49.default.red("\u2717")} ${outPath} already exists`);
122768
123268
  log(import_picocolors49.default.dim(" pass --force to overwrite"));
122769
123269
  return 1;
122770
123270
  }
122771
123271
  try {
122772
- (0, import_node_fs21.mkdirSync)((0, import_node_path24.dirname)(outPath), { recursive: true });
122773
- (0, import_node_fs21.writeFileSync)(outPath, configTemplate(name2, repoPath), "utf8");
123272
+ (0, import_node_fs22.mkdirSync)((0, import_node_path25.dirname)(outPath), { recursive: true });
123273
+ (0, import_node_fs22.writeFileSync)(outPath, configTemplate(name2, repoPath), "utf8");
122774
123274
  } catch (err2) {
122775
123275
  log(`${import_picocolors49.default.red("\u2717")} Could not write ${outPath}: ${err2.message}`);
122776
123276
  return 1;
@@ -123080,9 +123580,9 @@ async function runReadiness(opts) {
123080
123580
 
123081
123581
  // ../../packages/cli/src/commands/skill.ts
123082
123582
  init_cjs_shims();
123083
- var import_node_fs22 = require("fs");
123084
- var import_node_path25 = require("path");
123085
- var import_node_os8 = require("os");
123583
+ var import_node_fs23 = require("fs");
123584
+ var import_node_path26 = require("path");
123585
+ var import_node_os9 = require("os");
123086
123586
  var import_promises3 = require("readline/promises");
123087
123587
  var import_picocolors51 = __toESM(require_picocolors(), 1);
123088
123588
  var SUPPORTED_TARGETS = ["claude", "codex", "gemini", "cursor", "generic"];
@@ -123090,23 +123590,23 @@ function isValidTarget(target) {
123090
123590
  return SUPPORTED_TARGETS.includes(target);
123091
123591
  }
123092
123592
  function getLegacyClaudeSkillPath(opts) {
123093
- const base2 = opts.global ? (0, import_node_os8.homedir)() : opts.cwd ?? process.cwd();
123094
- return (0, import_node_path25.join)(base2, ".claude", "skills", "horus.md");
123593
+ const base2 = opts.global ? (0, import_node_os9.homedir)() : opts.cwd ?? process.cwd();
123594
+ return (0, import_node_path26.join)(base2, ".claude", "skills", "horus.md");
123095
123595
  }
123096
123596
  function getSkillInstallPath(target, opts) {
123097
- const base2 = opts.global ? (0, import_node_os8.homedir)() : opts.cwd ?? process.cwd();
123597
+ const base2 = opts.global ? (0, import_node_os9.homedir)() : opts.cwd ?? process.cwd();
123098
123598
  switch (target) {
123099
123599
  case "claude":
123100
- return (0, import_node_path25.join)(base2, ".claude", "skills", "horus", "SKILL.md");
123600
+ return (0, import_node_path26.join)(base2, ".claude", "skills", "horus", "SKILL.md");
123101
123601
  case "gemini":
123102
- return opts.global ? (0, import_node_path25.join)(base2, ".gemini", "GEMINI.md") : (0, import_node_path25.join)(base2, "GEMINI.md");
123602
+ return opts.global ? (0, import_node_path26.join)(base2, ".gemini", "GEMINI.md") : (0, import_node_path26.join)(base2, "GEMINI.md");
123103
123603
  case "cursor":
123104
- return opts.global ? (0, import_node_path25.join)(base2, ".cursorrules") : (0, import_node_path25.join)(base2, ".cursor", "rules", "horus.mdc");
123604
+ return opts.global ? (0, import_node_path26.join)(base2, ".cursorrules") : (0, import_node_path26.join)(base2, ".cursor", "rules", "horus.mdc");
123105
123605
  case "codex":
123106
- return opts.global ? (0, import_node_path25.join)(base2, ".codex", "AGENTS.md") : (0, import_node_path25.join)(base2, "AGENTS.md");
123606
+ return opts.global ? (0, import_node_path26.join)(base2, ".codex", "AGENTS.md") : (0, import_node_path26.join)(base2, "AGENTS.md");
123107
123607
  case "generic":
123108
123608
  default:
123109
- return (0, import_node_path25.join)(base2, ".horus", "skills", `horus-${target}.md`);
123609
+ return (0, import_node_path26.join)(base2, ".horus", "skills", `horus-${target}.md`);
123110
123610
  }
123111
123611
  }
123112
123612
  var BASE_SKILL = `# Horus Grounded Investigation Skill
@@ -123431,7 +123931,7 @@ async function runSkillInstall(target, opts) {
123431
123931
  const content = generateSkillContent(target);
123432
123932
  if (target === "claude") {
123433
123933
  const legacyPath = getLegacyClaudeSkillPath({ global: opts.global, cwd: opts.cwd });
123434
- if ((0, import_node_fs22.existsSync)(legacyPath)) {
123934
+ if ((0, import_node_fs23.existsSync)(legacyPath)) {
123435
123935
  if (!opts.force) {
123436
123936
  const confirmed = await (opts.confirm ?? confirm)(
123437
123937
  `Found old flat skill file at ${legacyPath}. Replace it with the new directory layout?`
@@ -123443,7 +123943,7 @@ async function runSkillInstall(target, opts) {
123443
123943
  }
123444
123944
  }
123445
123945
  try {
123446
- (0, import_node_fs22.rmSync)(legacyPath);
123946
+ (0, import_node_fs23.rmSync)(legacyPath);
123447
123947
  log(`${import_picocolors51.default.yellow("!")} Replaced old flat skill file: ${legacyPath}`);
123448
123948
  } catch (err2) {
123449
123949
  log(`${import_picocolors51.default.red("\u2717")} Could not remove legacy skill file ${legacyPath}: ${err2.message}`);
@@ -123451,14 +123951,14 @@ async function runSkillInstall(target, opts) {
123451
123951
  }
123452
123952
  }
123453
123953
  }
123454
- if ((0, import_node_fs22.existsSync)(outPath) && !opts.force) {
123954
+ if ((0, import_node_fs23.existsSync)(outPath) && !opts.force) {
123455
123955
  log(`${import_picocolors51.default.red("\u2717")} ${outPath} already exists`);
123456
123956
  log(import_picocolors51.default.dim(" pass --force to overwrite"));
123457
123957
  return 1;
123458
123958
  }
123459
123959
  try {
123460
- (0, import_node_fs22.mkdirSync)((0, import_node_path25.dirname)(outPath), { recursive: true });
123461
- (0, import_node_fs22.writeFileSync)(outPath, content, "utf8");
123960
+ (0, import_node_fs23.mkdirSync)((0, import_node_path26.dirname)(outPath), { recursive: true });
123961
+ (0, import_node_fs23.writeFileSync)(outPath, content, "utf8");
123462
123962
  } catch (err2) {
123463
123963
  log(`${import_picocolors51.default.red("\u2717")} Could not write ${outPath}: ${err2.message}`);
123464
123964
  return 1;
@@ -124199,251 +124699,8 @@ function installCommandTelemetry(program2) {
124199
124699
  // ../../packages/cli/src/commands/eval.ts
124200
124700
  init_cjs_shims();
124201
124701
  var import_promises5 = require("fs/promises");
124202
- var import_node_path26 = require("path");
124702
+ var import_node_path27 = require("path");
124203
124703
  var import_picocolors56 = __toESM(require_picocolors(), 1);
124204
-
124205
- // ../../packages/eval/src/index.ts
124206
- init_cjs_shims();
124207
-
124208
- // ../../packages/eval/src/types.ts
124209
- init_cjs_shims();
124210
- var CORPUS_VERSION = "v1";
124211
-
124212
- // ../../packages/eval/src/corpus.ts
124213
- init_cjs_shims();
124214
- function toUsableReport(blob) {
124215
- if (blob === null || typeof blob !== "object") return null;
124216
- const r = blob;
124217
- if (!Array.isArray(r.suspectedCauses)) return null;
124218
- const seeds = Array.isArray(r.seeds) ? r.seeds.map((s5) => s5 && typeof s5 === "object" && "name" in s5 ? String(s5.name) : "").filter(Boolean) : [];
124219
- return {
124220
- summary: typeof r.summary === "string" ? r.summary : "",
124221
- confidence: typeof r.confidence === "number" && Number.isFinite(r.confidence) ? r.confidence : 0,
124222
- seeds,
124223
- causes: r.suspectedCauses
124224
- };
124225
- }
124226
- function pickHeadlineCause(causes) {
124227
- let best = null;
124228
- for (const c4 of causes) {
124229
- if (best === null || c4.finalScore > best.finalScore || c4.finalScore === best.finalScore && c4.id < best.id) {
124230
- best = c4;
124231
- }
124232
- }
124233
- return best;
124234
- }
124235
- function flattenFactors(cause) {
124236
- const sums = /* @__PURE__ */ new Map();
124237
- for (const e of cause.explanations ?? []) {
124238
- sums.set(e.factor, (sums.get(e.factor) ?? 0) + e.delta);
124239
- }
124240
- return [...sums.entries()].map(([factor, delta]) => ({ factor, delta })).sort((a2, b5) => a2.factor < b5.factor ? -1 : a2.factor > b5.factor ? 1 : 0);
124241
- }
124242
- function projectHeadline(cause) {
124243
- return {
124244
- id: cause.id,
124245
- category: cause.category,
124246
- finalScore: cause.finalScore,
124247
- confidence: cause.confidence,
124248
- band: cause.band,
124249
- sourceEvidenceIds: [...cause.sourceEvidenceIds ?? []].sort(),
124250
- factors: flattenFactors(cause)
124251
- };
124252
- }
124253
- function buildCorpus(labels, resolveReport) {
124254
- const current = dedupeToCurrentVerdict(labels);
124255
- const evaluated = current.length;
124256
- const rows = [];
124257
- const quarantined = [];
124258
- const unjoinable = [];
124259
- for (const label of current) {
124260
- if (label.investigationId === null) {
124261
- unjoinable.push({ investigationId: null, reason: "null-investigation" });
124262
- continue;
124263
- }
124264
- if (!isOutcomeResolved(label.resolved) || !isOutcomeSource(label.source)) {
124265
- quarantined.push({
124266
- investigationId: label.investigationId,
124267
- resolved: String(label.resolved),
124268
- source: String(label.source),
124269
- reason: !isOutcomeResolved(label.resolved) ? `invalid resolved "${String(label.resolved)}"` : `invalid source "${String(label.source)}"`
124270
- });
124271
- continue;
124272
- }
124273
- const blob = resolveReport(label.investigationId);
124274
- if (blob === null || blob === void 0) {
124275
- unjoinable.push({ investigationId: label.investigationId, reason: "missing-report" });
124276
- continue;
124277
- }
124278
- const report = toUsableReport(blob);
124279
- if (report === null) {
124280
- unjoinable.push({ investigationId: label.investigationId, reason: "legacy-report" });
124281
- continue;
124282
- }
124283
- const isConfirm = label.source === "confirm";
124284
- const headline = pickHeadlineCause(report.causes);
124285
- rows.push({
124286
- investigationId: label.investigationId,
124287
- project: label.project,
124288
- at: label.at instanceof Date ? label.at.toISOString() : String(label.at),
124289
- target: label.resolved,
124290
- source: label.source,
124291
- weak: isConfirm,
124292
- confirmedCause: isConfirm ? null : label.confirmedCause ?? null,
124293
- summary: report.summary,
124294
- reportConfidence: report.confidence,
124295
- seeds: report.seeds,
124296
- causeCount: report.causes.length,
124297
- headlineCause: headline ? projectHeadline(headline) : null
124298
- });
124299
- }
124300
- const classBalance = { yes: 0, partly: 0, no: 0 };
124301
- const bySource = { feedback: 0, confirm: 0 };
124302
- for (const r of rows) {
124303
- classBalance[r.target] += 1;
124304
- bySource[r.source] += 1;
124305
- }
124306
- return { rows, evaluated, quarantined, unjoinable, classBalance, bySource };
124307
- }
124308
- function corpusFilename(version3 = CORPUS_VERSION) {
124309
- return `corpus-${version3}.jsonl`;
124310
- }
124311
- function sortRows(rows) {
124312
- return [...rows].sort((a2, b5) => {
124313
- if (a2.investigationId !== b5.investigationId) return a2.investigationId < b5.investigationId ? -1 : 1;
124314
- if (a2.at !== b5.at) return a2.at < b5.at ? -1 : 1;
124315
- return 0;
124316
- });
124317
- }
124318
- function fnv1a(s5) {
124319
- let h3 = 2166136261;
124320
- for (let i3 = 0; i3 < s5.length; i3++) {
124321
- h3 ^= s5.charCodeAt(i3);
124322
- h3 = Math.imul(h3, 16777619);
124323
- }
124324
- return h3 >>> 0;
124325
- }
124326
- function holdoutSplit(rows, holdoutPct = 20) {
124327
- const train = [];
124328
- const holdout = [];
124329
- for (const r of rows) {
124330
- if (fnv1a(r.investigationId) % 100 < holdoutPct) holdout.push(r.investigationId);
124331
- else train.push(r.investigationId);
124332
- }
124333
- train.sort();
124334
- holdout.sort();
124335
- return { holdoutPct, train, holdout };
124336
- }
124337
- function serializeCorpus(build, version3 = CORPUS_VERSION) {
124338
- const sorted = sortRows(build.rows);
124339
- const jsonl = sorted.map((r) => JSON.stringify(r)).join("\n") + (sorted.length > 0 ? "\n" : "");
124340
- const manifest = {
124341
- version: version3,
124342
- generatedAt: null,
124343
- rows: sorted.length,
124344
- evaluated: build.evaluated,
124345
- quarantined: build.quarantined.length,
124346
- unjoinable: build.unjoinable.length,
124347
- classBalance: build.classBalance,
124348
- bySource: build.bySource,
124349
- holdout: holdoutSplit(sorted)
124350
- };
124351
- return { filename: corpusFilename(version3), jsonl, manifest };
124352
- }
124353
-
124354
- // ../../packages/eval/src/baseline.ts
124355
- init_cjs_shims();
124356
- function computeBaseline(labels) {
124357
- const summary = summarizeOutcomeLabels(labels);
124358
- const byProjectMap = /* @__PURE__ */ new Map();
124359
- for (const l2 of labels) {
124360
- const key = l2.project ?? null;
124361
- const arr = byProjectMap.get(key);
124362
- if (arr) arr.push(l2);
124363
- else byProjectMap.set(key, [l2]);
124364
- }
124365
- const byProject = [...byProjectMap.entries()].map(([project, slice]) => {
124366
- const s5 = summarizeOutcomeLabels(slice);
124367
- return {
124368
- project,
124369
- n: s5.evaluated,
124370
- strictHitRate: s5.accuracy,
124371
- weightedHitRate: s5.weightedScore
124372
- };
124373
- }).sort((a2, b5) => {
124374
- if (a2.project === b5.project) return 0;
124375
- if (a2.project === null) return 1;
124376
- if (b5.project === null) return -1;
124377
- return a2.project < b5.project ? -1 : 1;
124378
- });
124379
- return {
124380
- n: summary.evaluated,
124381
- strictHitRate: summary.accuracy,
124382
- weightedHitRate: summary.weightedScore,
124383
- classBalance: summary.counts,
124384
- bySource: summary.bySource,
124385
- byProject
124386
- };
124387
- }
124388
- function mean(xs) {
124389
- if (xs.length === 0) return 0;
124390
- return xs.reduce((a2, b5) => a2 + b5, 0) / xs.length;
124391
- }
124392
- function variance(xs, m5) {
124393
- if (xs.length === 0) return 0;
124394
- return xs.reduce((a2, b5) => a2 + (b5 - m5) * (b5 - m5), 0) / xs.length;
124395
- }
124396
- function featureSeparation(rows) {
124397
- const usable = rows.filter(
124398
- (r) => !r.weak && r.headlineCause !== null && (r.target === "yes" || r.target === "no")
124399
- );
124400
- const factorSet = /* @__PURE__ */ new Set();
124401
- for (const r of usable) {
124402
- for (const f5 of r.headlineCause.factors) factorSet.add(f5.factor);
124403
- }
124404
- const factors = [...factorSet].sort();
124405
- const out2 = factors.map((factor) => {
124406
- const yes = [];
124407
- const no = [];
124408
- for (const r of usable) {
124409
- const f5 = r.headlineCause.factors.find((x4) => x4.factor === factor);
124410
- const delta = f5 ? f5.delta : 0;
124411
- if (r.target === "yes") yes.push(delta);
124412
- else no.push(delta);
124413
- }
124414
- const meanYes = mean(yes);
124415
- const meanNo = mean(no);
124416
- const varYes = variance(yes, meanYes);
124417
- const varNo = variance(no, meanNo);
124418
- const separation = meanYes - meanNo;
124419
- const all = yes.concat(no);
124420
- const overallMean = mean(all);
124421
- const pooledVar = variance(all, overallMean);
124422
- const pooledStd = Math.sqrt(pooledVar);
124423
- const effectSize = pooledStd === 0 ? 0 : separation / pooledStd;
124424
- const cv = overallMean === 0 ? 0 : pooledStd / Math.abs(overallMean);
124425
- return {
124426
- factor,
124427
- nYes: yes.length,
124428
- nNo: no.length,
124429
- meanYes,
124430
- meanNo,
124431
- varYes,
124432
- varNo,
124433
- separation,
124434
- effectSize,
124435
- cv
124436
- };
124437
- });
124438
- out2.sort((a2, b5) => {
124439
- const d3 = Math.abs(b5.effectSize) - Math.abs(a2.effectSize);
124440
- if (d3 !== 0) return d3;
124441
- return a2.factor < b5.factor ? -1 : a2.factor > b5.factor ? 1 : 0;
124442
- });
124443
- return { evaluated: usable.length, factors: out2 };
124444
- }
124445
-
124446
- // ../../packages/cli/src/commands/eval.ts
124447
124704
  function resolveProject2(config2, repo) {
124448
124705
  let project;
124449
124706
  try {
@@ -124492,7 +124749,7 @@ async function runEvalBuild(opts) {
124492
124749
  const project = resolveProject2(config2, opts.repo);
124493
124750
  if (!project) return 1;
124494
124751
  const version3 = opts.version ?? CORPUS_VERSION;
124495
- const outDir = opts.out ? (0, import_node_path26.isAbsolute)(opts.out) ? opts.out : (0, import_node_path26.resolve)(process.cwd(), opts.out) : (0, import_node_path26.join)(process.cwd(), ".horus", "eval");
124752
+ const outDir = opts.out ? (0, import_node_path27.isAbsolute)(opts.out) ? opts.out : (0, import_node_path27.resolve)(process.cwd(), opts.out) : (0, import_node_path27.join)(process.cwd(), ".horus", "eval");
124496
124753
  const { db, sql: sql2 } = await openDb(config2.database.url);
124497
124754
  try {
124498
124755
  const labels = await listOutcomeLabels(db, {
@@ -124508,8 +124765,8 @@ async function runEvalBuild(opts) {
124508
124765
  const build = buildCorpus(labels, resolveReport);
124509
124766
  const artifact = serializeCorpus(build, version3);
124510
124767
  await (0, import_promises5.mkdir)(outDir, { recursive: true });
124511
- const corpusPath = (0, import_node_path26.join)(outDir, artifact.filename);
124512
- const manifestPath = (0, import_node_path26.join)(outDir, `corpus-${version3}.manifest.json`);
124768
+ const corpusPath = (0, import_node_path27.join)(outDir, artifact.filename);
124769
+ const manifestPath = (0, import_node_path27.join)(outDir, `corpus-${version3}.manifest.json`);
124513
124770
  const manifest = { ...artifact.manifest, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), project };
124514
124771
  await (0, import_promises5.writeFile)(corpusPath, artifact.jsonl, "utf8");
124515
124772
  await (0, import_promises5.writeFile)(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf8");
@@ -124564,7 +124821,7 @@ async function runEvalBaseline(opts) {
124564
124821
  if (opts.json) {
124565
124822
  console.log(JSON.stringify({ project, baseline, separation }, null, 2));
124566
124823
  } else {
124567
- const pct = (n4) => `${(n4 * 100).toFixed(0)}%`;
124824
+ const pct2 = (n4) => `${(n4 * 100).toFixed(0)}%`;
124568
124825
  console.log(import_picocolors56.default.bold(`Eval baseline \u2014 ${project}`));
124569
124826
  if (baseline.n === 0) {
124570
124827
  console.log(
@@ -124574,7 +124831,7 @@ async function runEvalBaseline(opts) {
124574
124831
  );
124575
124832
  } else {
124576
124833
  console.log(
124577
- ` Strict hit-rate: ${import_picocolors56.default.green(pct(baseline.strictHitRate))} ` + import_picocolors56.default.dim(`(weighted ${pct(baseline.weightedHitRate)}) over ${baseline.n} investigation(s)`)
124834
+ ` Strict hit-rate: ${import_picocolors56.default.green(pct2(baseline.strictHitRate))} ` + import_picocolors56.default.dim(`(weighted ${pct2(baseline.weightedHitRate)}) over ${baseline.n} investigation(s)`)
124578
124835
  );
124579
124836
  console.log(
124580
124837
  ` Class balance: ${import_picocolors56.default.green(`${baseline.classBalance.yes} yes`)}, ${import_picocolors56.default.yellow(`${baseline.classBalance.partly} partly`)}, ${import_picocolors56.default.red(`${baseline.classBalance.no} no`)} \xB7 ` + import_picocolors56.default.dim(`source: ${baseline.bySource.feedback} feedback, ${baseline.bySource.confirm} confirm`)
@@ -124607,12 +124864,120 @@ async function runEvalBaseline(opts) {
124607
124864
  }
124608
124865
  }
124609
124866
 
124610
- // ../../packages/cli/src/lib/report-crash.ts
124867
+ // ../../packages/cli/src/commands/train.ts
124611
124868
  init_cjs_shims();
124869
+ var import_promises6 = require("fs/promises");
124870
+ var import_node_path28 = require("path");
124871
+ var import_node_os10 = require("os");
124612
124872
  var import_picocolors57 = __toESM(require_picocolors(), 1);
124873
+ var RERANKER_MODEL_PATH = (0, import_node_path28.join)((0, import_node_os10.homedir)(), ".horus", "reranker.json");
124874
+ function candidatesFromReport(blob) {
124875
+ if (blob === null || typeof blob !== "object") return null;
124876
+ const causes = blob.suspectedCauses;
124877
+ if (!Array.isArray(causes)) return null;
124878
+ return causes;
124879
+ }
124880
+ function pct(n4) {
124881
+ return `${(n4 * 100).toFixed(1)}%`;
124882
+ }
124883
+ async function runTrain(opts) {
124884
+ let source;
124885
+ if (opts.source !== void 0) {
124886
+ if (!isOutcomeSource(opts.source)) {
124887
+ console.error(import_picocolors57.default.red(`Unknown --source "${opts.source}" (one of: feedback, confirm).`));
124888
+ return 1;
124889
+ }
124890
+ source = opts.source;
124891
+ }
124892
+ let since;
124893
+ if (opts.days !== void 0) {
124894
+ if (!Number.isFinite(opts.days) || opts.days <= 0) {
124895
+ console.error(import_picocolors57.default.red("--days must be a positive number."));
124896
+ return 1;
124897
+ }
124898
+ since = new Date(Date.now() - opts.days * 24 * 60 * 60 * 1e3);
124899
+ }
124900
+ let investigations2;
124901
+ try {
124902
+ const config2 = await loadConfig(opts.config);
124903
+ const { db, sql: sql2 } = await openDb(config2.database.url);
124904
+ try {
124905
+ const labels = await listOutcomeLabels(db, {
124906
+ ...source ? { source } : {},
124907
+ ...since ? { since } : {},
124908
+ ...opts.limit ? { limit: opts.limit } : {}
124909
+ });
124910
+ const current = dedupeToCurrentVerdict(labels);
124911
+ investigations2 = [];
124912
+ for (const label of current) {
124913
+ if (label.investigationId === null) continue;
124914
+ if (!isOutcomeResolved(label.resolved) || !isOutcomeSource(label.source)) continue;
124915
+ const inv = await getInvestigation(db, label.investigationId);
124916
+ const candidates = candidatesFromReport(inv?.report ?? null);
124917
+ if (candidates === null) continue;
124918
+ const isConfirm = label.source === "confirm";
124919
+ investigations2.push({
124920
+ investigationId: label.investigationId,
124921
+ target: label.resolved,
124922
+ // confirm's confirmedCause is Horus's own summary (circular) — drop it, like the corpus.
124923
+ confirmedCause: isConfirm ? null : label.confirmedCause ?? null,
124924
+ candidates
124925
+ });
124926
+ }
124927
+ } finally {
124928
+ await sql2.end();
124929
+ }
124930
+ } catch (err2) {
124931
+ console.error(import_picocolors57.default.red(`Could not read the local corpus: ${err2.message}`));
124932
+ return 1;
124933
+ }
124934
+ const split = holdoutSplit(investigations2);
124935
+ const result = trainReranker(investigations2, new Set(split.holdout));
124936
+ if (!result.ok) {
124937
+ if (result.reason === "insufficient-corpus") {
124938
+ console.log(
124939
+ import_picocolors57.default.yellow(
124940
+ `Not enough labeled investigations to train yet (have ${result.labeledInvestigations} with a known-correct cause; need \u2265${MIN_TRAIN_INVESTIGATIONS}).`
124941
+ )
124942
+ );
124943
+ console.log(import_picocolors57.default.dim(" Keep labeling outcomes with `horus feedback` \u2014 the reranker trains once the corpus is large enough."));
124944
+ } else {
124945
+ console.log(import_picocolors57.default.yellow(`Cannot train a meaningful model yet: ${result.detail}.`));
124946
+ }
124947
+ return 0;
124948
+ }
124949
+ const { baselineHitRate, rerankedHitRate, delta, n: n4 } = result.holdout;
124950
+ console.log(import_picocolors57.default.bold("\nReranker training (local, per-tenant)\n"));
124951
+ console.log(` Holdout investigations: ${n4}`);
124952
+ console.log(` Baseline top-1 hit-rate: ${pct(baselineHitRate)}`);
124953
+ console.log(` Reranked top-1 hit-rate: ${pct(rerankedHitRate)}`);
124954
+ console.log(` Delta: ${delta >= 0 ? "+" : ""}${pct(delta)}`);
124955
+ console.log("");
124956
+ if (delta <= 0) {
124957
+ console.log(import_picocolors57.default.yellow(" The reranker did NOT beat the deterministic baseline \u2014 not saving a model."));
124958
+ console.log(import_picocolors57.default.dim(" Horus stays on its hand-tuned ranking. Re-run as more labels accumulate."));
124959
+ return 0;
124960
+ }
124961
+ try {
124962
+ await (0, import_promises6.mkdir)((0, import_node_path28.join)((0, import_node_os10.homedir)(), ".horus"), { recursive: true });
124963
+ const { ok: _ok, ...model } = result;
124964
+ void _ok;
124965
+ await (0, import_promises6.writeFile)(RERANKER_MODEL_PATH, JSON.stringify(model, null, 2) + "\n", "utf8");
124966
+ } catch (err2) {
124967
+ console.error(import_picocolors57.default.red(`Trained, but could not write the model: ${err2.message}`));
124968
+ return 1;
124969
+ }
124970
+ console.log(import_picocolors57.default.green(` \u2713 Reranker beats baseline by +${pct(delta)} \u2014 saved to ${RERANKER_MODEL_PATH}`));
124971
+ console.log(import_picocolors57.default.dim(" It ships OFF by default. Enable it with `HORUS_RERANK=1 horus investigate \u2026`."));
124972
+ return 0;
124973
+ }
124974
+
124975
+ // ../../packages/cli/src/lib/report-crash.ts
124976
+ init_cjs_shims();
124977
+ var import_picocolors58 = __toESM(require_picocolors(), 1);
124613
124978
  function reportCrash(err2, log = console.error) {
124614
124979
  log(err2);
124615
- log(import_picocolors57.default.dim("\n Looks like a Horus bug or a missing capability? Report it: horus report"));
124980
+ log(import_picocolors58.default.dim("\n Looks like a Horus bug or a missing capability? Report it: horus report"));
124616
124981
  }
124617
124982
 
124618
124983
  // ../../packages/cli/src/index.ts
@@ -124824,6 +125189,16 @@ Examples:
124824
125189
  process.exitCode = await runEvalBaseline(opts);
124825
125190
  }
124826
125191
  );
125192
+ program2.command("train").description("Fit the local, per-tenant reranker over your outcome-label corpus (off until it beats baseline)").option("-c, --config <path>", "path to horus.config.ts").option("--source <source>", "filter to one signal source: feedback | confirm").option("--days <n>", "only train on labels from the last N days", (v4) => Number(v4)).option("--limit <n>", "max labels to scan", (v4) => Number(v4)).addHelpText(
125193
+ "after",
125194
+ `
125195
+ The reranker is LOCAL and per-tenant: your corpus never leaves this machine. It only REORDERS
125196
+ candidate causes among those that already clear Horus's confidence gates \u2014 it never changes a
125197
+ score, a confidence, or a verdict. It ships OFF; once trained, enable with:
125198
+ HORUS_RERANK=1 horus investigate "<hint>"`
125199
+ ).action(async (opts) => {
125200
+ process.exitCode = await runTrain(opts);
125201
+ });
124827
125202
  program2.command("mcp").description("Run the Horus MCP server (stdio) \u2014 exposes the local project-knowledge index to agents").action(async () => {
124828
125203
  process.exitCode = await runMcpServer();
124829
125204
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@merittdev/horus",
3
- "version": "0.13.2",
3
+ "version": "0.14.0",
4
4
  "description": "Local-first, source-aware production-incident investigation engine",
5
5
  "type": "module",
6
6
  "bin": {