@merittdev/horus 0.13.2 → 0.14.1
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 +716 -336
- 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.1" : "dev";
|
|
65884
65884
|
var PINNED_SOURCE_VERSION = "2.0.2";
|
|
65885
65885
|
|
|
65886
65886
|
// ../../packages/core/src/evidence.ts
|
|
@@ -70856,6 +70856,9 @@ init_cjs_shims();
|
|
|
70856
70856
|
|
|
70857
70857
|
// ../../packages/connectors/src/source/client.ts
|
|
70858
70858
|
init_cjs_shims();
|
|
70859
|
+
function encodeNodePath(nodeId) {
|
|
70860
|
+
return encodeURI(nodeId).replace(/#/g, "%23").replace(/\?/g, "%3F");
|
|
70861
|
+
}
|
|
70859
70862
|
var SourceHttpError = class extends Error {
|
|
70860
70863
|
status;
|
|
70861
70864
|
body;
|
|
@@ -70982,7 +70985,7 @@ var SourceHttpClient = class {
|
|
|
70982
70985
|
impact(nodeId, depth = 3) {
|
|
70983
70986
|
return this.request(
|
|
70984
70987
|
"GET",
|
|
70985
|
-
`/api/impact/${
|
|
70988
|
+
`/api/impact/${encodeNodePath(nodeId)}?depth=${depth}`
|
|
70986
70989
|
);
|
|
70987
70990
|
}
|
|
70988
70991
|
diff(base2, compare) {
|
|
@@ -71032,7 +71035,7 @@ var SourceHttpClient = class {
|
|
|
71032
71035
|
}
|
|
71033
71036
|
/** Process flows a symbol participates in, with each flow's named ordered steps. */
|
|
71034
71037
|
flows(nodeId) {
|
|
71035
|
-
return this.request("GET", `/api/flows/${
|
|
71038
|
+
return this.request("GET", `/api/flows/${encodeNodePath(nodeId)}`);
|
|
71036
71039
|
}
|
|
71037
71040
|
/** Method symbols of `className` defined in `file`, ordered by start line. */
|
|
71038
71041
|
async classMethods(file, className) {
|
|
@@ -71044,7 +71047,7 @@ var SourceHttpClient = class {
|
|
|
71044
71047
|
}
|
|
71045
71048
|
/** Extended node detail — node + content + callers/callees/typeRefs + imports + coupling + communities. */
|
|
71046
71049
|
node(nodeId) {
|
|
71047
|
-
return this.request("GET", `/api/node/${
|
|
71050
|
+
return this.request("GET", `/api/node/${encodeNodePath(nodeId)}`);
|
|
71048
71051
|
}
|
|
71049
71052
|
/** Community clusters with their member nodes (source-graph extraction). */
|
|
71050
71053
|
async communities() {
|
|
@@ -74634,21 +74637,21 @@ function analyzeQueueSignals(q2) {
|
|
|
74634
74637
|
if (q2.failedBreakdown && q2.failedBreakdown.length > 0 && q2.failed > 0) {
|
|
74635
74638
|
const top = q2.failedBreakdown[0];
|
|
74636
74639
|
const sampleTotal = q2.failedBreakdown.reduce((sum, b5) => sum + b5.count, 0);
|
|
74637
|
-
const
|
|
74638
|
-
if (
|
|
74640
|
+
const pct2 = Math.round(top.count / Math.max(sampleTotal, 1) * 100);
|
|
74641
|
+
if (pct2 >= BREAKDOWN_PCT_THRESHOLD) {
|
|
74639
74642
|
const stale = isStaleFailure(top.lastFailedAgeMs);
|
|
74640
74643
|
const ageStr = top.lastFailedAgeMs !== void 0 ? `, last failed ${fmtMs(top.lastFailedAgeMs)} ago${stale ? " [STALE]" : ""}` : "";
|
|
74641
74644
|
signals.push({
|
|
74642
74645
|
queueName: q2.queueName,
|
|
74643
74646
|
kind: "failed-breakdown",
|
|
74644
|
-
title: `${q2.queueName}: ${
|
|
74647
|
+
title: `${q2.queueName}: ${pct2}% of recently sampled failures (${top.count}/${sampleTotal} sampled) are "${top.reason}"${ageStr}`,
|
|
74645
74648
|
// Demote a stale-only breakdown so a days-old error isn't read as live (HOR-217).
|
|
74646
74649
|
relevance: stale ? 0.5 : 0.82,
|
|
74647
74650
|
payload: {
|
|
74648
74651
|
queueName: q2.queueName,
|
|
74649
74652
|
topReason: top.reason,
|
|
74650
74653
|
topCount: top.count,
|
|
74651
|
-
topPct:
|
|
74654
|
+
topPct: pct2,
|
|
74652
74655
|
totalFailed: q2.failed,
|
|
74653
74656
|
topLastFailedAgeMs: top.lastFailedAgeMs ?? null,
|
|
74654
74657
|
topStale: stale,
|
|
@@ -76748,7 +76751,7 @@ function sql(strings, ...params) {
|
|
|
76748
76751
|
return new SQL([new StringChunk(str3)]);
|
|
76749
76752
|
}
|
|
76750
76753
|
sql2.raw = raw;
|
|
76751
|
-
function
|
|
76754
|
+
function join20(chunks, separator) {
|
|
76752
76755
|
const result = [];
|
|
76753
76756
|
for (const [i3, chunk] of chunks.entries()) {
|
|
76754
76757
|
if (i3 > 0 && separator !== void 0) {
|
|
@@ -76758,7 +76761,7 @@ function sql(strings, ...params) {
|
|
|
76758
76761
|
}
|
|
76759
76762
|
return new SQL(result);
|
|
76760
76763
|
}
|
|
76761
|
-
sql2.join =
|
|
76764
|
+
sql2.join = join20;
|
|
76762
76765
|
function identifier(value) {
|
|
76763
76766
|
return new Name(value);
|
|
76764
76767
|
}
|
|
@@ -80349,7 +80352,7 @@ var PgSelectQueryBuilderBase = class extends TypedQueryBuilder {
|
|
|
80349
80352
|
return (table, on2) => {
|
|
80350
80353
|
const baseTableName = this.tableName;
|
|
80351
80354
|
const tableName = getTableLikeName(table);
|
|
80352
|
-
if (typeof tableName === "string" && this.config.joins?.some((
|
|
80355
|
+
if (typeof tableName === "string" && this.config.joins?.some((join20) => join20.alias === tableName)) {
|
|
80353
80356
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
80354
80357
|
}
|
|
80355
80358
|
if (!this.isPartialSelect) {
|
|
@@ -81361,7 +81364,7 @@ var PgUpdateBase = class extends QueryPromise {
|
|
|
81361
81364
|
createJoin(joinType) {
|
|
81362
81365
|
return (table, on2) => {
|
|
81363
81366
|
const tableName = getTableLikeName(table);
|
|
81364
|
-
if (typeof tableName === "string" && this.config.joins.some((
|
|
81367
|
+
if (typeof tableName === "string" && this.config.joins.some((join20) => join20.alias === tableName)) {
|
|
81365
81368
|
throw new Error(`Alias "${tableName}" is already used in this query`);
|
|
81366
81369
|
}
|
|
81367
81370
|
if (typeof on2 === "function") {
|
|
@@ -81457,10 +81460,10 @@ var PgUpdateBase = class extends QueryPromise {
|
|
|
81457
81460
|
const fromFields = this.getTableLikeFields(this.config.from);
|
|
81458
81461
|
fields[tableName] = fromFields;
|
|
81459
81462
|
}
|
|
81460
|
-
for (const
|
|
81461
|
-
const tableName2 = getTableLikeName(
|
|
81462
|
-
if (typeof tableName2 === "string" && !is(
|
|
81463
|
-
const fromFields = this.getTableLikeFields(
|
|
81463
|
+
for (const join20 of this.config.joins) {
|
|
81464
|
+
const tableName2 = getTableLikeName(join20.table);
|
|
81465
|
+
if (typeof tableName2 === "string" && !is(join20.table, SQL)) {
|
|
81466
|
+
const fromFields = this.getTableLikeFields(join20.table);
|
|
81464
81467
|
fields[tableName2] = fromFields;
|
|
81465
81468
|
}
|
|
81466
81469
|
}
|
|
@@ -93552,16 +93555,27 @@ function rankCauses(inputs, ctx, limit = 3) {
|
|
|
93552
93555
|
});
|
|
93553
93556
|
return scored.slice(0, limit);
|
|
93554
93557
|
}
|
|
93555
|
-
function selectHeadlineCause(rankedCauses, isLinkedToSeed) {
|
|
93558
|
+
function selectHeadlineCause(rankedCauses, isLinkedToSeed, rerank) {
|
|
93556
93559
|
let topCause;
|
|
93557
|
-
let topLinkedCause;
|
|
93558
93560
|
for (const c4 of rankedCauses) {
|
|
93559
93561
|
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
93562
|
}
|
|
93564
|
-
const
|
|
93563
|
+
const pickEligible = (set) => {
|
|
93564
|
+
if (set.length === 0) return void 0;
|
|
93565
|
+
if (rerank) {
|
|
93566
|
+
const top = rerank(set).find((c4) => set.includes(c4));
|
|
93567
|
+
if (top) return top;
|
|
93568
|
+
}
|
|
93569
|
+
let best = set[0];
|
|
93570
|
+
for (const c4 of set) if (c4.finalScore > best.finalScore) best = c4;
|
|
93571
|
+
return best;
|
|
93572
|
+
};
|
|
93573
|
+
const linkedEligible = rankedCauses.filter(
|
|
93574
|
+
(c4) => c4.finalScore >= 0.2 && isLinkedToSeed(c4.sourceEvidenceIds)
|
|
93575
|
+
);
|
|
93576
|
+
const allEligible = rankedCauses.filter((c4) => c4.finalScore >= 0.2);
|
|
93577
|
+
const topLinkedCause = pickEligible(linkedEligible);
|
|
93578
|
+
const headlineCause = topLinkedCause ?? pickEligible(allEligible);
|
|
93565
93579
|
const headlineLinked = headlineCause !== void 0 && isLinkedToSeed(headlineCause.sourceEvidenceIds);
|
|
93566
93580
|
return { topCause, topLinkedCause, headlineCause, headlineLinked };
|
|
93567
93581
|
}
|
|
@@ -96300,7 +96314,7 @@ function formatRegressionCitation(recentChanges, changes, seedFile) {
|
|
|
96300
96314
|
return { commitClause, symbolClause };
|
|
96301
96315
|
}
|
|
96302
96316
|
async function investigate(input, deps) {
|
|
96303
|
-
const { code, db } = deps;
|
|
96317
|
+
const { code, db, rerank } = deps;
|
|
96304
96318
|
const hint = input.hint.trim();
|
|
96305
96319
|
const intent = classifyIntent(hint, input.since);
|
|
96306
96320
|
const namedSymbols = parseNamedSymbols(hint);
|
|
@@ -96490,8 +96504,8 @@ async function investigate(input, deps) {
|
|
|
96490
96504
|
label = top.name === "constructor" ? `${(top.id.split(":").pop() ?? "").replace(/\.constructor$/, "") || top.name} (constructor)` : top.name;
|
|
96491
96505
|
[ctx, impact, flows] = await Promise.all([
|
|
96492
96506
|
code.context(top.id),
|
|
96493
|
-
code.impact(top.id, 2),
|
|
96494
|
-
code.flowsFor(top.id)
|
|
96507
|
+
code.impact(top.id, 2).catch(() => null),
|
|
96508
|
+
code.flowsFor(top.id).catch(() => [])
|
|
96495
96509
|
]);
|
|
96496
96510
|
let edges = [];
|
|
96497
96511
|
try {
|
|
@@ -96549,12 +96563,14 @@ async function investigate(input, deps) {
|
|
|
96549
96563
|
);
|
|
96550
96564
|
flowEvIds.push(ev.id);
|
|
96551
96565
|
}
|
|
96552
|
-
|
|
96553
|
-
|
|
96554
|
-
|
|
96555
|
-
|
|
96556
|
-
|
|
96557
|
-
|
|
96566
|
+
if (impact) {
|
|
96567
|
+
impactEv = mkEv(
|
|
96568
|
+
"impact",
|
|
96569
|
+
`Impact of ${top.name}: ${impact.affected} affected symbol(s)`,
|
|
96570
|
+
{ affected: impact.affected },
|
|
96571
|
+
{ symbolId: top.id, file: top.filePath }
|
|
96572
|
+
);
|
|
96573
|
+
}
|
|
96558
96574
|
for (const edge of queueHits) {
|
|
96559
96575
|
const producer = edge.producerSymbol ?? "unknown-producer";
|
|
96560
96576
|
const worker = edge.workerSymbol ?? "unknown-worker";
|
|
@@ -97806,7 +97822,7 @@ async function investigate(input, deps) {
|
|
|
97806
97822
|
...looksPerformance(hint) ? latencyMetricEvIds : []
|
|
97807
97823
|
]);
|
|
97808
97824
|
const isLinkedToSeed = (ids2) => ids2.some((id) => seedLinkedEvIds.has(id));
|
|
97809
|
-
const { headlineCause, headlineLinked } = selectHeadlineCause(rankedCauses, isLinkedToSeed);
|
|
97825
|
+
const { headlineCause, headlineLinked } = selectHeadlineCause(rankedCauses, isLinkedToSeed, rerank);
|
|
97810
97826
|
if (headlineCause && !headlineLinked && !sourceImpactHint) confidence = Math.min(confidence, 0.6);
|
|
97811
97827
|
const banner2 = degradedNoSource ? "Runtime-only (source intelligence unavailable). " : "";
|
|
97812
97828
|
const scope = top ? seedIsLowConfidence ? "could not be confidently localized from source alone" : `resolved to ${label} (${area})` : `over runtime evidence (${area})`;
|
|
@@ -99380,8 +99396,8 @@ function renderMemoryView(v4) {
|
|
|
99380
99396
|
}
|
|
99381
99397
|
const own = v4.ownedAreas.ownership;
|
|
99382
99398
|
if (own != null && own.file != null) {
|
|
99383
|
-
const
|
|
99384
|
-
lines.push(`Likely maintainer: ${own.likelyMaintainer ?? "(unknown)"} (${
|
|
99399
|
+
const pct2 = (own.confidence * 100).toFixed(0);
|
|
99400
|
+
lines.push(`Likely maintainer: ${own.likelyMaintainer ?? "(unknown)"} (${pct2}% confidence)`);
|
|
99385
99401
|
if (own.mostActiveRecent != null) {
|
|
99386
99402
|
lines.push(`Most active recently: ${own.mostActiveRecent}`);
|
|
99387
99403
|
}
|
|
@@ -99561,9 +99577,9 @@ function renderOwnership(o5) {
|
|
|
99561
99577
|
}
|
|
99562
99578
|
lines.push("File : " + o5.file);
|
|
99563
99579
|
lines.push("");
|
|
99564
|
-
const
|
|
99580
|
+
const pct2 = (o5.confidence * 100).toFixed(0);
|
|
99565
99581
|
lines.push(
|
|
99566
|
-
"Likely maintainer : " + (o5.likelyMaintainer ?? "(unknown)") + " (" +
|
|
99582
|
+
"Likely maintainer : " + (o5.likelyMaintainer ?? "(unknown)") + " (" + pct2 + "% confidence)"
|
|
99567
99583
|
);
|
|
99568
99584
|
lines.push("Most active recently: " + (o5.mostActiveRecent ?? "(unknown)"));
|
|
99569
99585
|
lines.push("");
|
|
@@ -100446,10 +100462,10 @@ function renderOnboarding(g4) {
|
|
|
100446
100462
|
lines.push("## Who owns this area");
|
|
100447
100463
|
lines.push("");
|
|
100448
100464
|
if (g4.ownership != null && g4.ownership.file != null) {
|
|
100449
|
-
const
|
|
100465
|
+
const pct2 = (g4.ownership.confidence * 100).toFixed(0);
|
|
100450
100466
|
lines.push(`File: ${g4.ownership.file}`);
|
|
100451
100467
|
lines.push(
|
|
100452
|
-
`Likely maintainer: ${g4.ownership.likelyMaintainer ?? "(unknown)"} (${
|
|
100468
|
+
`Likely maintainer: ${g4.ownership.likelyMaintainer ?? "(unknown)"} (${pct2}% confidence)`
|
|
100453
100469
|
);
|
|
100454
100470
|
} else {
|
|
100455
100471
|
lines.push(
|
|
@@ -102784,27 +102800,27 @@ function extractQueueGraph(input) {
|
|
|
102784
102800
|
}
|
|
102785
102801
|
}
|
|
102786
102802
|
const producers = [];
|
|
102787
|
-
for (const
|
|
102788
|
-
const loopVarEnums = loopVarEnumsFor(
|
|
102789
|
-
for (const m5 of
|
|
102803
|
+
for (const pc59 of input.producerClasses) {
|
|
102804
|
+
const loopVarEnums = loopVarEnumsFor(pc59.content);
|
|
102805
|
+
for (const m5 of pc59.content.matchAll(INJECT_QUEUE_RE)) {
|
|
102790
102806
|
const queue = m5[1] ?? "";
|
|
102791
|
-
if (queue) producers.push({ queue, symbol:
|
|
102807
|
+
if (queue) producers.push({ queue, symbol: pc59.name, file: pc59.filePath });
|
|
102792
102808
|
}
|
|
102793
|
-
for (const m5 of
|
|
102809
|
+
for (const m5 of pc59.content.matchAll(NEW_BULL_RE)) {
|
|
102794
102810
|
if (m5[2] !== "Queue") continue;
|
|
102795
102811
|
const queues2 = resolveArg(m5[3] ?? "", constMap, enums, loopVarEnums);
|
|
102796
102812
|
for (const queue of queues2) {
|
|
102797
102813
|
producers.push({
|
|
102798
102814
|
queue,
|
|
102799
|
-
symbol: (m5[1] ??
|
|
102800
|
-
file:
|
|
102815
|
+
symbol: (m5[1] ?? pc59.name) || baseName(pc59.filePath),
|
|
102816
|
+
file: pc59.filePath
|
|
102801
102817
|
});
|
|
102802
102818
|
}
|
|
102803
102819
|
}
|
|
102804
|
-
for (const m5 of
|
|
102820
|
+
for (const m5 of pc59.content.matchAll(QUEUE_NAME_CONST_RE)) {
|
|
102805
102821
|
const ident = m5[1] ?? "";
|
|
102806
102822
|
const queue = m5[2] ?? "";
|
|
102807
|
-
if (queue) producers.push({ queue, symbol: ident || baseName(
|
|
102823
|
+
if (queue) producers.push({ queue, symbol: ident || baseName(pc59.filePath), file: pc59.filePath });
|
|
102808
102824
|
}
|
|
102809
102825
|
}
|
|
102810
102826
|
const workers = [];
|
|
@@ -116553,6 +116569,479 @@ function renderFreshness(f5) {
|
|
|
116553
116569
|
// ../../packages/cli/src/lib/investigation-runner.ts
|
|
116554
116570
|
init_cjs_shims();
|
|
116555
116571
|
var import_picocolors16 = __toESM(require_picocolors(), 1);
|
|
116572
|
+
var import_node_fs19 = require("fs");
|
|
116573
|
+
var import_node_path21 = require("path");
|
|
116574
|
+
var import_node_os8 = require("os");
|
|
116575
|
+
|
|
116576
|
+
// ../../packages/eval/src/index.ts
|
|
116577
|
+
init_cjs_shims();
|
|
116578
|
+
|
|
116579
|
+
// ../../packages/eval/src/types.ts
|
|
116580
|
+
init_cjs_shims();
|
|
116581
|
+
var CORPUS_VERSION = "v1";
|
|
116582
|
+
|
|
116583
|
+
// ../../packages/eval/src/corpus.ts
|
|
116584
|
+
init_cjs_shims();
|
|
116585
|
+
function toUsableReport(blob) {
|
|
116586
|
+
if (blob === null || typeof blob !== "object") return null;
|
|
116587
|
+
const r = blob;
|
|
116588
|
+
if (!Array.isArray(r.suspectedCauses)) return null;
|
|
116589
|
+
const seeds = Array.isArray(r.seeds) ? r.seeds.map((s5) => s5 && typeof s5 === "object" && "name" in s5 ? String(s5.name) : "").filter(Boolean) : [];
|
|
116590
|
+
return {
|
|
116591
|
+
summary: typeof r.summary === "string" ? r.summary : "",
|
|
116592
|
+
confidence: typeof r.confidence === "number" && Number.isFinite(r.confidence) ? r.confidence : 0,
|
|
116593
|
+
seeds,
|
|
116594
|
+
causes: r.suspectedCauses
|
|
116595
|
+
};
|
|
116596
|
+
}
|
|
116597
|
+
function pickHeadlineCause(causes) {
|
|
116598
|
+
let best = null;
|
|
116599
|
+
for (const c4 of causes) {
|
|
116600
|
+
if (best === null || c4.finalScore > best.finalScore || c4.finalScore === best.finalScore && c4.id < best.id) {
|
|
116601
|
+
best = c4;
|
|
116602
|
+
}
|
|
116603
|
+
}
|
|
116604
|
+
return best;
|
|
116605
|
+
}
|
|
116606
|
+
function flattenFactors(cause) {
|
|
116607
|
+
const sums = /* @__PURE__ */ new Map();
|
|
116608
|
+
for (const e of cause.explanations ?? []) {
|
|
116609
|
+
sums.set(e.factor, (sums.get(e.factor) ?? 0) + e.delta);
|
|
116610
|
+
}
|
|
116611
|
+
return [...sums.entries()].map(([factor, delta]) => ({ factor, delta })).sort((a2, b5) => a2.factor < b5.factor ? -1 : a2.factor > b5.factor ? 1 : 0);
|
|
116612
|
+
}
|
|
116613
|
+
function projectHeadline(cause) {
|
|
116614
|
+
return {
|
|
116615
|
+
id: cause.id,
|
|
116616
|
+
category: cause.category,
|
|
116617
|
+
finalScore: cause.finalScore,
|
|
116618
|
+
confidence: cause.confidence,
|
|
116619
|
+
band: cause.band,
|
|
116620
|
+
sourceEvidenceIds: [...cause.sourceEvidenceIds ?? []].sort(),
|
|
116621
|
+
factors: flattenFactors(cause)
|
|
116622
|
+
};
|
|
116623
|
+
}
|
|
116624
|
+
function buildCorpus(labels, resolveReport) {
|
|
116625
|
+
const current = dedupeToCurrentVerdict(labels);
|
|
116626
|
+
const evaluated = current.length;
|
|
116627
|
+
const rows = [];
|
|
116628
|
+
const quarantined = [];
|
|
116629
|
+
const unjoinable = [];
|
|
116630
|
+
for (const label of current) {
|
|
116631
|
+
if (label.investigationId === null) {
|
|
116632
|
+
unjoinable.push({ investigationId: null, reason: "null-investigation" });
|
|
116633
|
+
continue;
|
|
116634
|
+
}
|
|
116635
|
+
if (!isOutcomeResolved(label.resolved) || !isOutcomeSource(label.source)) {
|
|
116636
|
+
quarantined.push({
|
|
116637
|
+
investigationId: label.investigationId,
|
|
116638
|
+
resolved: String(label.resolved),
|
|
116639
|
+
source: String(label.source),
|
|
116640
|
+
reason: !isOutcomeResolved(label.resolved) ? `invalid resolved "${String(label.resolved)}"` : `invalid source "${String(label.source)}"`
|
|
116641
|
+
});
|
|
116642
|
+
continue;
|
|
116643
|
+
}
|
|
116644
|
+
const blob = resolveReport(label.investigationId);
|
|
116645
|
+
if (blob === null || blob === void 0) {
|
|
116646
|
+
unjoinable.push({ investigationId: label.investigationId, reason: "missing-report" });
|
|
116647
|
+
continue;
|
|
116648
|
+
}
|
|
116649
|
+
const report = toUsableReport(blob);
|
|
116650
|
+
if (report === null) {
|
|
116651
|
+
unjoinable.push({ investigationId: label.investigationId, reason: "legacy-report" });
|
|
116652
|
+
continue;
|
|
116653
|
+
}
|
|
116654
|
+
const isConfirm = label.source === "confirm";
|
|
116655
|
+
const headline = pickHeadlineCause(report.causes);
|
|
116656
|
+
rows.push({
|
|
116657
|
+
investigationId: label.investigationId,
|
|
116658
|
+
project: label.project,
|
|
116659
|
+
at: label.at instanceof Date ? label.at.toISOString() : String(label.at),
|
|
116660
|
+
target: label.resolved,
|
|
116661
|
+
source: label.source,
|
|
116662
|
+
weak: isConfirm,
|
|
116663
|
+
confirmedCause: isConfirm ? null : label.confirmedCause ?? null,
|
|
116664
|
+
summary: report.summary,
|
|
116665
|
+
reportConfidence: report.confidence,
|
|
116666
|
+
seeds: report.seeds,
|
|
116667
|
+
causeCount: report.causes.length,
|
|
116668
|
+
headlineCause: headline ? projectHeadline(headline) : null
|
|
116669
|
+
});
|
|
116670
|
+
}
|
|
116671
|
+
const classBalance = { yes: 0, partly: 0, no: 0 };
|
|
116672
|
+
const bySource = { feedback: 0, confirm: 0 };
|
|
116673
|
+
for (const r of rows) {
|
|
116674
|
+
classBalance[r.target] += 1;
|
|
116675
|
+
bySource[r.source] += 1;
|
|
116676
|
+
}
|
|
116677
|
+
return { rows, evaluated, quarantined, unjoinable, classBalance, bySource };
|
|
116678
|
+
}
|
|
116679
|
+
function corpusFilename(version3 = CORPUS_VERSION) {
|
|
116680
|
+
return `corpus-${version3}.jsonl`;
|
|
116681
|
+
}
|
|
116682
|
+
function sortRows(rows) {
|
|
116683
|
+
return [...rows].sort((a2, b5) => {
|
|
116684
|
+
if (a2.investigationId !== b5.investigationId) return a2.investigationId < b5.investigationId ? -1 : 1;
|
|
116685
|
+
if (a2.at !== b5.at) return a2.at < b5.at ? -1 : 1;
|
|
116686
|
+
return 0;
|
|
116687
|
+
});
|
|
116688
|
+
}
|
|
116689
|
+
function fnv1a(s5) {
|
|
116690
|
+
let h3 = 2166136261;
|
|
116691
|
+
for (let i3 = 0; i3 < s5.length; i3++) {
|
|
116692
|
+
h3 ^= s5.charCodeAt(i3);
|
|
116693
|
+
h3 = Math.imul(h3, 16777619);
|
|
116694
|
+
}
|
|
116695
|
+
return h3 >>> 0;
|
|
116696
|
+
}
|
|
116697
|
+
function holdoutSplit(rows, holdoutPct = 20) {
|
|
116698
|
+
const train = [];
|
|
116699
|
+
const holdout = [];
|
|
116700
|
+
for (const r of rows) {
|
|
116701
|
+
if (fnv1a(r.investigationId) % 100 < holdoutPct) holdout.push(r.investigationId);
|
|
116702
|
+
else train.push(r.investigationId);
|
|
116703
|
+
}
|
|
116704
|
+
train.sort();
|
|
116705
|
+
holdout.sort();
|
|
116706
|
+
return { holdoutPct, train, holdout };
|
|
116707
|
+
}
|
|
116708
|
+
function serializeCorpus(build, version3 = CORPUS_VERSION) {
|
|
116709
|
+
const sorted = sortRows(build.rows);
|
|
116710
|
+
const jsonl = sorted.map((r) => JSON.stringify(r)).join("\n") + (sorted.length > 0 ? "\n" : "");
|
|
116711
|
+
const manifest = {
|
|
116712
|
+
version: version3,
|
|
116713
|
+
generatedAt: null,
|
|
116714
|
+
rows: sorted.length,
|
|
116715
|
+
evaluated: build.evaluated,
|
|
116716
|
+
quarantined: build.quarantined.length,
|
|
116717
|
+
unjoinable: build.unjoinable.length,
|
|
116718
|
+
classBalance: build.classBalance,
|
|
116719
|
+
bySource: build.bySource,
|
|
116720
|
+
holdout: holdoutSplit(sorted)
|
|
116721
|
+
};
|
|
116722
|
+
return { filename: corpusFilename(version3), jsonl, manifest };
|
|
116723
|
+
}
|
|
116724
|
+
|
|
116725
|
+
// ../../packages/eval/src/baseline.ts
|
|
116726
|
+
init_cjs_shims();
|
|
116727
|
+
function computeBaseline(labels) {
|
|
116728
|
+
const summary = summarizeOutcomeLabels(labels);
|
|
116729
|
+
const byProjectMap = /* @__PURE__ */ new Map();
|
|
116730
|
+
for (const l2 of labels) {
|
|
116731
|
+
const key = l2.project ?? null;
|
|
116732
|
+
const arr = byProjectMap.get(key);
|
|
116733
|
+
if (arr) arr.push(l2);
|
|
116734
|
+
else byProjectMap.set(key, [l2]);
|
|
116735
|
+
}
|
|
116736
|
+
const byProject = [...byProjectMap.entries()].map(([project, slice]) => {
|
|
116737
|
+
const s5 = summarizeOutcomeLabels(slice);
|
|
116738
|
+
return {
|
|
116739
|
+
project,
|
|
116740
|
+
n: s5.evaluated,
|
|
116741
|
+
strictHitRate: s5.accuracy,
|
|
116742
|
+
weightedHitRate: s5.weightedScore
|
|
116743
|
+
};
|
|
116744
|
+
}).sort((a2, b5) => {
|
|
116745
|
+
if (a2.project === b5.project) return 0;
|
|
116746
|
+
if (a2.project === null) return 1;
|
|
116747
|
+
if (b5.project === null) return -1;
|
|
116748
|
+
return a2.project < b5.project ? -1 : 1;
|
|
116749
|
+
});
|
|
116750
|
+
return {
|
|
116751
|
+
n: summary.evaluated,
|
|
116752
|
+
strictHitRate: summary.accuracy,
|
|
116753
|
+
weightedHitRate: summary.weightedScore,
|
|
116754
|
+
classBalance: summary.counts,
|
|
116755
|
+
bySource: summary.bySource,
|
|
116756
|
+
byProject
|
|
116757
|
+
};
|
|
116758
|
+
}
|
|
116759
|
+
function mean(xs) {
|
|
116760
|
+
if (xs.length === 0) return 0;
|
|
116761
|
+
return xs.reduce((a2, b5) => a2 + b5, 0) / xs.length;
|
|
116762
|
+
}
|
|
116763
|
+
function variance(xs, m5) {
|
|
116764
|
+
if (xs.length === 0) return 0;
|
|
116765
|
+
return xs.reduce((a2, b5) => a2 + (b5 - m5) * (b5 - m5), 0) / xs.length;
|
|
116766
|
+
}
|
|
116767
|
+
function featureSeparation(rows) {
|
|
116768
|
+
const usable = rows.filter(
|
|
116769
|
+
(r) => !r.weak && r.headlineCause !== null && (r.target === "yes" || r.target === "no")
|
|
116770
|
+
);
|
|
116771
|
+
const factorSet = /* @__PURE__ */ new Set();
|
|
116772
|
+
for (const r of usable) {
|
|
116773
|
+
for (const f5 of r.headlineCause.factors) factorSet.add(f5.factor);
|
|
116774
|
+
}
|
|
116775
|
+
const factors = [...factorSet].sort();
|
|
116776
|
+
const out2 = factors.map((factor) => {
|
|
116777
|
+
const yes = [];
|
|
116778
|
+
const no = [];
|
|
116779
|
+
for (const r of usable) {
|
|
116780
|
+
const f5 = r.headlineCause.factors.find((x4) => x4.factor === factor);
|
|
116781
|
+
const delta = f5 ? f5.delta : 0;
|
|
116782
|
+
if (r.target === "yes") yes.push(delta);
|
|
116783
|
+
else no.push(delta);
|
|
116784
|
+
}
|
|
116785
|
+
const meanYes = mean(yes);
|
|
116786
|
+
const meanNo = mean(no);
|
|
116787
|
+
const varYes = variance(yes, meanYes);
|
|
116788
|
+
const varNo = variance(no, meanNo);
|
|
116789
|
+
const separation = meanYes - meanNo;
|
|
116790
|
+
const all = yes.concat(no);
|
|
116791
|
+
const overallMean = mean(all);
|
|
116792
|
+
const pooledVar = variance(all, overallMean);
|
|
116793
|
+
const pooledStd = Math.sqrt(pooledVar);
|
|
116794
|
+
const effectSize = pooledStd === 0 ? 0 : separation / pooledStd;
|
|
116795
|
+
const cv = overallMean === 0 ? 0 : pooledStd / Math.abs(overallMean);
|
|
116796
|
+
return {
|
|
116797
|
+
factor,
|
|
116798
|
+
nYes: yes.length,
|
|
116799
|
+
nNo: no.length,
|
|
116800
|
+
meanYes,
|
|
116801
|
+
meanNo,
|
|
116802
|
+
varYes,
|
|
116803
|
+
varNo,
|
|
116804
|
+
separation,
|
|
116805
|
+
effectSize,
|
|
116806
|
+
cv
|
|
116807
|
+
};
|
|
116808
|
+
});
|
|
116809
|
+
out2.sort((a2, b5) => {
|
|
116810
|
+
const d3 = Math.abs(b5.effectSize) - Math.abs(a2.effectSize);
|
|
116811
|
+
if (d3 !== 0) return d3;
|
|
116812
|
+
return a2.factor < b5.factor ? -1 : a2.factor > b5.factor ? 1 : 0;
|
|
116813
|
+
});
|
|
116814
|
+
return { evaluated: usable.length, factors: out2 };
|
|
116815
|
+
}
|
|
116816
|
+
|
|
116817
|
+
// ../../packages/eval/src/reranker.ts
|
|
116818
|
+
init_cjs_shims();
|
|
116819
|
+
var RERANKER_VERSION = "v1";
|
|
116820
|
+
var MIN_TRAIN_INVESTIGATIONS = 25;
|
|
116821
|
+
var SCALAR_KEYS = ["finalScore", "confidence", "baseScore", "evidenceCount", "affectedCount"];
|
|
116822
|
+
function tokenize4(s5) {
|
|
116823
|
+
return new Set(
|
|
116824
|
+
s5.toLowerCase().split(/[^a-z0-9]+/).filter((t2) => t2.length >= 3)
|
|
116825
|
+
);
|
|
116826
|
+
}
|
|
116827
|
+
function matchesConfirmedCause(confirmedCause, cause) {
|
|
116828
|
+
const a2 = tokenize4(confirmedCause);
|
|
116829
|
+
const b5 = tokenize4(`${cause.title} ${cause.category}`);
|
|
116830
|
+
if (a2.size === 0 || b5.size === 0) return 0;
|
|
116831
|
+
let overlap = 0;
|
|
116832
|
+
for (const t2 of a2) if (b5.has(t2)) overlap += 1;
|
|
116833
|
+
return overlap / a2.size;
|
|
116834
|
+
}
|
|
116835
|
+
function collectFeatureKeys(investigations2) {
|
|
116836
|
+
const factors = /* @__PURE__ */ new Set();
|
|
116837
|
+
for (const inv of investigations2) {
|
|
116838
|
+
for (const c4 of inv.candidates) {
|
|
116839
|
+
for (const e of c4.explanations ?? []) factors.add(e.factor);
|
|
116840
|
+
}
|
|
116841
|
+
}
|
|
116842
|
+
return [...SCALAR_KEYS, ...[...factors].sort().map((f5) => `f:${f5}`)];
|
|
116843
|
+
}
|
|
116844
|
+
function extractRawFeatures(cause, featureKeys) {
|
|
116845
|
+
const factorDelta = /* @__PURE__ */ new Map();
|
|
116846
|
+
for (const e of cause.explanations ?? []) {
|
|
116847
|
+
factorDelta.set(e.factor, (factorDelta.get(e.factor) ?? 0) + e.delta);
|
|
116848
|
+
}
|
|
116849
|
+
return featureKeys.map((k3) => {
|
|
116850
|
+
switch (k3) {
|
|
116851
|
+
case "finalScore":
|
|
116852
|
+
return cause.finalScore;
|
|
116853
|
+
case "confidence":
|
|
116854
|
+
return cause.confidence;
|
|
116855
|
+
case "baseScore":
|
|
116856
|
+
return cause.baseScore;
|
|
116857
|
+
case "evidenceCount":
|
|
116858
|
+
return cause.sourceEvidenceIds?.length ?? 0;
|
|
116859
|
+
case "affectedCount":
|
|
116860
|
+
return cause.affectedNodeIds?.length ?? 0;
|
|
116861
|
+
default:
|
|
116862
|
+
return factorDelta.get(k3.slice(2)) ?? 0;
|
|
116863
|
+
}
|
|
116864
|
+
});
|
|
116865
|
+
}
|
|
116866
|
+
function baselineTop(candidates) {
|
|
116867
|
+
let best = null;
|
|
116868
|
+
for (const c4 of candidates) {
|
|
116869
|
+
if (best === null || c4.finalScore > best.finalScore || c4.finalScore === best.finalScore && c4.id < best.id) {
|
|
116870
|
+
best = c4;
|
|
116871
|
+
}
|
|
116872
|
+
}
|
|
116873
|
+
return best;
|
|
116874
|
+
}
|
|
116875
|
+
function deriveLabels(inv) {
|
|
116876
|
+
const cands = inv.candidates;
|
|
116877
|
+
if (cands.length === 0) return { relevantId: null, examples: [] };
|
|
116878
|
+
if (inv.confirmedCause && inv.confirmedCause.trim() !== "") {
|
|
116879
|
+
let bestId = null;
|
|
116880
|
+
let bestScore = 0;
|
|
116881
|
+
for (const c4 of cands) {
|
|
116882
|
+
const m5 = matchesConfirmedCause(inv.confirmedCause, c4);
|
|
116883
|
+
if (m5 > bestScore) {
|
|
116884
|
+
bestScore = m5;
|
|
116885
|
+
bestId = c4.id;
|
|
116886
|
+
}
|
|
116887
|
+
}
|
|
116888
|
+
if (bestId !== null && bestScore >= 0.34) {
|
|
116889
|
+
return {
|
|
116890
|
+
relevantId: bestId,
|
|
116891
|
+
examples: cands.map((c4) => ({ cause: c4, label: c4.id === bestId ? 1 : 0 }))
|
|
116892
|
+
};
|
|
116893
|
+
}
|
|
116894
|
+
}
|
|
116895
|
+
const top = baselineTop(cands);
|
|
116896
|
+
if (top === null) return { relevantId: null, examples: [] };
|
|
116897
|
+
if (inv.target === "yes") {
|
|
116898
|
+
return { relevantId: top.id, examples: cands.map((c4) => ({ cause: c4, label: c4.id === top.id ? 1 : 0 })) };
|
|
116899
|
+
}
|
|
116900
|
+
if (inv.target === "no") {
|
|
116901
|
+
return { relevantId: null, examples: [{ cause: top, label: 0 }] };
|
|
116902
|
+
}
|
|
116903
|
+
return { relevantId: null, examples: [] };
|
|
116904
|
+
}
|
|
116905
|
+
function sigmoid(z4) {
|
|
116906
|
+
if (z4 >= 0) return 1 / (1 + Math.exp(-z4));
|
|
116907
|
+
const e = Math.exp(z4);
|
|
116908
|
+
return e / (1 + e);
|
|
116909
|
+
}
|
|
116910
|
+
function fitLogistic(X2, y5, opts = {}) {
|
|
116911
|
+
const epochs = opts.epochs ?? 400;
|
|
116912
|
+
const lr = opts.lr ?? 0.1;
|
|
116913
|
+
const l2 = opts.l2 ?? 1e-3;
|
|
116914
|
+
const dim = X2[0]?.length ?? 0;
|
|
116915
|
+
const weights = new Array(dim).fill(0);
|
|
116916
|
+
let bias = 0;
|
|
116917
|
+
const n4 = X2.length;
|
|
116918
|
+
if (n4 === 0) return { weights, bias };
|
|
116919
|
+
for (let epoch = 0; epoch < epochs; epoch++) {
|
|
116920
|
+
const gradW = new Array(dim).fill(0);
|
|
116921
|
+
let gradB = 0;
|
|
116922
|
+
for (let i3 = 0; i3 < n4; i3++) {
|
|
116923
|
+
const xi = X2[i3];
|
|
116924
|
+
let z4 = bias;
|
|
116925
|
+
for (let j3 = 0; j3 < dim; j3++) z4 += weights[j3] * xi[j3];
|
|
116926
|
+
const err2 = sigmoid(z4) - y5[i3];
|
|
116927
|
+
for (let j3 = 0; j3 < dim; j3++) gradW[j3] = gradW[j3] + err2 * xi[j3];
|
|
116928
|
+
gradB += err2;
|
|
116929
|
+
}
|
|
116930
|
+
for (let j3 = 0; j3 < dim; j3++) weights[j3] = weights[j3] - lr * (gradW[j3] / n4 + l2 * weights[j3]);
|
|
116931
|
+
bias -= lr * (gradB / n4);
|
|
116932
|
+
}
|
|
116933
|
+
return { weights, bias };
|
|
116934
|
+
}
|
|
116935
|
+
function scoreVector(weights, bias, x4) {
|
|
116936
|
+
let z4 = bias;
|
|
116937
|
+
for (let j3 = 0; j3 < weights.length; j3++) z4 += weights[j3] * x4[j3];
|
|
116938
|
+
return z4;
|
|
116939
|
+
}
|
|
116940
|
+
function standardizeParams(X2) {
|
|
116941
|
+
const dim = X2[0]?.length ?? 0;
|
|
116942
|
+
const mean2 = new Array(dim).fill(0);
|
|
116943
|
+
const std = new Array(dim).fill(0);
|
|
116944
|
+
if (X2.length === 0) return { mean: mean2, std };
|
|
116945
|
+
for (const row of X2) for (let j3 = 0; j3 < dim; j3++) mean2[j3] = mean2[j3] + row[j3];
|
|
116946
|
+
for (let j3 = 0; j3 < dim; j3++) mean2[j3] = mean2[j3] / X2.length;
|
|
116947
|
+
for (const row of X2) for (let j3 = 0; j3 < dim; j3++) std[j3] = std[j3] + (row[j3] - mean2[j3]) ** 2;
|
|
116948
|
+
for (let j3 = 0; j3 < dim; j3++) std[j3] = Math.sqrt(std[j3] / X2.length) || 1;
|
|
116949
|
+
return { mean: mean2, std };
|
|
116950
|
+
}
|
|
116951
|
+
function applyStandardize(x4, mean2, std) {
|
|
116952
|
+
return x4.map((v4, j3) => (v4 - mean2[j3]) / std[j3]);
|
|
116953
|
+
}
|
|
116954
|
+
function top1HitRate(set, score) {
|
|
116955
|
+
if (set.length === 0) return 0;
|
|
116956
|
+
let hits = 0;
|
|
116957
|
+
for (const { inv, relevantId } of set) {
|
|
116958
|
+
let bestId = null;
|
|
116959
|
+
let bestScore = -Infinity;
|
|
116960
|
+
for (const c4 of inv.candidates) {
|
|
116961
|
+
const s5 = score(c4);
|
|
116962
|
+
if (s5 > bestScore || s5 === bestScore && bestId !== null && c4.id < bestId) {
|
|
116963
|
+
bestScore = s5;
|
|
116964
|
+
bestId = c4.id;
|
|
116965
|
+
}
|
|
116966
|
+
}
|
|
116967
|
+
if (bestId === relevantId) hits += 1;
|
|
116968
|
+
}
|
|
116969
|
+
return hits / set.length;
|
|
116970
|
+
}
|
|
116971
|
+
function trainReranker(investigations2, holdoutIds, opts = {}) {
|
|
116972
|
+
const labeled = investigations2.map((inv) => ({ inv, derived: deriveLabels(inv) })).filter((x4) => x4.derived.examples.length > 0);
|
|
116973
|
+
const scorable = labeled.filter((x4) => x4.derived.relevantId !== null && x4.inv.candidates.length >= 2);
|
|
116974
|
+
if (scorable.length < MIN_TRAIN_INVESTIGATIONS) {
|
|
116975
|
+
return {
|
|
116976
|
+
ok: false,
|
|
116977
|
+
reason: "insufficient-corpus",
|
|
116978
|
+
detail: `need \u2265${MIN_TRAIN_INVESTIGATIONS} labeled investigations with a known-correct candidate; have ${scorable.length}`,
|
|
116979
|
+
labeledInvestigations: scorable.length
|
|
116980
|
+
};
|
|
116981
|
+
}
|
|
116982
|
+
const featureKeys = collectFeatureKeys(investigations2);
|
|
116983
|
+
const trainSet = scorable.filter((x4) => !holdoutIds.has(x4.inv.investigationId));
|
|
116984
|
+
const holdoutSet = scorable.filter((x4) => holdoutIds.has(x4.inv.investigationId));
|
|
116985
|
+
if (holdoutSet.length === 0 || trainSet.length === 0) {
|
|
116986
|
+
return {
|
|
116987
|
+
ok: false,
|
|
116988
|
+
reason: "degenerate-holdout",
|
|
116989
|
+
detail: `train/holdout split left ${trainSet.length} train / ${holdoutSet.length} holdout`,
|
|
116990
|
+
labeledInvestigations: scorable.length
|
|
116991
|
+
};
|
|
116992
|
+
}
|
|
116993
|
+
const rawX = [];
|
|
116994
|
+
const y5 = [];
|
|
116995
|
+
for (const { inv } of trainSet) {
|
|
116996
|
+
for (const ex of deriveLabels(inv).examples) {
|
|
116997
|
+
rawX.push(extractRawFeatures(ex.cause, featureKeys));
|
|
116998
|
+
y5.push(ex.label);
|
|
116999
|
+
}
|
|
117000
|
+
}
|
|
117001
|
+
if (rawX.length === 0) {
|
|
117002
|
+
return { ok: false, reason: "no-derivable-labels", detail: "no training examples after split", labeledInvestigations: scorable.length };
|
|
117003
|
+
}
|
|
117004
|
+
const { mean: mean2, std } = standardizeParams(rawX);
|
|
117005
|
+
const X2 = rawX.map((row) => applyStandardize(row, mean2, std));
|
|
117006
|
+
const { weights, bias } = fitLogistic(X2, y5, opts);
|
|
117007
|
+
const modelScore = (c4) => scoreVector(weights, bias, applyStandardize(extractRawFeatures(c4, featureKeys), mean2, std));
|
|
117008
|
+
const holdoutScored = holdoutSet.map((x4) => ({ inv: x4.inv, relevantId: x4.derived.relevantId }));
|
|
117009
|
+
const baselineHitRate = top1HitRate(holdoutScored, (c4) => c4.finalScore);
|
|
117010
|
+
const rerankedHitRate = top1HitRate(holdoutScored, modelScore);
|
|
117011
|
+
return {
|
|
117012
|
+
ok: true,
|
|
117013
|
+
version: RERANKER_VERSION,
|
|
117014
|
+
featureKeys,
|
|
117015
|
+
mean: mean2,
|
|
117016
|
+
std,
|
|
117017
|
+
weights,
|
|
117018
|
+
bias,
|
|
117019
|
+
holdout: {
|
|
117020
|
+
n: holdoutScored.length,
|
|
117021
|
+
baselineHitRate,
|
|
117022
|
+
rerankedHitRate,
|
|
117023
|
+
delta: rerankedHitRate - baselineHitRate
|
|
117024
|
+
},
|
|
117025
|
+
trainExamples: rawX.length,
|
|
117026
|
+
trainInvestigations: trainSet.length
|
|
117027
|
+
};
|
|
117028
|
+
}
|
|
117029
|
+
function applyReranker(model, candidates) {
|
|
117030
|
+
const scored = candidates.map((c4, i3) => ({
|
|
117031
|
+
c: c4,
|
|
117032
|
+
i: i3,
|
|
117033
|
+
s: scoreVector(model.weights, model.bias, applyStandardize(extractRawFeatures(c4, model.featureKeys), model.mean, model.std))
|
|
117034
|
+
}));
|
|
117035
|
+
scored.sort((a2, b5) => b5.s - a2.s || a2.i - b5.i);
|
|
117036
|
+
return scored.map((x4) => x4.c);
|
|
117037
|
+
}
|
|
117038
|
+
function isRerankerModel(blob) {
|
|
117039
|
+
if (blob === null || typeof blob !== "object") return false;
|
|
117040
|
+
const m5 = blob;
|
|
117041
|
+
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;
|
|
117042
|
+
}
|
|
117043
|
+
|
|
117044
|
+
// ../../packages/cli/src/lib/investigation-runner.ts
|
|
116556
117045
|
async function buildInvestigationContext(renv, opts) {
|
|
116557
117046
|
const log = opts.log ?? ((l2) => console.error(l2));
|
|
116558
117047
|
let code = codeForEnv(renv);
|
|
@@ -116611,8 +117100,21 @@ function databaseUrlOrThrow(url) {
|
|
|
116611
117100
|
if (!url) throw new Error("No database URL configured for the investigation store.");
|
|
116612
117101
|
return url;
|
|
116613
117102
|
}
|
|
117103
|
+
function loadReranker() {
|
|
117104
|
+
const flag = process.env.HORUS_RERANK;
|
|
117105
|
+
if (!flag || flag === "0" || flag.toLowerCase() === "false") return void 0;
|
|
117106
|
+
try {
|
|
117107
|
+
const blob = JSON.parse((0, import_node_fs19.readFileSync)((0, import_node_path21.join)((0, import_node_os8.homedir)(), ".horus", "reranker.json"), "utf8"));
|
|
117108
|
+
if (!isRerankerModel(blob)) return void 0;
|
|
117109
|
+
if ((blob.holdout?.delta ?? 0) <= 0) return void 0;
|
|
117110
|
+
return (causes) => applyReranker(blob, causes);
|
|
117111
|
+
} catch {
|
|
117112
|
+
return void 0;
|
|
117113
|
+
}
|
|
117114
|
+
}
|
|
116614
117115
|
async function runOneInvestigation(input, ctx, opts = {}) {
|
|
116615
117116
|
const { renv } = ctx;
|
|
117117
|
+
const rerank = loadReranker();
|
|
116616
117118
|
const investigation = investigate(
|
|
116617
117119
|
{
|
|
116618
117120
|
hint: input.hint,
|
|
@@ -116646,7 +117148,10 @@ async function runOneInvestigation(input, ctx, opts = {}) {
|
|
|
116646
117148
|
axiom: !!renv.connectors.axiom,
|
|
116647
117149
|
redis: !!renv.connectors.redis?.url,
|
|
116648
117150
|
queue: !!ctx.queue
|
|
116649
|
-
}
|
|
117151
|
+
},
|
|
117152
|
+
// HOR-404: inject the learned reranker only when enabled + proven (loadReranker gates both);
|
|
117153
|
+
// CONTEXT-ONLY — it reorders eligible causes, never touches confidence/verdict/ceilings.
|
|
117154
|
+
...rerank ? { rerank } : {}
|
|
116650
117155
|
}
|
|
116651
117156
|
);
|
|
116652
117157
|
const timeoutMs = opts.timeoutMs ?? (Number(process.env.HORUS_INVESTIGATE_TIMEOUT_S) || 120) * 1e3;
|
|
@@ -117980,8 +118485,8 @@ async function runOwner(query, opts) {
|
|
|
117980
118485
|
|
|
117981
118486
|
// ../../packages/cli/src/commands/postmortem.ts
|
|
117982
118487
|
init_cjs_shims();
|
|
117983
|
-
var
|
|
117984
|
-
var
|
|
118488
|
+
var import_node_fs20 = require("fs");
|
|
118489
|
+
var import_node_path22 = require("path");
|
|
117985
118490
|
var import_picocolors30 = __toESM(require_picocolors(), 1);
|
|
117986
118491
|
async function runPostmortem(id, opts) {
|
|
117987
118492
|
const log = opts.write ?? ((line2) => console.log(line2));
|
|
@@ -118092,14 +118597,14 @@ _AI summary unavailable: ${validationErrors?.[0] ?? "provider error"}_
|
|
|
118092
118597
|
}
|
|
118093
118598
|
}
|
|
118094
118599
|
if (opts.output) {
|
|
118095
|
-
const outputPath = (0,
|
|
118096
|
-
if ((0,
|
|
118600
|
+
const outputPath = (0, import_node_path22.resolve)(opts.output);
|
|
118601
|
+
if ((0, import_node_fs20.existsSync)(outputPath) && !opts.force) {
|
|
118097
118602
|
log(import_picocolors30.default.red("\u2717") + " " + outputPath + " already exists");
|
|
118098
118603
|
log(import_picocolors30.default.dim(" pass --force to overwrite"));
|
|
118099
118604
|
return 1;
|
|
118100
118605
|
}
|
|
118101
118606
|
try {
|
|
118102
|
-
(0,
|
|
118607
|
+
(0, import_node_fs20.writeFileSync)(outputPath, content, "utf8");
|
|
118103
118608
|
} catch (err2) {
|
|
118104
118609
|
log(import_picocolors30.default.red("\u2717 Could not write " + outputPath + ": ") + err2.message);
|
|
118105
118610
|
return 1;
|
|
@@ -119683,7 +120188,7 @@ async function runMemoryAccuracy(opts) {
|
|
|
119683
120188
|
)
|
|
119684
120189
|
);
|
|
119685
120190
|
} else {
|
|
119686
|
-
const
|
|
120191
|
+
const pct2 = (n4) => `${(n4 * 100).toFixed(0)}%`;
|
|
119687
120192
|
console.log(import_picocolors34.default.bold(`Investigation accuracy \u2014 ${project}`) + import_picocolors34.default.dim(` \xB7 window: ${window2}`));
|
|
119688
120193
|
if (summary.evaluated === 0) {
|
|
119689
120194
|
console.log(
|
|
@@ -119693,7 +120198,7 @@ async function runMemoryAccuracy(opts) {
|
|
|
119693
120198
|
);
|
|
119694
120199
|
} else {
|
|
119695
120200
|
console.log(
|
|
119696
|
-
` Pointed at the cause: ${import_picocolors34.default.green(
|
|
120201
|
+
` 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
120202
|
);
|
|
119698
120203
|
console.log(
|
|
119699
120204
|
` 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 +121002,12 @@ async function runState(opts) {
|
|
|
120497
121002
|
// ../../packages/cli/src/commands/init.ts
|
|
120498
121003
|
init_cjs_shims();
|
|
120499
121004
|
var import_picocolors39 = __toESM(require_picocolors(), 1);
|
|
120500
|
-
var
|
|
121005
|
+
var import_node_path23 = require("path");
|
|
120501
121006
|
async function runInit(opts) {
|
|
120502
121007
|
try {
|
|
120503
121008
|
const cwd = process.cwd();
|
|
120504
|
-
const root = opts.path ? (0,
|
|
120505
|
-
const name2 = opts.name ?? (0,
|
|
121009
|
+
const root = opts.path ? (0, import_node_path23.resolve)(opts.path) : findRepoRoot(cwd) ?? cwd;
|
|
121010
|
+
const name2 = opts.name ?? (0, import_node_path23.basename)(root);
|
|
120506
121011
|
const envName = opts.env ?? "production";
|
|
120507
121012
|
const repo = { name: name2, path: root };
|
|
120508
121013
|
if (opts.source) repo["source"] = { hostUrl: opts.source };
|
|
@@ -121888,12 +122393,12 @@ function redactUrl(raw) {
|
|
|
121888
122393
|
// ../../packages/cli/src/commands/stop.ts
|
|
121889
122394
|
init_cjs_shims();
|
|
121890
122395
|
var import_node_child_process10 = require("child_process");
|
|
121891
|
-
var
|
|
122396
|
+
var import_node_fs21 = require("fs");
|
|
121892
122397
|
var import_node_util6 = require("util");
|
|
121893
|
-
var
|
|
122398
|
+
var import_node_path24 = require("path");
|
|
121894
122399
|
var import_picocolors45 = __toESM(require_picocolors(), 1);
|
|
121895
122400
|
var execFileAsync3 = (0, import_node_util6.promisify)(import_node_child_process10.execFile);
|
|
121896
|
-
var unlinkAsync = (0, import_node_util6.promisify)(
|
|
122401
|
+
var unlinkAsync = (0, import_node_util6.promisify)(import_node_fs21.unlink);
|
|
121897
122402
|
var SPAWNED_HOST_FILE2 = "spawned-host.json";
|
|
121898
122403
|
var START_TIME_TOLERANCE_S = 60;
|
|
121899
122404
|
var STOP_WAIT_MS = 5e3;
|
|
@@ -122035,11 +122540,11 @@ function isOwnHostProcess(pid, port, root, args2) {
|
|
|
122035
122540
|
if (hostArgvRegex(port).test(args2)) return true;
|
|
122036
122541
|
if (!hostArgvAnyPortRegex().test(args2)) return false;
|
|
122037
122542
|
const rec = readSourceHostPid(root);
|
|
122038
|
-
return rec !== null && rec.pid === pid && Number.isFinite(rec.port) && rec.port === port && !!rec.repoPath && (0,
|
|
122543
|
+
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
122544
|
}
|
|
122040
122545
|
async function cleanupSpawnedRecord(root) {
|
|
122041
122546
|
try {
|
|
122042
|
-
await unlinkAsync((0,
|
|
122547
|
+
await unlinkAsync((0, import_node_path24.join)(root, HORUS_DIR, SPAWNED_HOST_FILE2));
|
|
122043
122548
|
} catch {
|
|
122044
122549
|
}
|
|
122045
122550
|
}
|
|
@@ -122710,8 +123215,8 @@ async function runProvidersDoctorCommand(opts) {
|
|
|
122710
123215
|
|
|
122711
123216
|
// ../../packages/cli/src/commands/generate-config.ts
|
|
122712
123217
|
init_cjs_shims();
|
|
122713
|
-
var
|
|
122714
|
-
var
|
|
123218
|
+
var import_node_fs22 = require("fs");
|
|
123219
|
+
var import_node_path25 = require("path");
|
|
122715
123220
|
var import_picocolors49 = __toESM(require_picocolors(), 1);
|
|
122716
123221
|
function configTemplate(name2, repoPath) {
|
|
122717
123222
|
return `export default {
|
|
@@ -122760,17 +123265,17 @@ async function runGenerateConfig(opts) {
|
|
|
122760
123265
|
const defaults2 = localConfigPath2 != null ? projectDefaults(localConfigPath2) : null;
|
|
122761
123266
|
const hasLocalConfig = defaults2 != null;
|
|
122762
123267
|
const defaultOut = hasLocalConfig ? "horus.config.example.js" : "horus.config.js";
|
|
122763
|
-
const outPath = (0,
|
|
123268
|
+
const outPath = (0, import_node_path25.resolve)(cwd, opts.out ?? defaultOut);
|
|
122764
123269
|
const name2 = opts.name ?? defaults2?.name ?? "my-project";
|
|
122765
123270
|
const repoPath = opts.repo ?? defaults2?.repoPath ?? `/path/to/${name2}`;
|
|
122766
|
-
if ((0,
|
|
123271
|
+
if ((0, import_node_fs22.existsSync)(outPath) && !opts.force) {
|
|
122767
123272
|
log(`${import_picocolors49.default.red("\u2717")} ${outPath} already exists`);
|
|
122768
123273
|
log(import_picocolors49.default.dim(" pass --force to overwrite"));
|
|
122769
123274
|
return 1;
|
|
122770
123275
|
}
|
|
122771
123276
|
try {
|
|
122772
|
-
(0,
|
|
122773
|
-
(0,
|
|
123277
|
+
(0, import_node_fs22.mkdirSync)((0, import_node_path25.dirname)(outPath), { recursive: true });
|
|
123278
|
+
(0, import_node_fs22.writeFileSync)(outPath, configTemplate(name2, repoPath), "utf8");
|
|
122774
123279
|
} catch (err2) {
|
|
122775
123280
|
log(`${import_picocolors49.default.red("\u2717")} Could not write ${outPath}: ${err2.message}`);
|
|
122776
123281
|
return 1;
|
|
@@ -123080,9 +123585,9 @@ async function runReadiness(opts) {
|
|
|
123080
123585
|
|
|
123081
123586
|
// ../../packages/cli/src/commands/skill.ts
|
|
123082
123587
|
init_cjs_shims();
|
|
123083
|
-
var
|
|
123084
|
-
var
|
|
123085
|
-
var
|
|
123588
|
+
var import_node_fs23 = require("fs");
|
|
123589
|
+
var import_node_path26 = require("path");
|
|
123590
|
+
var import_node_os9 = require("os");
|
|
123086
123591
|
var import_promises3 = require("readline/promises");
|
|
123087
123592
|
var import_picocolors51 = __toESM(require_picocolors(), 1);
|
|
123088
123593
|
var SUPPORTED_TARGETS = ["claude", "codex", "gemini", "cursor", "generic"];
|
|
@@ -123090,23 +123595,23 @@ function isValidTarget(target) {
|
|
|
123090
123595
|
return SUPPORTED_TARGETS.includes(target);
|
|
123091
123596
|
}
|
|
123092
123597
|
function getLegacyClaudeSkillPath(opts) {
|
|
123093
|
-
const base2 = opts.global ? (0,
|
|
123094
|
-
return (0,
|
|
123598
|
+
const base2 = opts.global ? (0, import_node_os9.homedir)() : opts.cwd ?? process.cwd();
|
|
123599
|
+
return (0, import_node_path26.join)(base2, ".claude", "skills", "horus.md");
|
|
123095
123600
|
}
|
|
123096
123601
|
function getSkillInstallPath(target, opts) {
|
|
123097
|
-
const base2 = opts.global ? (0,
|
|
123602
|
+
const base2 = opts.global ? (0, import_node_os9.homedir)() : opts.cwd ?? process.cwd();
|
|
123098
123603
|
switch (target) {
|
|
123099
123604
|
case "claude":
|
|
123100
|
-
return (0,
|
|
123605
|
+
return (0, import_node_path26.join)(base2, ".claude", "skills", "horus", "SKILL.md");
|
|
123101
123606
|
case "gemini":
|
|
123102
|
-
return opts.global ? (0,
|
|
123607
|
+
return opts.global ? (0, import_node_path26.join)(base2, ".gemini", "GEMINI.md") : (0, import_node_path26.join)(base2, "GEMINI.md");
|
|
123103
123608
|
case "cursor":
|
|
123104
|
-
return opts.global ? (0,
|
|
123609
|
+
return opts.global ? (0, import_node_path26.join)(base2, ".cursorrules") : (0, import_node_path26.join)(base2, ".cursor", "rules", "horus.mdc");
|
|
123105
123610
|
case "codex":
|
|
123106
|
-
return opts.global ? (0,
|
|
123611
|
+
return opts.global ? (0, import_node_path26.join)(base2, ".codex", "AGENTS.md") : (0, import_node_path26.join)(base2, "AGENTS.md");
|
|
123107
123612
|
case "generic":
|
|
123108
123613
|
default:
|
|
123109
|
-
return (0,
|
|
123614
|
+
return (0, import_node_path26.join)(base2, ".horus", "skills", `horus-${target}.md`);
|
|
123110
123615
|
}
|
|
123111
123616
|
}
|
|
123112
123617
|
var BASE_SKILL = `# Horus Grounded Investigation Skill
|
|
@@ -123431,7 +123936,7 @@ async function runSkillInstall(target, opts) {
|
|
|
123431
123936
|
const content = generateSkillContent(target);
|
|
123432
123937
|
if (target === "claude") {
|
|
123433
123938
|
const legacyPath = getLegacyClaudeSkillPath({ global: opts.global, cwd: opts.cwd });
|
|
123434
|
-
if ((0,
|
|
123939
|
+
if ((0, import_node_fs23.existsSync)(legacyPath)) {
|
|
123435
123940
|
if (!opts.force) {
|
|
123436
123941
|
const confirmed = await (opts.confirm ?? confirm)(
|
|
123437
123942
|
`Found old flat skill file at ${legacyPath}. Replace it with the new directory layout?`
|
|
@@ -123443,7 +123948,7 @@ async function runSkillInstall(target, opts) {
|
|
|
123443
123948
|
}
|
|
123444
123949
|
}
|
|
123445
123950
|
try {
|
|
123446
|
-
(0,
|
|
123951
|
+
(0, import_node_fs23.rmSync)(legacyPath);
|
|
123447
123952
|
log(`${import_picocolors51.default.yellow("!")} Replaced old flat skill file: ${legacyPath}`);
|
|
123448
123953
|
} catch (err2) {
|
|
123449
123954
|
log(`${import_picocolors51.default.red("\u2717")} Could not remove legacy skill file ${legacyPath}: ${err2.message}`);
|
|
@@ -123451,14 +123956,14 @@ async function runSkillInstall(target, opts) {
|
|
|
123451
123956
|
}
|
|
123452
123957
|
}
|
|
123453
123958
|
}
|
|
123454
|
-
if ((0,
|
|
123959
|
+
if ((0, import_node_fs23.existsSync)(outPath) && !opts.force) {
|
|
123455
123960
|
log(`${import_picocolors51.default.red("\u2717")} ${outPath} already exists`);
|
|
123456
123961
|
log(import_picocolors51.default.dim(" pass --force to overwrite"));
|
|
123457
123962
|
return 1;
|
|
123458
123963
|
}
|
|
123459
123964
|
try {
|
|
123460
|
-
(0,
|
|
123461
|
-
(0,
|
|
123965
|
+
(0, import_node_fs23.mkdirSync)((0, import_node_path26.dirname)(outPath), { recursive: true });
|
|
123966
|
+
(0, import_node_fs23.writeFileSync)(outPath, content, "utf8");
|
|
123462
123967
|
} catch (err2) {
|
|
123463
123968
|
log(`${import_picocolors51.default.red("\u2717")} Could not write ${outPath}: ${err2.message}`);
|
|
123464
123969
|
return 1;
|
|
@@ -124199,251 +124704,8 @@ function installCommandTelemetry(program2) {
|
|
|
124199
124704
|
// ../../packages/cli/src/commands/eval.ts
|
|
124200
124705
|
init_cjs_shims();
|
|
124201
124706
|
var import_promises5 = require("fs/promises");
|
|
124202
|
-
var
|
|
124707
|
+
var import_node_path27 = require("path");
|
|
124203
124708
|
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
124709
|
function resolveProject2(config2, repo) {
|
|
124448
124710
|
let project;
|
|
124449
124711
|
try {
|
|
@@ -124492,7 +124754,7 @@ async function runEvalBuild(opts) {
|
|
|
124492
124754
|
const project = resolveProject2(config2, opts.repo);
|
|
124493
124755
|
if (!project) return 1;
|
|
124494
124756
|
const version3 = opts.version ?? CORPUS_VERSION;
|
|
124495
|
-
const outDir = opts.out ? (0,
|
|
124757
|
+
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
124758
|
const { db, sql: sql2 } = await openDb(config2.database.url);
|
|
124497
124759
|
try {
|
|
124498
124760
|
const labels = await listOutcomeLabels(db, {
|
|
@@ -124508,8 +124770,8 @@ async function runEvalBuild(opts) {
|
|
|
124508
124770
|
const build = buildCorpus(labels, resolveReport);
|
|
124509
124771
|
const artifact = serializeCorpus(build, version3);
|
|
124510
124772
|
await (0, import_promises5.mkdir)(outDir, { recursive: true });
|
|
124511
|
-
const corpusPath = (0,
|
|
124512
|
-
const manifestPath = (0,
|
|
124773
|
+
const corpusPath = (0, import_node_path27.join)(outDir, artifact.filename);
|
|
124774
|
+
const manifestPath = (0, import_node_path27.join)(outDir, `corpus-${version3}.manifest.json`);
|
|
124513
124775
|
const manifest = { ...artifact.manifest, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), project };
|
|
124514
124776
|
await (0, import_promises5.writeFile)(corpusPath, artifact.jsonl, "utf8");
|
|
124515
124777
|
await (0, import_promises5.writeFile)(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf8");
|
|
@@ -124564,7 +124826,7 @@ async function runEvalBaseline(opts) {
|
|
|
124564
124826
|
if (opts.json) {
|
|
124565
124827
|
console.log(JSON.stringify({ project, baseline, separation }, null, 2));
|
|
124566
124828
|
} else {
|
|
124567
|
-
const
|
|
124829
|
+
const pct2 = (n4) => `${(n4 * 100).toFixed(0)}%`;
|
|
124568
124830
|
console.log(import_picocolors56.default.bold(`Eval baseline \u2014 ${project}`));
|
|
124569
124831
|
if (baseline.n === 0) {
|
|
124570
124832
|
console.log(
|
|
@@ -124574,7 +124836,7 @@ async function runEvalBaseline(opts) {
|
|
|
124574
124836
|
);
|
|
124575
124837
|
} else {
|
|
124576
124838
|
console.log(
|
|
124577
|
-
` Strict hit-rate: ${import_picocolors56.default.green(
|
|
124839
|
+
` Strict hit-rate: ${import_picocolors56.default.green(pct2(baseline.strictHitRate))} ` + import_picocolors56.default.dim(`(weighted ${pct2(baseline.weightedHitRate)}) over ${baseline.n} investigation(s)`)
|
|
124578
124840
|
);
|
|
124579
124841
|
console.log(
|
|
124580
124842
|
` 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 +124869,120 @@ async function runEvalBaseline(opts) {
|
|
|
124607
124869
|
}
|
|
124608
124870
|
}
|
|
124609
124871
|
|
|
124610
|
-
// ../../packages/cli/src/
|
|
124872
|
+
// ../../packages/cli/src/commands/train.ts
|
|
124611
124873
|
init_cjs_shims();
|
|
124874
|
+
var import_promises6 = require("fs/promises");
|
|
124875
|
+
var import_node_path28 = require("path");
|
|
124876
|
+
var import_node_os10 = require("os");
|
|
124612
124877
|
var import_picocolors57 = __toESM(require_picocolors(), 1);
|
|
124878
|
+
var RERANKER_MODEL_PATH = (0, import_node_path28.join)((0, import_node_os10.homedir)(), ".horus", "reranker.json");
|
|
124879
|
+
function candidatesFromReport(blob) {
|
|
124880
|
+
if (blob === null || typeof blob !== "object") return null;
|
|
124881
|
+
const causes = blob.suspectedCauses;
|
|
124882
|
+
if (!Array.isArray(causes)) return null;
|
|
124883
|
+
return causes;
|
|
124884
|
+
}
|
|
124885
|
+
function pct(n4) {
|
|
124886
|
+
return `${(n4 * 100).toFixed(1)}%`;
|
|
124887
|
+
}
|
|
124888
|
+
async function runTrain(opts) {
|
|
124889
|
+
let source;
|
|
124890
|
+
if (opts.source !== void 0) {
|
|
124891
|
+
if (!isOutcomeSource(opts.source)) {
|
|
124892
|
+
console.error(import_picocolors57.default.red(`Unknown --source "${opts.source}" (one of: feedback, confirm).`));
|
|
124893
|
+
return 1;
|
|
124894
|
+
}
|
|
124895
|
+
source = opts.source;
|
|
124896
|
+
}
|
|
124897
|
+
let since;
|
|
124898
|
+
if (opts.days !== void 0) {
|
|
124899
|
+
if (!Number.isFinite(opts.days) || opts.days <= 0) {
|
|
124900
|
+
console.error(import_picocolors57.default.red("--days must be a positive number."));
|
|
124901
|
+
return 1;
|
|
124902
|
+
}
|
|
124903
|
+
since = new Date(Date.now() - opts.days * 24 * 60 * 60 * 1e3);
|
|
124904
|
+
}
|
|
124905
|
+
let investigations2;
|
|
124906
|
+
try {
|
|
124907
|
+
const config2 = await loadConfig(opts.config);
|
|
124908
|
+
const { db, sql: sql2 } = await openDb(config2.database.url);
|
|
124909
|
+
try {
|
|
124910
|
+
const labels = await listOutcomeLabels(db, {
|
|
124911
|
+
...source ? { source } : {},
|
|
124912
|
+
...since ? { since } : {},
|
|
124913
|
+
...opts.limit ? { limit: opts.limit } : {}
|
|
124914
|
+
});
|
|
124915
|
+
const current = dedupeToCurrentVerdict(labels);
|
|
124916
|
+
investigations2 = [];
|
|
124917
|
+
for (const label of current) {
|
|
124918
|
+
if (label.investigationId === null) continue;
|
|
124919
|
+
if (!isOutcomeResolved(label.resolved) || !isOutcomeSource(label.source)) continue;
|
|
124920
|
+
const inv = await getInvestigation(db, label.investigationId);
|
|
124921
|
+
const candidates = candidatesFromReport(inv?.report ?? null);
|
|
124922
|
+
if (candidates === null) continue;
|
|
124923
|
+
const isConfirm = label.source === "confirm";
|
|
124924
|
+
investigations2.push({
|
|
124925
|
+
investigationId: label.investigationId,
|
|
124926
|
+
target: label.resolved,
|
|
124927
|
+
// confirm's confirmedCause is Horus's own summary (circular) — drop it, like the corpus.
|
|
124928
|
+
confirmedCause: isConfirm ? null : label.confirmedCause ?? null,
|
|
124929
|
+
candidates
|
|
124930
|
+
});
|
|
124931
|
+
}
|
|
124932
|
+
} finally {
|
|
124933
|
+
await sql2.end();
|
|
124934
|
+
}
|
|
124935
|
+
} catch (err2) {
|
|
124936
|
+
console.error(import_picocolors57.default.red(`Could not read the local corpus: ${err2.message}`));
|
|
124937
|
+
return 1;
|
|
124938
|
+
}
|
|
124939
|
+
const split = holdoutSplit(investigations2);
|
|
124940
|
+
const result = trainReranker(investigations2, new Set(split.holdout));
|
|
124941
|
+
if (!result.ok) {
|
|
124942
|
+
if (result.reason === "insufficient-corpus") {
|
|
124943
|
+
console.log(
|
|
124944
|
+
import_picocolors57.default.yellow(
|
|
124945
|
+
`Not enough labeled investigations to train yet (have ${result.labeledInvestigations} with a known-correct cause; need \u2265${MIN_TRAIN_INVESTIGATIONS}).`
|
|
124946
|
+
)
|
|
124947
|
+
);
|
|
124948
|
+
console.log(import_picocolors57.default.dim(" Keep labeling outcomes with `horus feedback` \u2014 the reranker trains once the corpus is large enough."));
|
|
124949
|
+
} else {
|
|
124950
|
+
console.log(import_picocolors57.default.yellow(`Cannot train a meaningful model yet: ${result.detail}.`));
|
|
124951
|
+
}
|
|
124952
|
+
return 0;
|
|
124953
|
+
}
|
|
124954
|
+
const { baselineHitRate, rerankedHitRate, delta, n: n4 } = result.holdout;
|
|
124955
|
+
console.log(import_picocolors57.default.bold("\nReranker training (local, per-tenant)\n"));
|
|
124956
|
+
console.log(` Holdout investigations: ${n4}`);
|
|
124957
|
+
console.log(` Baseline top-1 hit-rate: ${pct(baselineHitRate)}`);
|
|
124958
|
+
console.log(` Reranked top-1 hit-rate: ${pct(rerankedHitRate)}`);
|
|
124959
|
+
console.log(` Delta: ${delta >= 0 ? "+" : ""}${pct(delta)}`);
|
|
124960
|
+
console.log("");
|
|
124961
|
+
if (delta <= 0) {
|
|
124962
|
+
console.log(import_picocolors57.default.yellow(" The reranker did NOT beat the deterministic baseline \u2014 not saving a model."));
|
|
124963
|
+
console.log(import_picocolors57.default.dim(" Horus stays on its hand-tuned ranking. Re-run as more labels accumulate."));
|
|
124964
|
+
return 0;
|
|
124965
|
+
}
|
|
124966
|
+
try {
|
|
124967
|
+
await (0, import_promises6.mkdir)((0, import_node_path28.join)((0, import_node_os10.homedir)(), ".horus"), { recursive: true });
|
|
124968
|
+
const { ok: _ok, ...model } = result;
|
|
124969
|
+
void _ok;
|
|
124970
|
+
await (0, import_promises6.writeFile)(RERANKER_MODEL_PATH, JSON.stringify(model, null, 2) + "\n", "utf8");
|
|
124971
|
+
} catch (err2) {
|
|
124972
|
+
console.error(import_picocolors57.default.red(`Trained, but could not write the model: ${err2.message}`));
|
|
124973
|
+
return 1;
|
|
124974
|
+
}
|
|
124975
|
+
console.log(import_picocolors57.default.green(` \u2713 Reranker beats baseline by +${pct(delta)} \u2014 saved to ${RERANKER_MODEL_PATH}`));
|
|
124976
|
+
console.log(import_picocolors57.default.dim(" It ships OFF by default. Enable it with `HORUS_RERANK=1 horus investigate \u2026`."));
|
|
124977
|
+
return 0;
|
|
124978
|
+
}
|
|
124979
|
+
|
|
124980
|
+
// ../../packages/cli/src/lib/report-crash.ts
|
|
124981
|
+
init_cjs_shims();
|
|
124982
|
+
var import_picocolors58 = __toESM(require_picocolors(), 1);
|
|
124613
124983
|
function reportCrash(err2, log = console.error) {
|
|
124614
124984
|
log(err2);
|
|
124615
|
-
log(
|
|
124985
|
+
log(import_picocolors58.default.dim("\n Looks like a Horus bug or a missing capability? Report it: horus report"));
|
|
124616
124986
|
}
|
|
124617
124987
|
|
|
124618
124988
|
// ../../packages/cli/src/index.ts
|
|
@@ -124824,6 +125194,16 @@ Examples:
|
|
|
124824
125194
|
process.exitCode = await runEvalBaseline(opts);
|
|
124825
125195
|
}
|
|
124826
125196
|
);
|
|
125197
|
+
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(
|
|
125198
|
+
"after",
|
|
125199
|
+
`
|
|
125200
|
+
The reranker is LOCAL and per-tenant: your corpus never leaves this machine. It only REORDERS
|
|
125201
|
+
candidate causes among those that already clear Horus's confidence gates \u2014 it never changes a
|
|
125202
|
+
score, a confidence, or a verdict. It ships OFF; once trained, enable with:
|
|
125203
|
+
HORUS_RERANK=1 horus investigate "<hint>"`
|
|
125204
|
+
).action(async (opts) => {
|
|
125205
|
+
process.exitCode = await runTrain(opts);
|
|
125206
|
+
});
|
|
124827
125207
|
program2.command("mcp").description("Run the Horus MCP server (stdio) \u2014 exposes the local project-knowledge index to agents").action(async () => {
|
|
124828
125208
|
process.exitCode = await runMcpServer();
|
|
124829
125209
|
});
|