@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.
- package/dist/index.cjs +700 -325
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -65880,7 +65880,7 @@ init_cjs_shims();
|
|
|
65880
65880
|
|
|
65881
65881
|
// ../../packages/core/src/version.ts
|
|
65882
65882
|
init_cjs_shims();
|
|
65883
|
-
var HORUS_VERSION = true ? "0.
|
|
65883
|
+
var HORUS_VERSION = true ? "0.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
|
|
74638
|
-
if (
|
|
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}: ${
|
|
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:
|
|
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
|
|
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 =
|
|
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((
|
|
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((
|
|
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
|
|
81461
|
-
const tableName2 = getTableLikeName(
|
|
81462
|
-
if (typeof tableName2 === "string" && !is(
|
|
81463
|
-
const fromFields = this.getTableLikeFields(
|
|
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
|
|
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
|
|
99384
|
-
lines.push(`Likely maintainer: ${own.likelyMaintainer ?? "(unknown)"} (${
|
|
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
|
|
99575
|
+
const pct2 = (o5.confidence * 100).toFixed(0);
|
|
99565
99576
|
lines.push(
|
|
99566
|
-
"Likely maintainer : " + (o5.likelyMaintainer ?? "(unknown)") + " (" +
|
|
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
|
|
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)"} (${
|
|
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
|
|
102788
|
-
const loopVarEnums = loopVarEnumsFor(
|
|
102789
|
-
for (const m5 of
|
|
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:
|
|
102802
|
+
if (queue) producers.push({ queue, symbol: pc59.name, file: pc59.filePath });
|
|
102792
102803
|
}
|
|
102793
|
-
for (const m5 of
|
|
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] ??
|
|
102800
|
-
file:
|
|
102810
|
+
symbol: (m5[1] ?? pc59.name) || baseName(pc59.filePath),
|
|
102811
|
+
file: pc59.filePath
|
|
102801
102812
|
});
|
|
102802
102813
|
}
|
|
102803
102814
|
}
|
|
102804
|
-
for (const m5 of
|
|
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(
|
|
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
|
|
117984
|
-
var
|
|
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,
|
|
118096
|
-
if ((0,
|
|
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,
|
|
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
|
|
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(
|
|
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
|
|
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,
|
|
120505
|
-
const name2 = opts.name ?? (0,
|
|
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
|
|
122391
|
+
var import_node_fs21 = require("fs");
|
|
121892
122392
|
var import_node_util6 = require("util");
|
|
121893
|
-
var
|
|
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)(
|
|
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,
|
|
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,
|
|
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
|
|
122714
|
-
var
|
|
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,
|
|
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,
|
|
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,
|
|
122773
|
-
(0,
|
|
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
|
|
123084
|
-
var
|
|
123085
|
-
var
|
|
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,
|
|
123094
|
-
return (0,
|
|
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,
|
|
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,
|
|
123600
|
+
return (0, import_node_path26.join)(base2, ".claude", "skills", "horus", "SKILL.md");
|
|
123101
123601
|
case "gemini":
|
|
123102
|
-
return opts.global ? (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
123461
|
-
(0,
|
|
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
|
|
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,
|
|
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,
|
|
124512
|
-
const manifestPath = (0,
|
|
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
|
|
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(
|
|
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/
|
|
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(
|
|
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
|
});
|