@merittdev/horus 0.12.2 → 0.12.4
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 +1005 -234
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -65880,8 +65880,8 @@ init_cjs_shims();
|
|
|
65880
65880
|
|
|
65881
65881
|
// ../../packages/core/src/version.ts
|
|
65882
65882
|
init_cjs_shims();
|
|
65883
|
-
var HORUS_VERSION = true ? "0.12.
|
|
65884
|
-
var PINNED_SOURCE_VERSION = "2.0.
|
|
65883
|
+
var HORUS_VERSION = true ? "0.12.4" : "dev";
|
|
65884
|
+
var PINNED_SOURCE_VERSION = "2.0.2";
|
|
65885
65885
|
|
|
65886
65886
|
// ../../packages/core/src/evidence.ts
|
|
65887
65887
|
init_cjs_shims();
|
|
@@ -71127,6 +71127,42 @@ async function assertSourceVersionPinned() {
|
|
|
71127
71127
|
function isAnalyzed(root) {
|
|
71128
71128
|
return (0, import_node_fs3.existsSync)((0, import_node_path3.join)(root, ".horus", "source"));
|
|
71129
71129
|
}
|
|
71130
|
+
var CURRENT_STORE_BACKEND = (process.env["HORUS_SOURCE_STORAGE_BACKEND"] ?? "sqlite").trim().toLowerCase();
|
|
71131
|
+
function readSourceMeta(root) {
|
|
71132
|
+
try {
|
|
71133
|
+
const raw = (0, import_node_fs3.readFileSync)((0, import_node_path3.join)(root, ".horus", "source", "meta.json"), "utf8");
|
|
71134
|
+
const j3 = JSON.parse(raw);
|
|
71135
|
+
const stats = j3["stats"] ?? void 0;
|
|
71136
|
+
return {
|
|
71137
|
+
storeBackend: typeof j3["store_backend"] === "string" ? j3["store_backend"].toLowerCase() : void 0,
|
|
71138
|
+
embeddingsComplete: typeof j3["embeddings_complete"] === "boolean" ? j3["embeddings_complete"] : void 0,
|
|
71139
|
+
stats: stats ? { symbols: stats["symbols"], embeddings: stats["embeddings"] } : void 0
|
|
71140
|
+
};
|
|
71141
|
+
} catch {
|
|
71142
|
+
return null;
|
|
71143
|
+
}
|
|
71144
|
+
}
|
|
71145
|
+
function metaHasEmbeddings(meta) {
|
|
71146
|
+
if (meta.embeddingsComplete === false) return false;
|
|
71147
|
+
const embeddings = meta.stats?.embeddings ?? 0;
|
|
71148
|
+
const symbols = meta.stats?.symbols ?? 0;
|
|
71149
|
+
return !(symbols > 0 && embeddings === 0);
|
|
71150
|
+
}
|
|
71151
|
+
function indexNeedsReanalyze(root) {
|
|
71152
|
+
if (!isAnalyzed(root)) return null;
|
|
71153
|
+
if (CURRENT_STORE_BACKEND !== "kuzu" && (0, import_node_fs3.existsSync)((0, import_node_path3.join)(root, ".horus", "source", "kuzu"))) {
|
|
71154
|
+
return "legacy K\xF9zuDB store present";
|
|
71155
|
+
}
|
|
71156
|
+
const meta = readSourceMeta(root);
|
|
71157
|
+
if (meta === null) return null;
|
|
71158
|
+
if (meta.storeBackend && meta.storeBackend !== CURRENT_STORE_BACKEND) {
|
|
71159
|
+
return `store backend changed (${meta.storeBackend} -> ${CURRENT_STORE_BACKEND})`;
|
|
71160
|
+
}
|
|
71161
|
+
if (!metaHasEmbeddings(meta)) {
|
|
71162
|
+
return "index has no semantic embeddings (symbols present but 0 vectors)";
|
|
71163
|
+
}
|
|
71164
|
+
return null;
|
|
71165
|
+
}
|
|
71130
71166
|
async function analyzeRepo(root) {
|
|
71131
71167
|
const bin = await resolveSourceBin();
|
|
71132
71168
|
if (!bin) throw new Error("horus-source not found on PATH. Install it: curl -fsSL https://horus.sh/install.sh | bash");
|
|
@@ -71457,7 +71493,7 @@ var SourceCodeProvider = class {
|
|
|
71457
71493
|
if (n4.className) symbol.className = n4.className;
|
|
71458
71494
|
if (n4.language) symbol.language = n4.language;
|
|
71459
71495
|
const content = n4.content;
|
|
71460
|
-
const
|
|
71496
|
+
const snippet2 = typeof content === "string" ? content.slice(0, 600) : void 0;
|
|
71461
71497
|
const sourceBody = typeof content === "string" ? content.slice(0, 2e4) : void 0;
|
|
71462
71498
|
const callees = (detail.callees ?? []).map((c4) => this.serializedToSymbol(c4.node));
|
|
71463
71499
|
const callers = (detail.callers ?? []).map((c4) => this.serializedToSymbol(c4.node));
|
|
@@ -71471,7 +71507,7 @@ var SourceCodeProvider = class {
|
|
|
71471
71507
|
}));
|
|
71472
71508
|
return {
|
|
71473
71509
|
symbol,
|
|
71474
|
-
snippet,
|
|
71510
|
+
snippet: snippet2,
|
|
71475
71511
|
...sourceBody !== void 0 ? { sourceBody } : {},
|
|
71476
71512
|
callers,
|
|
71477
71513
|
callees,
|
|
@@ -71586,6 +71622,167 @@ var SourceMemoryVectorIndex = class {
|
|
|
71586
71622
|
// ../../packages/connectors/src/contract.ts
|
|
71587
71623
|
init_cjs_shims();
|
|
71588
71624
|
|
|
71625
|
+
// ../../packages/connectors/src/duration.ts
|
|
71626
|
+
init_cjs_shims();
|
|
71627
|
+
function readPath(obj, path) {
|
|
71628
|
+
if (Object.prototype.hasOwnProperty.call(obj, path)) return obj[path];
|
|
71629
|
+
const parts2 = path.split(".");
|
|
71630
|
+
let node = obj;
|
|
71631
|
+
for (const part of parts2) {
|
|
71632
|
+
if (node === null || typeof node !== "object") return void 0;
|
|
71633
|
+
node = node[part];
|
|
71634
|
+
}
|
|
71635
|
+
return node;
|
|
71636
|
+
}
|
|
71637
|
+
var UNIT_MS = {
|
|
71638
|
+
h: 36e5,
|
|
71639
|
+
m: 6e4,
|
|
71640
|
+
s: 1e3,
|
|
71641
|
+
ms: 1,
|
|
71642
|
+
us: 1e-3,
|
|
71643
|
+
"\xB5s": 1e-3,
|
|
71644
|
+
ns: 1e-6
|
|
71645
|
+
};
|
|
71646
|
+
var DURATION_TOKEN = /(\d+(?:\.\d+)?)\s*(ms|us|µs|ns|h|m|s)(?![a-zA-Z])/gi;
|
|
71647
|
+
function parseDurationMs(raw) {
|
|
71648
|
+
if (typeof raw !== "string") return null;
|
|
71649
|
+
const s5 = raw.trim().replace(/^~/, "");
|
|
71650
|
+
if (s5 === "") return null;
|
|
71651
|
+
let total = 0;
|
|
71652
|
+
let matched = false;
|
|
71653
|
+
DURATION_TOKEN.lastIndex = 0;
|
|
71654
|
+
let m5;
|
|
71655
|
+
while ((m5 = DURATION_TOKEN.exec(s5)) !== null) {
|
|
71656
|
+
const value = Number(m5[1]);
|
|
71657
|
+
const unit = m5[2].toLowerCase();
|
|
71658
|
+
const factor = UNIT_MS[unit];
|
|
71659
|
+
if (Number.isFinite(value) && factor !== void 0) {
|
|
71660
|
+
total += value * factor;
|
|
71661
|
+
matched = true;
|
|
71662
|
+
}
|
|
71663
|
+
}
|
|
71664
|
+
if (matched) return total;
|
|
71665
|
+
if (/^\d+(?:\.\d+)?$/.test(s5)) {
|
|
71666
|
+
const n4 = Number(s5);
|
|
71667
|
+
return Number.isFinite(n4) ? n4 : null;
|
|
71668
|
+
}
|
|
71669
|
+
return null;
|
|
71670
|
+
}
|
|
71671
|
+
function extractByRegex(text2, regex) {
|
|
71672
|
+
const m5 = regex.exec(text2);
|
|
71673
|
+
if (m5 === null) return null;
|
|
71674
|
+
const captured = m5[1] ?? m5[0];
|
|
71675
|
+
const v4 = captured.trim();
|
|
71676
|
+
return v4 === "" ? null : v4;
|
|
71677
|
+
}
|
|
71678
|
+
function percentile(sortedAsc, p5) {
|
|
71679
|
+
const n4 = sortedAsc.length;
|
|
71680
|
+
if (n4 === 0) return 0;
|
|
71681
|
+
if (n4 === 1) return sortedAsc[0];
|
|
71682
|
+
const rank = Math.ceil(p5 / 100 * n4);
|
|
71683
|
+
const idx = Math.min(n4 - 1, Math.max(0, rank - 1));
|
|
71684
|
+
return sortedAsc[idx];
|
|
71685
|
+
}
|
|
71686
|
+
function computeStat(durations) {
|
|
71687
|
+
const finite = durations.filter((d3) => Number.isFinite(d3));
|
|
71688
|
+
if (finite.length === 0) {
|
|
71689
|
+
return { avg: 0, p95: 0, count: 0, min: 0, max: 0 };
|
|
71690
|
+
}
|
|
71691
|
+
const sorted = [...finite].sort((a2, b5) => a2 - b5);
|
|
71692
|
+
const sum = sorted.reduce((acc, d3) => acc + d3, 0);
|
|
71693
|
+
return {
|
|
71694
|
+
avg: sum / sorted.length,
|
|
71695
|
+
p95: percentile(sorted, 95),
|
|
71696
|
+
count: sorted.length,
|
|
71697
|
+
min: sorted[0],
|
|
71698
|
+
max: sorted[sorted.length - 1]
|
|
71699
|
+
};
|
|
71700
|
+
}
|
|
71701
|
+
function readDimension(rec, spec) {
|
|
71702
|
+
if (spec.field !== void 0) {
|
|
71703
|
+
const raw = readPath(rec.fields, spec.field);
|
|
71704
|
+
if (raw === null || raw === void 0) {
|
|
71705
|
+
if (spec.pattern === void 0) return null;
|
|
71706
|
+
} else {
|
|
71707
|
+
const v4 = String(raw).trim();
|
|
71708
|
+
if (v4 !== "") return v4;
|
|
71709
|
+
if (spec.pattern === void 0) return null;
|
|
71710
|
+
}
|
|
71711
|
+
}
|
|
71712
|
+
if (spec.pattern !== void 0) {
|
|
71713
|
+
const sourceField = spec.patternField;
|
|
71714
|
+
const text2 = sourceField !== void 0 ? typeof readPath(rec.fields, sourceField) === "string" ? readPath(rec.fields, sourceField) : String(readPath(rec.fields, sourceField) ?? "") : rec.message ?? "";
|
|
71715
|
+
if (text2 === "") return null;
|
|
71716
|
+
try {
|
|
71717
|
+
return extractByRegex(text2, new RegExp(spec.pattern));
|
|
71718
|
+
} catch {
|
|
71719
|
+
return null;
|
|
71720
|
+
}
|
|
71721
|
+
}
|
|
71722
|
+
return null;
|
|
71723
|
+
}
|
|
71724
|
+
function readDuration(rec, opts) {
|
|
71725
|
+
if (opts.durationField !== void 0) {
|
|
71726
|
+
const raw = readPath(rec.fields, opts.durationField);
|
|
71727
|
+
if (typeof raw === "number" && Number.isFinite(raw)) {
|
|
71728
|
+
return opts.durationFieldUnit === "s" ? raw * 1e3 : raw;
|
|
71729
|
+
}
|
|
71730
|
+
if (typeof raw === "string") return parseDurationMs(raw);
|
|
71731
|
+
return null;
|
|
71732
|
+
}
|
|
71733
|
+
const msg = rec.message ?? "";
|
|
71734
|
+
if (msg === "") return null;
|
|
71735
|
+
if (opts.durationPattern !== void 0) {
|
|
71736
|
+
let isolated;
|
|
71737
|
+
try {
|
|
71738
|
+
isolated = extractByRegex(msg, new RegExp(opts.durationPattern));
|
|
71739
|
+
} catch {
|
|
71740
|
+
isolated = null;
|
|
71741
|
+
}
|
|
71742
|
+
return isolated !== null ? parseDurationMs(isolated) : null;
|
|
71743
|
+
}
|
|
71744
|
+
return parseDurationMs(msg);
|
|
71745
|
+
}
|
|
71746
|
+
function extractDurationSamples(records, opts) {
|
|
71747
|
+
const needle = opts.completionText?.toLowerCase();
|
|
71748
|
+
const out2 = [];
|
|
71749
|
+
for (const rec of records) {
|
|
71750
|
+
if (needle !== void 0) {
|
|
71751
|
+
const hay = (rec.message ?? "").toLowerCase();
|
|
71752
|
+
if (!hay.includes(needle)) continue;
|
|
71753
|
+
}
|
|
71754
|
+
const dimension = readDimension(rec, opts.dimension);
|
|
71755
|
+
if (dimension === null) continue;
|
|
71756
|
+
const durationMs = readDuration(rec, opts);
|
|
71757
|
+
if (durationMs === null || !Number.isFinite(durationMs)) continue;
|
|
71758
|
+
out2.push({ dimension, durationMs });
|
|
71759
|
+
}
|
|
71760
|
+
return out2;
|
|
71761
|
+
}
|
|
71762
|
+
function aggregateDurations(samples, dimensionName) {
|
|
71763
|
+
if (samples.length === 0) return null;
|
|
71764
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
71765
|
+
for (const s5 of samples) {
|
|
71766
|
+
const list = grouped.get(s5.dimension);
|
|
71767
|
+
if (list === void 0) grouped.set(s5.dimension, [s5.durationMs]);
|
|
71768
|
+
else list.push(s5.durationMs);
|
|
71769
|
+
}
|
|
71770
|
+
const byValue = {};
|
|
71771
|
+
for (const [value, durations] of grouped) {
|
|
71772
|
+
byValue[value] = computeStat(durations);
|
|
71773
|
+
}
|
|
71774
|
+
return {
|
|
71775
|
+
dimension: dimensionName,
|
|
71776
|
+
unit: "ms",
|
|
71777
|
+
byValue,
|
|
71778
|
+
sampleCount: samples.length
|
|
71779
|
+
};
|
|
71780
|
+
}
|
|
71781
|
+
function durationsByDimension(records, opts) {
|
|
71782
|
+
const samples = extractDurationSamples(records, opts);
|
|
71783
|
+
return aggregateDurations(samples, opts.dimension.name);
|
|
71784
|
+
}
|
|
71785
|
+
|
|
71589
71786
|
// ../../packages/connectors/src/factory.ts
|
|
71590
71787
|
init_cjs_shims();
|
|
71591
71788
|
|
|
@@ -72486,6 +72683,33 @@ var ElasticsearchLogsProvider = class {
|
|
|
72486
72683
|
}
|
|
72487
72684
|
return analysis;
|
|
72488
72685
|
}
|
|
72686
|
+
async analyzeDurations(opts) {
|
|
72687
|
+
try {
|
|
72688
|
+
const q2 = {
|
|
72689
|
+
level: opts.level ?? "info",
|
|
72690
|
+
limit: opts.limit ?? 500,
|
|
72691
|
+
...opts.service !== void 0 ? { service: opts.service } : {},
|
|
72692
|
+
...opts.from !== void 0 ? { from: opts.from } : {},
|
|
72693
|
+
...opts.to !== void 0 ? { to: opts.to } : {},
|
|
72694
|
+
...opts.completionText !== void 0 ? { text: opts.completionText, broadText: true } : {}
|
|
72695
|
+
};
|
|
72696
|
+
const records = await this.searchLogs(q2);
|
|
72697
|
+
const normalized = records.map((r) => ({
|
|
72698
|
+
message: r.message,
|
|
72699
|
+
fields: {
|
|
72700
|
+
...r.raw,
|
|
72701
|
+
...r.context !== void 0 ? { context: r.context } : {},
|
|
72702
|
+
...r.eventCode !== void 0 ? { event_code: r.eventCode } : {},
|
|
72703
|
+
...r.service !== void 0 ? { service_name: r.service } : {},
|
|
72704
|
+
...r.detail !== void 0 ? { detail: r.detail } : {},
|
|
72705
|
+
message: r.message
|
|
72706
|
+
}
|
|
72707
|
+
}));
|
|
72708
|
+
return durationsByDimension(normalized, opts);
|
|
72709
|
+
} catch {
|
|
72710
|
+
return null;
|
|
72711
|
+
}
|
|
72712
|
+
}
|
|
72489
72713
|
async checkCompatibility(opts) {
|
|
72490
72714
|
const fields = [
|
|
72491
72715
|
this.mapping.timestampField,
|
|
@@ -72706,6 +72930,85 @@ function compareWindows(baseline, current, spikeRatio = 1.5) {
|
|
|
72706
72930
|
comparisons.sort((a2, b5) => b5.delta - a2.delta);
|
|
72707
72931
|
return comparisons;
|
|
72708
72932
|
}
|
|
72933
|
+
function quantileSorted(sortedAsc, p5) {
|
|
72934
|
+
const n4 = sortedAsc.length;
|
|
72935
|
+
if (n4 === 0) return NaN;
|
|
72936
|
+
if (n4 === 1) return sortedAsc[0];
|
|
72937
|
+
const pos = (n4 - 1) * p5;
|
|
72938
|
+
const lo = Math.floor(pos);
|
|
72939
|
+
const hi = Math.ceil(pos);
|
|
72940
|
+
if (lo === hi) return sortedAsc[lo];
|
|
72941
|
+
const frac = pos - lo;
|
|
72942
|
+
return sortedAsc[lo] * (1 - frac) + sortedAsc[hi] * frac;
|
|
72943
|
+
}
|
|
72944
|
+
function median(values2) {
|
|
72945
|
+
const finite = values2.filter((v4) => Number.isFinite(v4));
|
|
72946
|
+
if (finite.length === 0) return NaN;
|
|
72947
|
+
const sorted = [...finite].sort((a2, b5) => a2 - b5);
|
|
72948
|
+
return quantileSorted(sorted, 0.5);
|
|
72949
|
+
}
|
|
72950
|
+
function detectBimodalPopulation(comparisons, opts) {
|
|
72951
|
+
const spike = opts?.spike ?? 1.5;
|
|
72952
|
+
const drop = opts?.drop ?? 0.67;
|
|
72953
|
+
const minShare = opts?.minShare ?? 0.2;
|
|
72954
|
+
const minMinority = opts?.minMinority ?? 2;
|
|
72955
|
+
const minTotal = opts?.minTotal ?? 3;
|
|
72956
|
+
const gapFactor = opts?.gapFactor ?? 2;
|
|
72957
|
+
const total = comparisons.length;
|
|
72958
|
+
const empty = {
|
|
72959
|
+
bimodal: false,
|
|
72960
|
+
total,
|
|
72961
|
+
highCount: 0,
|
|
72962
|
+
lowCount: 0,
|
|
72963
|
+
degenerateCount: 0,
|
|
72964
|
+
highShare: 0,
|
|
72965
|
+
lowShare: 0,
|
|
72966
|
+
highRatio: NaN,
|
|
72967
|
+
lowRatio: NaN
|
|
72968
|
+
};
|
|
72969
|
+
const highRatios = [];
|
|
72970
|
+
const lowRatios = [];
|
|
72971
|
+
let degenerateCount = 0;
|
|
72972
|
+
for (const c4 of comparisons) {
|
|
72973
|
+
const r = c4.ratio;
|
|
72974
|
+
if (!Number.isFinite(r)) {
|
|
72975
|
+
degenerateCount += 1;
|
|
72976
|
+
} else if (r >= spike) {
|
|
72977
|
+
highRatios.push(r);
|
|
72978
|
+
} else if (r > 0 && r <= drop) {
|
|
72979
|
+
lowRatios.push(r);
|
|
72980
|
+
} else if (r === 0) {
|
|
72981
|
+
degenerateCount += 1;
|
|
72982
|
+
}
|
|
72983
|
+
}
|
|
72984
|
+
const highCount = highRatios.length;
|
|
72985
|
+
const lowCount = lowRatios.length;
|
|
72986
|
+
const counterCount = lowCount + degenerateCount;
|
|
72987
|
+
const result = {
|
|
72988
|
+
...empty,
|
|
72989
|
+
highCount,
|
|
72990
|
+
lowCount,
|
|
72991
|
+
degenerateCount,
|
|
72992
|
+
highShare: total > 0 ? highCount / total : 0,
|
|
72993
|
+
lowShare: total > 0 ? counterCount / total : 0,
|
|
72994
|
+
highRatio: median(highRatios),
|
|
72995
|
+
lowRatio: median(lowRatios)
|
|
72996
|
+
};
|
|
72997
|
+
if (total < minTotal) return result;
|
|
72998
|
+
if (highCount < 1 || counterCount < 1) return result;
|
|
72999
|
+
if (counterCount < minMinority) return result;
|
|
73000
|
+
if (result.lowShare < minShare) return result;
|
|
73001
|
+
if (highCount < minMinority && result.highShare < minShare) return result;
|
|
73002
|
+
if (lowCount > 0) {
|
|
73003
|
+
const hi = result.highRatio;
|
|
73004
|
+
const lo = result.lowRatio;
|
|
73005
|
+
if (Number.isFinite(hi) && Number.isFinite(lo) && lo > 0 && hi / lo < gapFactor) {
|
|
73006
|
+
return result;
|
|
73007
|
+
}
|
|
73008
|
+
}
|
|
73009
|
+
result.bimodal = true;
|
|
73010
|
+
return result;
|
|
73011
|
+
}
|
|
72709
73012
|
|
|
72710
73013
|
// ../../packages/connectors/src/grafana/panels.ts
|
|
72711
73014
|
init_cjs_shims();
|
|
@@ -72833,8 +73136,19 @@ function classifyAnomaly(kind, cmp, opts) {
|
|
|
72833
73136
|
}
|
|
72834
73137
|
function buildFindings(dashboardUid, panelTitle, kind, baseline, current) {
|
|
72835
73138
|
const comparisons = compareWindows(baseline, current);
|
|
73139
|
+
const bimodal = detectBimodalPopulation(comparisons);
|
|
73140
|
+
const spike = 1.5;
|
|
73141
|
+
const drop = 0.67;
|
|
73142
|
+
const isBimodalMember = (cmp) => {
|
|
73143
|
+
const r = cmp.ratio;
|
|
73144
|
+
if (!Number.isFinite(r)) return true;
|
|
73145
|
+
if (r >= spike) return true;
|
|
73146
|
+
if (r === 0) return true;
|
|
73147
|
+
return r > 0 && r <= drop;
|
|
73148
|
+
};
|
|
72836
73149
|
return comparisons.map((cmp) => {
|
|
72837
|
-
const
|
|
73150
|
+
const baseAnomaly = classifyAnomaly(kind, cmp);
|
|
73151
|
+
const anomaly = bimodal.bimodal && baseAnomaly !== "none" && isBimodalMember(cmp) ? "bimodal-population" : baseAnomaly;
|
|
72838
73152
|
const currentSeries = current.find((s5) => {
|
|
72839
73153
|
const keys = Object.keys(cmp.labels);
|
|
72840
73154
|
return keys.every((k3) => s5.labels[k3] === cmp.labels[k3]);
|
|
@@ -72865,6 +73179,8 @@ function anomalyLabel(anomaly) {
|
|
|
72865
73179
|
return "Queue Growth";
|
|
72866
73180
|
case "change":
|
|
72867
73181
|
return "Change";
|
|
73182
|
+
case "bimodal-population":
|
|
73183
|
+
return "Bimodal Population (possible per-segment variance)";
|
|
72868
73184
|
case "none":
|
|
72869
73185
|
return "No Anomaly";
|
|
72870
73186
|
}
|
|
@@ -72879,6 +73195,11 @@ function anomalyRelevance(anomaly) {
|
|
|
72879
73195
|
return 0.8;
|
|
72880
73196
|
case "change":
|
|
72881
73197
|
return 0.55;
|
|
73198
|
+
// Lower than a confident single-cause spike: a two-population pattern is a
|
|
73199
|
+
// hypothesis ("per-segment variance"), not a uniform regression — keep the
|
|
73200
|
+
// engine honest by NOT presenting it with spike-level weight (HOR-435).
|
|
73201
|
+
case "bimodal-population":
|
|
73202
|
+
return 0.6;
|
|
72882
73203
|
case "none":
|
|
72883
73204
|
return 0;
|
|
72884
73205
|
}
|
|
@@ -74009,6 +74330,37 @@ var AxiomProvider = class {
|
|
|
74009
74330
|
return [];
|
|
74010
74331
|
}
|
|
74011
74332
|
}
|
|
74333
|
+
/**
|
|
74334
|
+
* INFO-level duration coverage (HOR-434). Query NON-error completion/duration rows
|
|
74335
|
+
* (e.g. `Completed MANAGE_SALES:KSA ~2m10s`) and aggregate duration by an extracted
|
|
74336
|
+
* dimension (region / market / tenant) — from a structured field or a regex over the
|
|
74337
|
+
* job id / message. Returns per-dimension stats `{ region: { KSA: {avg,p95,count,…} } }`
|
|
74338
|
+
* so the engine sees per-segment variance the error-biased `collect()` path misses.
|
|
74339
|
+
*
|
|
74340
|
+
* Graceful: no completion rows, no parseable duration, or no extractable dimension
|
|
74341
|
+
* → `null`. Never throws.
|
|
74342
|
+
*/
|
|
74343
|
+
async analyzeDurations(opts) {
|
|
74344
|
+
try {
|
|
74345
|
+
const to = opts.to ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
74346
|
+
const from = opts.from ?? new Date(
|
|
74347
|
+
Date.parse(to) - (this.opts.windowMs ?? DEFAULT_WINDOW_MS)
|
|
74348
|
+
).toISOString();
|
|
74349
|
+
const apl = buildApl(
|
|
74350
|
+
this.opts.dataset,
|
|
74351
|
+
opts.completionText !== void 0 ? [opts.completionText] : [],
|
|
74352
|
+
opts.limit ?? 500
|
|
74353
|
+
);
|
|
74354
|
+
const rows = await this.client.query(apl, from, to);
|
|
74355
|
+
const normalized = rows.map((rec) => ({
|
|
74356
|
+
message: pickField2(rec.fields, MESSAGE_FIELDS) ?? "",
|
|
74357
|
+
fields: rec.fields
|
|
74358
|
+
}));
|
|
74359
|
+
return durationsByDimension(normalized, opts);
|
|
74360
|
+
} catch {
|
|
74361
|
+
return null;
|
|
74362
|
+
}
|
|
74363
|
+
}
|
|
74012
74364
|
async health() {
|
|
74013
74365
|
return this.client.health();
|
|
74014
74366
|
}
|
|
@@ -92770,6 +93122,36 @@ function generateHypotheses(evidence2, _correlation, ctx) {
|
|
|
92770
93122
|
contradictingEvidenceIds: [],
|
|
92771
93123
|
missingEvidence: infraSupport.length > 0 ? [] : ["Infrastructure metrics + datastore/cache state (`horus metrics`)"]
|
|
92772
93124
|
});
|
|
93125
|
+
const benignSupport = [
|
|
93126
|
+
.../* @__PURE__ */ new Set([
|
|
93127
|
+
...ctx.perDimensionDurationEvIds ?? [],
|
|
93128
|
+
...ctx.bimodalMetricEvIds ?? []
|
|
93129
|
+
])
|
|
93130
|
+
];
|
|
93131
|
+
if (benignSupport.length > 0 || ctx.benignVarianceApplicable === true) {
|
|
93132
|
+
hyps.push({
|
|
93133
|
+
id: globalThis.crypto.randomUUID(),
|
|
93134
|
+
category: "benign-variance",
|
|
93135
|
+
statement: "The anomaly may reflect expected per-segment/per-region variance (or a skewed average), not a failure.",
|
|
93136
|
+
confidence: 0.1,
|
|
93137
|
+
supportingEvidenceIds: benignSupport,
|
|
93138
|
+
contradictingEvidenceIds: [],
|
|
93139
|
+
missingEvidence: benignSupport.length > 0 ? [] : [
|
|
93140
|
+
"Per-segment duration breakdown (region/market/tenant) or a bimodal metric distribution (`horus logs` / `horus metrics`)"
|
|
93141
|
+
]
|
|
93142
|
+
});
|
|
93143
|
+
}
|
|
93144
|
+
const suggested = new Set(ctx.alertSuggestedCategories ?? []);
|
|
93145
|
+
if (suggested.size > 0) {
|
|
93146
|
+
for (const h3 of hyps) {
|
|
93147
|
+
if (suggested.has(h3.category) && h3.supportingEvidenceIds.length === 0) {
|
|
93148
|
+
h3.missingEvidence = [
|
|
93149
|
+
...h3.missingEvidence,
|
|
93150
|
+
"Suggested by the alert/hint text \u2014 confidence requires independent evidence, not the alert wording itself"
|
|
93151
|
+
];
|
|
93152
|
+
}
|
|
93153
|
+
}
|
|
93154
|
+
}
|
|
92773
93155
|
hyps.sort((a2, b5) => b5.confidence - a2.confidence);
|
|
92774
93156
|
return hyps;
|
|
92775
93157
|
}
|
|
@@ -92817,6 +93199,26 @@ function validateHypotheses(hyps, evidence2) {
|
|
|
92817
93199
|
rationale
|
|
92818
93200
|
};
|
|
92819
93201
|
});
|
|
93202
|
+
const SHARED_EVIDENCE_CEILING = 0.75;
|
|
93203
|
+
const bySupport = /* @__PURE__ */ new Map();
|
|
93204
|
+
for (const v4 of validated) {
|
|
93205
|
+
if (v4.contradictingPresent > 0) continue;
|
|
93206
|
+
const presentSupport = v4.supportingEvidenceIds.filter((id) => present.has(id)).sort();
|
|
93207
|
+
if (presentSupport.length === 0) continue;
|
|
93208
|
+
const key = presentSupport.join("|");
|
|
93209
|
+
const list = bySupport.get(key);
|
|
93210
|
+
if (list === void 0) bySupport.set(key, [v4]);
|
|
93211
|
+
else list.push(v4);
|
|
93212
|
+
}
|
|
93213
|
+
for (const cohort of bySupport.values()) {
|
|
93214
|
+
if (cohort.length < 2) continue;
|
|
93215
|
+
for (const v4 of cohort) {
|
|
93216
|
+
if (v4.confidence > SHARED_EVIDENCE_CEILING) {
|
|
93217
|
+
v4.confidence = SHARED_EVIDENCE_CEILING;
|
|
93218
|
+
v4.rationale = v4.rationale.replace(/\.$/, "") + `; capped at ${SHARED_EVIDENCE_CEILING} \u2014 shares all supporting evidence with ${cohort.length - 1} other hypothesis(es) and nothing distinguishes them.`;
|
|
93219
|
+
}
|
|
93220
|
+
}
|
|
93221
|
+
}
|
|
92820
93222
|
validated.sort((a2, b5) => {
|
|
92821
93223
|
const aElim = a2.verdict === "eliminated" ? 1 : 0;
|
|
92822
93224
|
const bElim = b5.verdict === "eliminated" ? 1 : 0;
|
|
@@ -93004,7 +93406,13 @@ var MEMORY_RELS = /* @__PURE__ */ new Set([
|
|
|
93004
93406
|
function isSymmetricRel(rel) {
|
|
93005
93407
|
return rel === "recurs-with";
|
|
93006
93408
|
}
|
|
93007
|
-
var INCIDENT_DERIVED_KINDS = /* @__PURE__ */ new Set([
|
|
93409
|
+
var INCIDENT_DERIVED_KINDS = /* @__PURE__ */ new Set([
|
|
93410
|
+
"confirmed-outcome",
|
|
93411
|
+
"incident-pattern",
|
|
93412
|
+
// HOR-432: auto-captured investigation memories carry the same incident-family signature/tags so
|
|
93413
|
+
// they participate in recurrence detection. CONTEXT-ONLY — never read by the scoring path.
|
|
93414
|
+
"investigation"
|
|
93415
|
+
]);
|
|
93008
93416
|
function normalizeStoredSignature(sig) {
|
|
93009
93417
|
const s5 = (sig ?? "").trim();
|
|
93010
93418
|
if (s5 === "" || /^\|*$/.test(s5)) return null;
|
|
@@ -93109,6 +93517,35 @@ function createLocalMemoryStore(db) {
|
|
|
93109
93517
|
});
|
|
93110
93518
|
return row;
|
|
93111
93519
|
},
|
|
93520
|
+
async update(id, patch, opts) {
|
|
93521
|
+
const current = await getById(id);
|
|
93522
|
+
if (current === null) throw new Error(`memory item not found: ${id}`);
|
|
93523
|
+
const set = {};
|
|
93524
|
+
if (patch.claim !== void 0) {
|
|
93525
|
+
assertClaimClean(patch.claim);
|
|
93526
|
+
set.claim = patch.claim;
|
|
93527
|
+
}
|
|
93528
|
+
if (patch.confidence !== void 0) set.confidence = patch.confidence;
|
|
93529
|
+
if (patch.payload !== void 0) set.payload = patch.payload;
|
|
93530
|
+
if (Object.keys(set).length > 0) {
|
|
93531
|
+
await db.update(memoryItem).set(set).where(eq(memoryItem.id, id));
|
|
93532
|
+
}
|
|
93533
|
+
await appendAudit({
|
|
93534
|
+
memoryId: id,
|
|
93535
|
+
action: opts.action ?? "update",
|
|
93536
|
+
actor: opts.audit.actor,
|
|
93537
|
+
fromStatus: null,
|
|
93538
|
+
toStatus: null,
|
|
93539
|
+
note: opts.audit.note ?? null,
|
|
93540
|
+
detail: {
|
|
93541
|
+
...opts.detection ? { detection: opts.detection } : {},
|
|
93542
|
+
...opts.detail ?? {}
|
|
93543
|
+
}
|
|
93544
|
+
});
|
|
93545
|
+
const updated = await getById(id);
|
|
93546
|
+
if (updated === null) throw new Error(`memory item vanished during update: ${id}`);
|
|
93547
|
+
return updated;
|
|
93548
|
+
},
|
|
93112
93549
|
get: getById,
|
|
93113
93550
|
async query(q2) {
|
|
93114
93551
|
const repo = q2.repo.trim();
|
|
@@ -93296,6 +93733,395 @@ function createLocalMemoryStore(db) {
|
|
|
93296
93733
|
};
|
|
93297
93734
|
}
|
|
93298
93735
|
|
|
93736
|
+
// ../../packages/engine/src/auto-investigation-memory.ts
|
|
93737
|
+
init_cjs_shims();
|
|
93738
|
+
|
|
93739
|
+
// ../../packages/engine/src/memory-detect.ts
|
|
93740
|
+
init_cjs_shims();
|
|
93741
|
+
var import_node_crypto3 = require("crypto");
|
|
93742
|
+
|
|
93743
|
+
// ../../packages/engine/src/memory-store.ts
|
|
93744
|
+
init_cjs_shims();
|
|
93745
|
+
var HIDDEN_STATUSES = [
|
|
93746
|
+
"forgotten",
|
|
93747
|
+
"deprecated",
|
|
93748
|
+
"contradicted"
|
|
93749
|
+
];
|
|
93750
|
+
function tokenize2(text2) {
|
|
93751
|
+
const seen = /* @__PURE__ */ new Set();
|
|
93752
|
+
const out2 = [];
|
|
93753
|
+
for (const raw of text2.toLowerCase().split(/[^a-z0-9]+/)) {
|
|
93754
|
+
if (raw !== "" && !seen.has(raw)) {
|
|
93755
|
+
seen.add(raw);
|
|
93756
|
+
out2.push(raw);
|
|
93757
|
+
}
|
|
93758
|
+
}
|
|
93759
|
+
return out2;
|
|
93760
|
+
}
|
|
93761
|
+
function scopeSpecificity(scope) {
|
|
93762
|
+
if (scope.startsWith("symbol:")) return 3;
|
|
93763
|
+
if (scope.startsWith("module:")) return 2;
|
|
93764
|
+
if (scope === "repo") return 1;
|
|
93765
|
+
return 0;
|
|
93766
|
+
}
|
|
93767
|
+
var NoopVectorIndex = class {
|
|
93768
|
+
entries = /* @__PURE__ */ new Map();
|
|
93769
|
+
async upsert(i3) {
|
|
93770
|
+
this.entries.set(i3.memoryId, {
|
|
93771
|
+
memoryId: i3.memoryId,
|
|
93772
|
+
repo: i3.repo,
|
|
93773
|
+
scope: i3.scope,
|
|
93774
|
+
tokens: tokenize2(i3.claim)
|
|
93775
|
+
});
|
|
93776
|
+
}
|
|
93777
|
+
async search(i3) {
|
|
93778
|
+
const repo = i3.repo.trim();
|
|
93779
|
+
if (repo === "") return [];
|
|
93780
|
+
const queryTokens = tokenize2(i3.query);
|
|
93781
|
+
if (queryTokens.length === 0) return [];
|
|
93782
|
+
const scored = [...this.entries.values()].filter((e) => e.repo === repo).map((e) => ({
|
|
93783
|
+
memoryId: e.memoryId,
|
|
93784
|
+
score: tagOverlap(queryTokens, e.tokens),
|
|
93785
|
+
specificity: scopeSpecificity(e.scope)
|
|
93786
|
+
})).filter((h3) => h3.score > 0).sort(
|
|
93787
|
+
(a2, b5) => b5.score - a2.score || b5.specificity - a2.specificity || (a2.memoryId < b5.memoryId ? -1 : a2.memoryId > b5.memoryId ? 1 : 0)
|
|
93788
|
+
);
|
|
93789
|
+
const limit = Number.isFinite(i3.limit) && i3.limit > 0 ? Math.floor(i3.limit) : scored.length;
|
|
93790
|
+
return scored.slice(0, limit).map((h3) => ({ memoryId: h3.memoryId, score: h3.score }));
|
|
93791
|
+
}
|
|
93792
|
+
async remove(memoryId) {
|
|
93793
|
+
this.entries.delete(memoryId);
|
|
93794
|
+
}
|
|
93795
|
+
};
|
|
93796
|
+
|
|
93797
|
+
// ../../packages/engine/src/memory-detect.ts
|
|
93798
|
+
var TAG_OVERLAP_MIN = 0.6;
|
|
93799
|
+
var CLAIM_JACCARD_MIN = 0.6;
|
|
93800
|
+
var MIN_CLAIM_TOKENS = 4;
|
|
93801
|
+
var INCIDENT_DERIVED_KINDS2 = /* @__PURE__ */ new Set([
|
|
93802
|
+
"confirmed-outcome",
|
|
93803
|
+
"incident-pattern"
|
|
93804
|
+
]);
|
|
93805
|
+
function usableSignature(sig) {
|
|
93806
|
+
const s5 = (sig ?? "").trim();
|
|
93807
|
+
if (s5 === "" || /^\|*$/.test(s5)) return null;
|
|
93808
|
+
const components = s5.split(/[|,]/).map((c4) => c4.trim()).filter((c4) => c4 !== "");
|
|
93809
|
+
if (components.length === 0) return null;
|
|
93810
|
+
if (components.every((c4) => isGenericTag(c4))) return null;
|
|
93811
|
+
return s5;
|
|
93812
|
+
}
|
|
93813
|
+
function sharedNonGenericTags(a2, b5) {
|
|
93814
|
+
const setB = new Set(b5);
|
|
93815
|
+
const seen = /* @__PURE__ */ new Set();
|
|
93816
|
+
const out2 = [];
|
|
93817
|
+
for (const t2 of a2) {
|
|
93818
|
+
if (setB.has(t2) && !isGenericTag(t2) && !seen.has(t2)) {
|
|
93819
|
+
seen.add(t2);
|
|
93820
|
+
out2.push(t2);
|
|
93821
|
+
}
|
|
93822
|
+
}
|
|
93823
|
+
return out2;
|
|
93824
|
+
}
|
|
93825
|
+
function recurrenceEdgeId(idA, idB) {
|
|
93826
|
+
const [lo, hi] = idA <= idB ? [idA, idB] : [idB, idA];
|
|
93827
|
+
const h3 = (0, import_node_crypto3.createHash)("sha256").update(`recurs-with\0${lo}\0${hi}`).digest("hex");
|
|
93828
|
+
return `lnk_recurs_${h3.slice(0, 32)}`;
|
|
93829
|
+
}
|
|
93830
|
+
function normalizeCause(s5) {
|
|
93831
|
+
return (s5 ?? "").trim().toLowerCase().replace(/\s+/g, " ");
|
|
93832
|
+
}
|
|
93833
|
+
function outcomeConflict(a2, b5) {
|
|
93834
|
+
const opposite = a2.resolved === "yes" && b5.resolved === "no" || a2.resolved === "no" && b5.resolved === "yes";
|
|
93835
|
+
if (opposite) {
|
|
93836
|
+
return `outcome verdicts disagree (${a2.resolved} vs ${b5.resolved})`;
|
|
93837
|
+
}
|
|
93838
|
+
const ca = normalizeCause(a2.confirmedCause);
|
|
93839
|
+
const cb = normalizeCause(b5.confirmedCause);
|
|
93840
|
+
if (ca !== "" && cb !== "" && ca !== cb) {
|
|
93841
|
+
return `confirmed causes differ ("${a2.confirmedCause}" vs "${b5.confirmedCause}")`;
|
|
93842
|
+
}
|
|
93843
|
+
return null;
|
|
93844
|
+
}
|
|
93845
|
+
function recurrenceReason(a2, b5) {
|
|
93846
|
+
const sigA = usableSignature(a2.signature);
|
|
93847
|
+
const sigB = usableSignature(b5.signature);
|
|
93848
|
+
if (sigA !== null && sigB !== null && sigA === sigB) {
|
|
93849
|
+
return `identical incident signature "${sigA}"`;
|
|
93850
|
+
}
|
|
93851
|
+
const tagsA = a2.tags ?? [];
|
|
93852
|
+
const tagsB = b5.tags ?? [];
|
|
93853
|
+
const shared = sharedNonGenericTags(tagsA, tagsB);
|
|
93854
|
+
const overlap = tagOverlap([...tagsA], [...tagsB]);
|
|
93855
|
+
if (overlap >= TAG_OVERLAP_MIN && shared.length >= 1) {
|
|
93856
|
+
return `tag overlap ${overlap.toFixed(2)} on [${shared.join(", ")}]`;
|
|
93857
|
+
}
|
|
93858
|
+
if (sigA === null && sigB === null && shared.length >= 1) {
|
|
93859
|
+
const ta = tokenize2(a2.claim);
|
|
93860
|
+
const tb = tokenize2(b5.claim);
|
|
93861
|
+
if (ta.length >= MIN_CLAIM_TOKENS && tb.length >= MIN_CLAIM_TOKENS) {
|
|
93862
|
+
const j3 = tagOverlap(ta, tb);
|
|
93863
|
+
if (j3 >= CLAIM_JACCARD_MIN) {
|
|
93864
|
+
return `claim similarity ${j3.toFixed(2)} corroborated by [${shared.join(", ")}]`;
|
|
93865
|
+
}
|
|
93866
|
+
}
|
|
93867
|
+
}
|
|
93868
|
+
return null;
|
|
93869
|
+
}
|
|
93870
|
+
function edgeKey(rel, from, to) {
|
|
93871
|
+
if (rel === "recurs-with" || rel === "contradicts") {
|
|
93872
|
+
const [lo, hi] = from <= to ? [from, to] : [to, from];
|
|
93873
|
+
return `${rel}|${lo}|${hi}`;
|
|
93874
|
+
}
|
|
93875
|
+
return `${rel}|${from}|${to}`;
|
|
93876
|
+
}
|
|
93877
|
+
async function detectMemoryEdges(store, opts, deps = {}) {
|
|
93878
|
+
const repo = opts.repo.trim();
|
|
93879
|
+
if (repo === "") return [];
|
|
93880
|
+
const items = await store.query({ repo, limit: opts.limit ?? 1e3 });
|
|
93881
|
+
const candidates = items.filter((i3) => INCIDENT_DERIVED_KINDS2.has(i3.kind));
|
|
93882
|
+
if (candidates.length < 2) return [];
|
|
93883
|
+
const existing = /* @__PURE__ */ new Set();
|
|
93884
|
+
const investigationOf = /* @__PURE__ */ new Map();
|
|
93885
|
+
for (const item of candidates) {
|
|
93886
|
+
const out2 = await store.links(item.id, { direction: "out" });
|
|
93887
|
+
for (const l2 of out2) {
|
|
93888
|
+
if (l2.toKind === "memory") {
|
|
93889
|
+
existing.add(edgeKey(l2.rel, l2.fromMemoryId, l2.toRef));
|
|
93890
|
+
} else if (l2.toKind === "incident" && l2.rel === "about-incident") {
|
|
93891
|
+
if (!investigationOf.has(item.id)) investigationOf.set(item.id, l2.toRef);
|
|
93892
|
+
}
|
|
93893
|
+
}
|
|
93894
|
+
}
|
|
93895
|
+
const proposals = [];
|
|
93896
|
+
const outcomeCache = /* @__PURE__ */ new Map();
|
|
93897
|
+
const resolveOutcome = async (investigationId) => {
|
|
93898
|
+
if (deps.outcomeFor === void 0) return null;
|
|
93899
|
+
if (outcomeCache.has(investigationId)) return outcomeCache.get(investigationId) ?? null;
|
|
93900
|
+
let v4 = null;
|
|
93901
|
+
try {
|
|
93902
|
+
v4 = await deps.outcomeFor(investigationId);
|
|
93903
|
+
} catch {
|
|
93904
|
+
v4 = null;
|
|
93905
|
+
}
|
|
93906
|
+
outcomeCache.set(investigationId, v4);
|
|
93907
|
+
return v4;
|
|
93908
|
+
};
|
|
93909
|
+
for (let i3 = 0; i3 < candidates.length; i3++) {
|
|
93910
|
+
const a2 = candidates[i3];
|
|
93911
|
+
for (let j3 = i3 + 1; j3 < candidates.length; j3++) {
|
|
93912
|
+
const b5 = candidates[j3];
|
|
93913
|
+
if (!existing.has(edgeKey("recurs-with", a2.id, b5.id))) {
|
|
93914
|
+
const reason = recurrenceReason(a2, b5);
|
|
93915
|
+
if (reason !== null) {
|
|
93916
|
+
const [lo, hi] = a2.id <= b5.id ? [a2.id, b5.id] : [b5.id, a2.id];
|
|
93917
|
+
proposals.push({
|
|
93918
|
+
id: recurrenceEdgeId(a2.id, b5.id),
|
|
93919
|
+
fromMemoryId: lo,
|
|
93920
|
+
toMemoryId: hi,
|
|
93921
|
+
rel: "recurs-with",
|
|
93922
|
+
detection: "auto:recurrence",
|
|
93923
|
+
reason
|
|
93924
|
+
});
|
|
93925
|
+
}
|
|
93926
|
+
}
|
|
93927
|
+
if (deps.outcomeFor !== void 0 && a2.kind === "confirmed-outcome" && b5.kind === "confirmed-outcome" && !existing.has(edgeKey("contradicts", a2.id, b5.id))) {
|
|
93928
|
+
const sigA = usableSignature(a2.signature);
|
|
93929
|
+
const sigB = usableSignature(b5.signature);
|
|
93930
|
+
const invA = investigationOf.get(a2.id);
|
|
93931
|
+
const invB = investigationOf.get(b5.id);
|
|
93932
|
+
if (sigA !== null && sigA === sigB && invA !== void 0 && invB !== void 0) {
|
|
93933
|
+
const oa = await resolveOutcome(invA);
|
|
93934
|
+
const ob = await resolveOutcome(invB);
|
|
93935
|
+
if (oa !== null && ob !== null) {
|
|
93936
|
+
const reason = outcomeConflict(oa, ob);
|
|
93937
|
+
if (reason !== null) {
|
|
93938
|
+
const [lo, hi] = a2.id <= b5.id ? [a2.id, b5.id] : [b5.id, a2.id];
|
|
93939
|
+
const invLo = lo === a2.id ? invA : invB;
|
|
93940
|
+
const invHi = lo === a2.id ? invB : invA;
|
|
93941
|
+
proposals.push({
|
|
93942
|
+
id: "",
|
|
93943
|
+
// directional — the store mints the id
|
|
93944
|
+
fromMemoryId: lo,
|
|
93945
|
+
toMemoryId: hi,
|
|
93946
|
+
rel: "contradicts",
|
|
93947
|
+
detection: "auto:contradiction",
|
|
93948
|
+
reason,
|
|
93949
|
+
detail: { investigationIds: [invLo, invHi] }
|
|
93950
|
+
});
|
|
93951
|
+
}
|
|
93952
|
+
}
|
|
93953
|
+
}
|
|
93954
|
+
}
|
|
93955
|
+
}
|
|
93956
|
+
}
|
|
93957
|
+
return proposals;
|
|
93958
|
+
}
|
|
93959
|
+
|
|
93960
|
+
// ../../packages/engine/src/auto-investigation-memory.ts
|
|
93961
|
+
var RECURRENCE_SCAN_LIMIT = 100;
|
|
93962
|
+
var MAX_INVESTIGATION_IDS = 20;
|
|
93963
|
+
function snippet(text2, max = 140) {
|
|
93964
|
+
const s5 = text2.trim().replace(/\s+/g, " ");
|
|
93965
|
+
return s5.length > max ? `${s5.slice(0, max - 1)}\u2026` : s5;
|
|
93966
|
+
}
|
|
93967
|
+
function autoInvestigationMemoryEnabled() {
|
|
93968
|
+
const v4 = (process.env.HORUS_AUTO_INVESTIGATION_MEMORY ?? "").trim().toLowerCase();
|
|
93969
|
+
return v4 !== "0" && v4 !== "false" && v4 !== "off" && v4 !== "no";
|
|
93970
|
+
}
|
|
93971
|
+
function deriveInvestigationFields(report) {
|
|
93972
|
+
const hint = report.input.hint;
|
|
93973
|
+
const topHyp = report.hypotheses[0];
|
|
93974
|
+
const headline = topHyp !== void 0 ? snippet(topHyp.statement || topHyp.category || report.summary) : snippet(report.summary || "(no leading hypothesis)");
|
|
93975
|
+
return {
|
|
93976
|
+
hint,
|
|
93977
|
+
claim: `Investigation: ${snippet(hint)} -> ${headline}`,
|
|
93978
|
+
confidence: report.confidence,
|
|
93979
|
+
signature: deriveSignature(report),
|
|
93980
|
+
tags: deriveTags(report),
|
|
93981
|
+
topHypothesisCategory: topHyp !== void 0 ? topHyp.category ?? null : null
|
|
93982
|
+
};
|
|
93983
|
+
}
|
|
93984
|
+
function recurrenceCountOf(payload) {
|
|
93985
|
+
if (payload !== null && typeof payload === "object") {
|
|
93986
|
+
const c4 = payload.recurrenceCount;
|
|
93987
|
+
if (typeof c4 === "number" && Number.isFinite(c4) && c4 >= 1) return Math.floor(c4);
|
|
93988
|
+
}
|
|
93989
|
+
return 1;
|
|
93990
|
+
}
|
|
93991
|
+
async function createInvestigationMemory(store, investigationId, report, audit) {
|
|
93992
|
+
const repo = report.input.repo?.trim() || null;
|
|
93993
|
+
if (repo === null) return null;
|
|
93994
|
+
const fields = deriveInvestigationFields(report);
|
|
93995
|
+
const item = {
|
|
93996
|
+
id: "",
|
|
93997
|
+
kind: "investigation",
|
|
93998
|
+
source: "investigation",
|
|
93999
|
+
scope: "repo",
|
|
94000
|
+
visibility: "private",
|
|
94001
|
+
// HONEST confidence — the report's own value, never inflated.
|
|
94002
|
+
confidence: fields.confidence,
|
|
94003
|
+
claim: fields.claim,
|
|
94004
|
+
repo,
|
|
94005
|
+
evidence: [],
|
|
94006
|
+
// Incident-family recall keys (same derivation as incident_memory) so this memory recurs.
|
|
94007
|
+
signature: fields.signature,
|
|
94008
|
+
tags: fields.tags,
|
|
94009
|
+
payload: {
|
|
94010
|
+
investigationId,
|
|
94011
|
+
hint: fields.hint,
|
|
94012
|
+
topHypothesis: fields.topHypothesisCategory,
|
|
94013
|
+
// First sighting: a single investigation is "recurred 1×". Bumped on each later recurrence.
|
|
94014
|
+
recurrenceCount: 1
|
|
94015
|
+
}
|
|
94016
|
+
};
|
|
94017
|
+
const created = await store.add(item, audit);
|
|
94018
|
+
if (investigationId !== null && investigationId.trim() !== "") {
|
|
94019
|
+
await store.addLink(
|
|
94020
|
+
{
|
|
94021
|
+
id: "",
|
|
94022
|
+
fromMemoryId: created.id,
|
|
94023
|
+
rel: "about-incident",
|
|
94024
|
+
toKind: "incident",
|
|
94025
|
+
toRef: investigationId
|
|
94026
|
+
},
|
|
94027
|
+
{ detection: "structural", audit }
|
|
94028
|
+
);
|
|
94029
|
+
}
|
|
94030
|
+
return created;
|
|
94031
|
+
}
|
|
94032
|
+
async function detectRecurrence(store, probe2, repo) {
|
|
94033
|
+
const r = repo.trim();
|
|
94034
|
+
if (r === "") return null;
|
|
94035
|
+
const existing = await store.query({
|
|
94036
|
+
repo: r,
|
|
94037
|
+
kind: ["investigation"],
|
|
94038
|
+
limit: RECURRENCE_SCAN_LIMIT
|
|
94039
|
+
});
|
|
94040
|
+
for (const candidate of existing) {
|
|
94041
|
+
if (recurrenceReason(candidate, probe2) !== null) return candidate;
|
|
94042
|
+
}
|
|
94043
|
+
return null;
|
|
94044
|
+
}
|
|
94045
|
+
async function consolidateRecurrence(store, existing, investigationId, fields, reportTime, audit) {
|
|
94046
|
+
const prev = existing.payload ?? {};
|
|
94047
|
+
const recurrenceCount = recurrenceCountOf(prev) + 1;
|
|
94048
|
+
const prevIds = Array.isArray(prev.investigationIds) ? prev.investigationIds.filter((x4) => typeof x4 === "string") : [];
|
|
94049
|
+
const seedIds = prevIds.length > 0 ? prevIds : typeof prev.investigationId === "string" ? [prev.investigationId] : [];
|
|
94050
|
+
const thisId = investigationId !== null && investigationId.trim() !== "" ? investigationId : null;
|
|
94051
|
+
const investigationIds = (thisId !== null ? [...seedIds, thisId] : seedIds).slice(
|
|
94052
|
+
-MAX_INVESTIGATION_IDS
|
|
94053
|
+
);
|
|
94054
|
+
const payload = {
|
|
94055
|
+
...prev,
|
|
94056
|
+
recurrenceCount,
|
|
94057
|
+
lastSeenAt: reportTime.toISOString(),
|
|
94058
|
+
investigationIds,
|
|
94059
|
+
// Refresh the "latest finding" provenance; keep the original first-seen id for reference.
|
|
94060
|
+
hint: fields.hint,
|
|
94061
|
+
topHypothesis: fields.topHypothesisCategory,
|
|
94062
|
+
investigationId: typeof prev.investigationId === "string" ? prev.investigationId : seedIds[0] ?? thisId
|
|
94063
|
+
};
|
|
94064
|
+
const updated = await store.update(
|
|
94065
|
+
existing.id,
|
|
94066
|
+
{
|
|
94067
|
+
claim: fields.claim,
|
|
94068
|
+
// HONEST: the LATEST report's confidence, verbatim — never inflated, never an average.
|
|
94069
|
+
confidence: fields.confidence,
|
|
94070
|
+
payload
|
|
94071
|
+
},
|
|
94072
|
+
{
|
|
94073
|
+
audit,
|
|
94074
|
+
action: "recurrence",
|
|
94075
|
+
detection: "auto:recurrence-consolidate",
|
|
94076
|
+
detail: { recurrenceCount, ...thisId !== null ? { investigationId: thisId } : {} }
|
|
94077
|
+
}
|
|
94078
|
+
);
|
|
94079
|
+
if (thisId !== null) {
|
|
94080
|
+
await store.addLink(
|
|
94081
|
+
{
|
|
94082
|
+
id: "",
|
|
94083
|
+
fromMemoryId: existing.id,
|
|
94084
|
+
rel: "about-incident",
|
|
94085
|
+
toKind: "incident",
|
|
94086
|
+
toRef: thisId
|
|
94087
|
+
},
|
|
94088
|
+
{ detection: "structural", audit }
|
|
94089
|
+
);
|
|
94090
|
+
}
|
|
94091
|
+
return updated;
|
|
94092
|
+
}
|
|
94093
|
+
async function captureInvestigationMemory(store, investigationId, report, reportTime, audit) {
|
|
94094
|
+
const repo = report.input.repo?.trim() || null;
|
|
94095
|
+
if (repo === null) return { action: "skipped", memoryId: null, recurrenceCount: 0 };
|
|
94096
|
+
const fields = deriveInvestigationFields(report);
|
|
94097
|
+
const existing = await detectRecurrence(
|
|
94098
|
+
store,
|
|
94099
|
+
{ signature: fields.signature, tags: fields.tags, claim: fields.claim },
|
|
94100
|
+
repo
|
|
94101
|
+
);
|
|
94102
|
+
if (existing !== null) {
|
|
94103
|
+
const updated = await consolidateRecurrence(
|
|
94104
|
+
store,
|
|
94105
|
+
existing,
|
|
94106
|
+
investigationId,
|
|
94107
|
+
fields,
|
|
94108
|
+
reportTime,
|
|
94109
|
+
audit
|
|
94110
|
+
);
|
|
94111
|
+
return {
|
|
94112
|
+
action: "consolidated",
|
|
94113
|
+
memoryId: updated.id,
|
|
94114
|
+
recurrenceCount: recurrenceCountOf(updated.payload)
|
|
94115
|
+
};
|
|
94116
|
+
}
|
|
94117
|
+
const created = await createInvestigationMemory(store, investigationId, report, audit);
|
|
94118
|
+
return {
|
|
94119
|
+
action: created !== null ? "created" : "skipped",
|
|
94120
|
+
memoryId: created?.id ?? null,
|
|
94121
|
+
recurrenceCount: 1
|
|
94122
|
+
};
|
|
94123
|
+
}
|
|
94124
|
+
|
|
93299
94125
|
// ../../packages/engine/src/gaps.ts
|
|
93300
94126
|
init_cjs_shims();
|
|
93301
94127
|
function detectMissingEvidence(r, connectors = {}, mode) {
|
|
@@ -94544,6 +95370,46 @@ function looksPerformance(hint) {
|
|
|
94544
95370
|
hint
|
|
94545
95371
|
);
|
|
94546
95372
|
}
|
|
95373
|
+
function looksDurationThemed(hint) {
|
|
95374
|
+
return looksPerformance(hint) || /\b(duration|took|elapsed|anomal(?:y|ies|ous)|spike|variance|regress(?:ion|ed)?|p\d{2})\b/i.test(
|
|
95375
|
+
hint
|
|
95376
|
+
);
|
|
95377
|
+
}
|
|
95378
|
+
function parseSuggestedCauses(hint) {
|
|
95379
|
+
const SUGGESTIVE = /\b(could indicate|may be|might be|maybe|possibly|suggest(?:s|ing|ed)?|likely (?:caused|due)|points? to|consistent with|indicat(?:es|ive)|appears? to be|seems? to be|probably)\b/i;
|
|
95380
|
+
if (!SUGGESTIVE.test(hint)) return [];
|
|
95381
|
+
const CATEGORY_CUES = [
|
|
95382
|
+
["deployment-regression", /\b(deploy(?:ment|ed)?|release|rollout|regression|recent change|shipped)\b/i],
|
|
95383
|
+
["infrastructure", /\b(database|db|infra(?:structure)?|network|cache|disk|memory|cpu|oom|out of memory|connection pool|datastore|redis|mongo|postgres)\b/i],
|
|
95384
|
+
["external-api-latency", /\b(upstream|external api|third[- ]party|downstream|api latency|timeout|slow (?:api|service)|gateway)\b/i],
|
|
95385
|
+
["retry-storm", /\b(retr(?:y|ies)|retry storm|thundering herd|amplif)/i],
|
|
95386
|
+
["queue-backlog", /\b(queue|backlog|consumer lag|enqueue)\b/i],
|
|
95387
|
+
["worker-slowdown", /\b(worker|consumer)\b/i],
|
|
95388
|
+
["benign-variance", /\b(per[- ]segment|per[- ]region|skew(?:ed)?|variance|expected variation|noise)\b/i]
|
|
95389
|
+
];
|
|
95390
|
+
const out2 = [];
|
|
95391
|
+
for (const [category, cue] of CATEGORY_CUES) {
|
|
95392
|
+
if (cue.test(hint)) out2.push(category);
|
|
95393
|
+
}
|
|
95394
|
+
return out2;
|
|
95395
|
+
}
|
|
95396
|
+
function formatDurationMs(ms) {
|
|
95397
|
+
if (!Number.isFinite(ms) || ms < 0) return "?";
|
|
95398
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
95399
|
+
const totalSec = ms / 1e3;
|
|
95400
|
+
if (totalSec < 60) {
|
|
95401
|
+
const s5 = Math.round(totalSec * 10) / 10;
|
|
95402
|
+
return `${s5}s`;
|
|
95403
|
+
}
|
|
95404
|
+
const totalMin = Math.floor(totalSec / 60);
|
|
95405
|
+
const remSec = Math.round(totalSec % 60);
|
|
95406
|
+
if (totalMin < 60) {
|
|
95407
|
+
return remSec > 0 ? `${totalMin}m${remSec}s` : `${totalMin}m`;
|
|
95408
|
+
}
|
|
95409
|
+
const h3 = Math.floor(totalMin / 60);
|
|
95410
|
+
const remMin = totalMin % 60;
|
|
95411
|
+
return remMin > 0 ? `${h3}h${remMin}m` : `${h3}h`;
|
|
95412
|
+
}
|
|
94547
95413
|
var SOURCE_IMPACT_RE = [
|
|
94548
95414
|
/\bwhat depends on\b/i,
|
|
94549
95415
|
/\bblast[- ]?radius\b/i,
|
|
@@ -94694,6 +95560,7 @@ async function investigate(input, deps) {
|
|
|
94694
95560
|
const hint = input.hint.trim();
|
|
94695
95561
|
const intent = classifyIntent(hint, input.since);
|
|
94696
95562
|
const namedSymbols = parseNamedSymbols(hint);
|
|
95563
|
+
const alertSuggestedCategories = parseSuggestedCauses(hint);
|
|
94697
95564
|
const sourceImpactHint = intent === "source-impact";
|
|
94698
95565
|
const preferNamed = sourceImpactHint && namedSymbols.length > 0 ? namedSymbols[0] : void 0;
|
|
94699
95566
|
const evidence2 = [];
|
|
@@ -94858,8 +95725,8 @@ async function investigate(input, deps) {
|
|
|
94858
95725
|
// HOR-386 — empty-return: route to `search` for the right name (deterministic).
|
|
94859
95726
|
nextSteps: route({ command: "investigate", empty: true, query: hint })
|
|
94860
95727
|
};
|
|
94861
|
-
const
|
|
94862
|
-
if (
|
|
95728
|
+
const persistedEmpty = await persist(db, input, report2);
|
|
95729
|
+
if (persistedEmpty) report2.id = persistedEmpty.id;
|
|
94863
95730
|
return report2;
|
|
94864
95731
|
}
|
|
94865
95732
|
let label = input.service ?? hint;
|
|
@@ -95018,6 +95885,7 @@ async function investigate(input, deps) {
|
|
|
95018
95885
|
const logEvIds = [];
|
|
95019
95886
|
const directLogEvIds = [];
|
|
95020
95887
|
const ambientLogEvIds = [];
|
|
95888
|
+
const perDimensionDurationEvIds = [];
|
|
95021
95889
|
const directSignatures = [];
|
|
95022
95890
|
const directEvIdByKey = /* @__PURE__ */ new Map();
|
|
95023
95891
|
const seedEmittedJoins = [];
|
|
@@ -95436,6 +96304,55 @@ async function investigate(input, deps) {
|
|
|
95436
96304
|
axiomCollected = false;
|
|
95437
96305
|
}
|
|
95438
96306
|
}
|
|
96307
|
+
if (looksDurationThemed(hint)) {
|
|
96308
|
+
const from = logWindowFrom(input.logsSince ?? input.since);
|
|
96309
|
+
const baseDurationOpts = input.durationDimension ?? {
|
|
96310
|
+
// Conservative default for the common `Completed <JOB>:<REGION> ~2m10s` shape: extract an
|
|
96311
|
+
// alphanumeric token after a colon as the dimension, scoped to completion lines, parsing
|
|
96312
|
+
// the duration out of the message. A non-matching corpus yields null (graceful).
|
|
96313
|
+
dimension: { name: "region", pattern: ":\\s*([A-Za-z][\\w-]+)" },
|
|
96314
|
+
completionText: "Completed"
|
|
96315
|
+
};
|
|
96316
|
+
const durationOpts = {
|
|
96317
|
+
...baseDurationOpts,
|
|
96318
|
+
from: baseDurationOpts.from ?? from,
|
|
96319
|
+
...input.service !== void 0 && baseDurationOpts.service === void 0 ? { service: input.service } : {},
|
|
96320
|
+
level: baseDurationOpts.level ?? "info"
|
|
96321
|
+
};
|
|
96322
|
+
const durationProviders = [];
|
|
96323
|
+
if (deps.logs && typeof deps.logs.analyzeDurations === "function") {
|
|
96324
|
+
durationProviders.push({ source: "elasticsearch", analyze: (o5) => deps.logs.analyzeDurations(o5) });
|
|
96325
|
+
}
|
|
96326
|
+
if (deps.axiom && typeof deps.axiom.analyzeDurations === "function") {
|
|
96327
|
+
durationProviders.push({ source: "axiom", analyze: (o5) => deps.axiom.analyzeDurations(o5) });
|
|
96328
|
+
}
|
|
96329
|
+
for (const provider of durationProviders) {
|
|
96330
|
+
try {
|
|
96331
|
+
const result = await provider.analyze(durationOpts);
|
|
96332
|
+
if (result === null) continue;
|
|
96333
|
+
const values2 = Object.entries(result.byValue);
|
|
96334
|
+
if (values2.length === 0) continue;
|
|
96335
|
+
const summary2 = values2.sort((a2, b5) => b5[1].avg - a2[1].avg).map(([val, st3]) => `${val} ${formatDurationMs(st3.avg)}`).join(", ");
|
|
96336
|
+
const ev = mkEv(
|
|
96337
|
+
"log",
|
|
96338
|
+
`Duration by ${result.dimension}: ${summary2}`.slice(0, 220),
|
|
96339
|
+
{
|
|
96340
|
+
source: provider.source,
|
|
96341
|
+
kind: "duration-by-dimension",
|
|
96342
|
+
dimension: result.dimension,
|
|
96343
|
+
unit: result.unit,
|
|
96344
|
+
byValue: result.byValue,
|
|
96345
|
+
sampleCount: result.sampleCount
|
|
96346
|
+
},
|
|
96347
|
+
{},
|
|
96348
|
+
void 0,
|
|
96349
|
+
0.6
|
|
96350
|
+
);
|
|
96351
|
+
perDimensionDurationEvIds.push(ev.id);
|
|
96352
|
+
} catch {
|
|
96353
|
+
}
|
|
96354
|
+
}
|
|
96355
|
+
}
|
|
95439
96356
|
let stateAnalysis = null;
|
|
95440
96357
|
const stateEvIds = [];
|
|
95441
96358
|
const stateCollections = /* @__PURE__ */ new Set();
|
|
@@ -95526,6 +96443,7 @@ async function investigate(input, deps) {
|
|
|
95526
96443
|
const METRICS_TIMEOUT_MS = 3e4;
|
|
95527
96444
|
const metricEvIds = [];
|
|
95528
96445
|
const latencyMetricEvIds = [];
|
|
96446
|
+
const bimodalMetricEvIds = [];
|
|
95529
96447
|
const queueMetricEvIds = [];
|
|
95530
96448
|
const queueMetricEvIdsByQueue = /* @__PURE__ */ new Map();
|
|
95531
96449
|
const nominalMetricEvIds = [];
|
|
@@ -95577,6 +96495,8 @@ async function investigate(input, deps) {
|
|
|
95577
96495
|
);
|
|
95578
96496
|
const genericPerf = serviceFilter.length === 0 && hintIsPerf && f5.anomaly === "latency-spike";
|
|
95579
96497
|
if (matchesService || matchesHint || genericPerf) latencyMetricEvIds.push(ev.id);
|
|
96498
|
+
} else if (f5.anomaly === "bimodal-population") {
|
|
96499
|
+
bimodalMetricEvIds.push(ev.id);
|
|
95580
96500
|
} else if (f5.anomaly === "queue-growth") {
|
|
95581
96501
|
const panelNorm = f5.panelTitle.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
95582
96502
|
const labelNorms = Object.values(f5.labels).map(
|
|
@@ -95647,7 +96567,13 @@ async function investigate(input, deps) {
|
|
|
95647
96567
|
sinceProvided: input.since !== void 0,
|
|
95648
96568
|
suppressRuntimeHypotheses: sourceImpactHint,
|
|
95649
96569
|
recentChangeRelevant,
|
|
95650
|
-
graph
|
|
96570
|
+
graph,
|
|
96571
|
+
// HOR-435: per-segment duration breakdown (#2) + bimodal-population metric (#3) support
|
|
96572
|
+
// the benign-variance hypothesis (#4); the suggested categories de-anchor alert text (#1).
|
|
96573
|
+
perDimensionDurationEvIds,
|
|
96574
|
+
bimodalMetricEvIds,
|
|
96575
|
+
benignVarianceApplicable: looksDurationThemed(hint) || perDimensionDurationEvIds.length > 0 || bimodalMetricEvIds.length > 0 || latencyMetricEvIds.length > 0,
|
|
96576
|
+
alertSuggestedCategories
|
|
95651
96577
|
});
|
|
95652
96578
|
const validated = validateHypotheses(hyps, evidence2);
|
|
95653
96579
|
const causeChains = buildCauseChains(validated, evidence2, graph, label);
|
|
@@ -96253,13 +97179,26 @@ async function investigate(input, deps) {
|
|
|
96253
97179
|
query: hint
|
|
96254
97180
|
});
|
|
96255
97181
|
report.sourceStatus = buildRuntimeSourceStatus(evidence2, connectorFlags);
|
|
96256
|
-
const
|
|
96257
|
-
|
|
97182
|
+
const persisted = await persist(db, input, report);
|
|
97183
|
+
const persistedId = persisted?.id ?? null;
|
|
97184
|
+
if (persistedId !== null) report.id = persistedId;
|
|
96258
97185
|
report.persisted = persistedId !== null;
|
|
96259
97186
|
if (persistedId !== null) {
|
|
96260
97187
|
const tags = deriveTags(report);
|
|
96261
97188
|
report.similarIncidents = await recallSimilar(db, tags, persistedId, input.repo ?? null);
|
|
96262
97189
|
await storeIncidentMemory(db, persistedId, report);
|
|
97190
|
+
const store = deps.store;
|
|
97191
|
+
if (store !== void 0 && autoInvestigationMemoryEnabled()) {
|
|
97192
|
+
try {
|
|
97193
|
+
const audit = {
|
|
97194
|
+
actor: { kind: "system" },
|
|
97195
|
+
note: "auto: investigation memory (HOR-432)"
|
|
97196
|
+
};
|
|
97197
|
+
const reportTime = persisted?.createdAt ?? /* @__PURE__ */ new Date();
|
|
97198
|
+
await captureInvestigationMemory(store, persistedId, report, reportTime, audit);
|
|
97199
|
+
} catch {
|
|
97200
|
+
}
|
|
97201
|
+
}
|
|
96263
97202
|
}
|
|
96264
97203
|
if (persistedId !== null) {
|
|
96265
97204
|
try {
|
|
@@ -96303,7 +97242,7 @@ async function persist(db, input, report) {
|
|
|
96303
97242
|
incidentInput: input,
|
|
96304
97243
|
status: "open",
|
|
96305
97244
|
summary: report.summary
|
|
96306
|
-
}).returning({ id: investigations.id });
|
|
97245
|
+
}).returning({ id: investigations.id, createdAt: investigations.createdAt });
|
|
96307
97246
|
const row = inserted[0];
|
|
96308
97247
|
if (!row) return null;
|
|
96309
97248
|
const investigationId = row.id;
|
|
@@ -96347,7 +97286,7 @@ async function persist(db, input, report) {
|
|
|
96347
97286
|
}))
|
|
96348
97287
|
);
|
|
96349
97288
|
}
|
|
96350
|
-
return investigationId;
|
|
97289
|
+
return { id: investigationId, createdAt: row.createdAt ?? null };
|
|
96351
97290
|
} catch {
|
|
96352
97291
|
return null;
|
|
96353
97292
|
}
|
|
@@ -97056,225 +97995,6 @@ function blastRadiusToJSON(r) {
|
|
|
97056
97995
|
return JSON.stringify(r, null, 2);
|
|
97057
97996
|
}
|
|
97058
97997
|
|
|
97059
|
-
// ../../packages/engine/src/memory-store.ts
|
|
97060
|
-
init_cjs_shims();
|
|
97061
|
-
var HIDDEN_STATUSES = [
|
|
97062
|
-
"forgotten",
|
|
97063
|
-
"deprecated",
|
|
97064
|
-
"contradicted"
|
|
97065
|
-
];
|
|
97066
|
-
function tokenize2(text2) {
|
|
97067
|
-
const seen = /* @__PURE__ */ new Set();
|
|
97068
|
-
const out2 = [];
|
|
97069
|
-
for (const raw of text2.toLowerCase().split(/[^a-z0-9]+/)) {
|
|
97070
|
-
if (raw !== "" && !seen.has(raw)) {
|
|
97071
|
-
seen.add(raw);
|
|
97072
|
-
out2.push(raw);
|
|
97073
|
-
}
|
|
97074
|
-
}
|
|
97075
|
-
return out2;
|
|
97076
|
-
}
|
|
97077
|
-
function scopeSpecificity(scope) {
|
|
97078
|
-
if (scope.startsWith("symbol:")) return 3;
|
|
97079
|
-
if (scope.startsWith("module:")) return 2;
|
|
97080
|
-
if (scope === "repo") return 1;
|
|
97081
|
-
return 0;
|
|
97082
|
-
}
|
|
97083
|
-
var NoopVectorIndex = class {
|
|
97084
|
-
entries = /* @__PURE__ */ new Map();
|
|
97085
|
-
async upsert(i3) {
|
|
97086
|
-
this.entries.set(i3.memoryId, {
|
|
97087
|
-
memoryId: i3.memoryId,
|
|
97088
|
-
repo: i3.repo,
|
|
97089
|
-
scope: i3.scope,
|
|
97090
|
-
tokens: tokenize2(i3.claim)
|
|
97091
|
-
});
|
|
97092
|
-
}
|
|
97093
|
-
async search(i3) {
|
|
97094
|
-
const repo = i3.repo.trim();
|
|
97095
|
-
if (repo === "") return [];
|
|
97096
|
-
const queryTokens = tokenize2(i3.query);
|
|
97097
|
-
if (queryTokens.length === 0) return [];
|
|
97098
|
-
const scored = [...this.entries.values()].filter((e) => e.repo === repo).map((e) => ({
|
|
97099
|
-
memoryId: e.memoryId,
|
|
97100
|
-
score: tagOverlap(queryTokens, e.tokens),
|
|
97101
|
-
specificity: scopeSpecificity(e.scope)
|
|
97102
|
-
})).filter((h3) => h3.score > 0).sort(
|
|
97103
|
-
(a2, b5) => b5.score - a2.score || b5.specificity - a2.specificity || (a2.memoryId < b5.memoryId ? -1 : a2.memoryId > b5.memoryId ? 1 : 0)
|
|
97104
|
-
);
|
|
97105
|
-
const limit = Number.isFinite(i3.limit) && i3.limit > 0 ? Math.floor(i3.limit) : scored.length;
|
|
97106
|
-
return scored.slice(0, limit).map((h3) => ({ memoryId: h3.memoryId, score: h3.score }));
|
|
97107
|
-
}
|
|
97108
|
-
async remove(memoryId) {
|
|
97109
|
-
this.entries.delete(memoryId);
|
|
97110
|
-
}
|
|
97111
|
-
};
|
|
97112
|
-
|
|
97113
|
-
// ../../packages/engine/src/memory-detect.ts
|
|
97114
|
-
init_cjs_shims();
|
|
97115
|
-
var import_node_crypto3 = require("crypto");
|
|
97116
|
-
var TAG_OVERLAP_MIN = 0.6;
|
|
97117
|
-
var CLAIM_JACCARD_MIN = 0.6;
|
|
97118
|
-
var MIN_CLAIM_TOKENS = 4;
|
|
97119
|
-
var INCIDENT_DERIVED_KINDS2 = /* @__PURE__ */ new Set([
|
|
97120
|
-
"confirmed-outcome",
|
|
97121
|
-
"incident-pattern"
|
|
97122
|
-
]);
|
|
97123
|
-
function usableSignature(sig) {
|
|
97124
|
-
const s5 = (sig ?? "").trim();
|
|
97125
|
-
if (s5 === "" || /^\|*$/.test(s5)) return null;
|
|
97126
|
-
const components = s5.split(/[|,]/).map((c4) => c4.trim()).filter((c4) => c4 !== "");
|
|
97127
|
-
if (components.length === 0) return null;
|
|
97128
|
-
if (components.every((c4) => isGenericTag(c4))) return null;
|
|
97129
|
-
return s5;
|
|
97130
|
-
}
|
|
97131
|
-
function sharedNonGenericTags(a2, b5) {
|
|
97132
|
-
const setB = new Set(b5);
|
|
97133
|
-
const seen = /* @__PURE__ */ new Set();
|
|
97134
|
-
const out2 = [];
|
|
97135
|
-
for (const t2 of a2) {
|
|
97136
|
-
if (setB.has(t2) && !isGenericTag(t2) && !seen.has(t2)) {
|
|
97137
|
-
seen.add(t2);
|
|
97138
|
-
out2.push(t2);
|
|
97139
|
-
}
|
|
97140
|
-
}
|
|
97141
|
-
return out2;
|
|
97142
|
-
}
|
|
97143
|
-
function recurrenceEdgeId(idA, idB) {
|
|
97144
|
-
const [lo, hi] = idA <= idB ? [idA, idB] : [idB, idA];
|
|
97145
|
-
const h3 = (0, import_node_crypto3.createHash)("sha256").update(`recurs-with\0${lo}\0${hi}`).digest("hex");
|
|
97146
|
-
return `lnk_recurs_${h3.slice(0, 32)}`;
|
|
97147
|
-
}
|
|
97148
|
-
function normalizeCause(s5) {
|
|
97149
|
-
return (s5 ?? "").trim().toLowerCase().replace(/\s+/g, " ");
|
|
97150
|
-
}
|
|
97151
|
-
function outcomeConflict(a2, b5) {
|
|
97152
|
-
const opposite = a2.resolved === "yes" && b5.resolved === "no" || a2.resolved === "no" && b5.resolved === "yes";
|
|
97153
|
-
if (opposite) {
|
|
97154
|
-
return `outcome verdicts disagree (${a2.resolved} vs ${b5.resolved})`;
|
|
97155
|
-
}
|
|
97156
|
-
const ca = normalizeCause(a2.confirmedCause);
|
|
97157
|
-
const cb = normalizeCause(b5.confirmedCause);
|
|
97158
|
-
if (ca !== "" && cb !== "" && ca !== cb) {
|
|
97159
|
-
return `confirmed causes differ ("${a2.confirmedCause}" vs "${b5.confirmedCause}")`;
|
|
97160
|
-
}
|
|
97161
|
-
return null;
|
|
97162
|
-
}
|
|
97163
|
-
function recurrenceReason(a2, b5) {
|
|
97164
|
-
const sigA = usableSignature(a2.signature);
|
|
97165
|
-
const sigB = usableSignature(b5.signature);
|
|
97166
|
-
if (sigA !== null && sigB !== null && sigA === sigB) {
|
|
97167
|
-
return `identical incident signature "${sigA}"`;
|
|
97168
|
-
}
|
|
97169
|
-
const tagsA = a2.tags ?? [];
|
|
97170
|
-
const tagsB = b5.tags ?? [];
|
|
97171
|
-
const shared = sharedNonGenericTags(tagsA, tagsB);
|
|
97172
|
-
const overlap = tagOverlap([...tagsA], [...tagsB]);
|
|
97173
|
-
if (overlap >= TAG_OVERLAP_MIN && shared.length >= 1) {
|
|
97174
|
-
return `tag overlap ${overlap.toFixed(2)} on [${shared.join(", ")}]`;
|
|
97175
|
-
}
|
|
97176
|
-
if (sigA === null && sigB === null && shared.length >= 1) {
|
|
97177
|
-
const ta = tokenize2(a2.claim);
|
|
97178
|
-
const tb = tokenize2(b5.claim);
|
|
97179
|
-
if (ta.length >= MIN_CLAIM_TOKENS && tb.length >= MIN_CLAIM_TOKENS) {
|
|
97180
|
-
const j3 = tagOverlap(ta, tb);
|
|
97181
|
-
if (j3 >= CLAIM_JACCARD_MIN) {
|
|
97182
|
-
return `claim similarity ${j3.toFixed(2)} corroborated by [${shared.join(", ")}]`;
|
|
97183
|
-
}
|
|
97184
|
-
}
|
|
97185
|
-
}
|
|
97186
|
-
return null;
|
|
97187
|
-
}
|
|
97188
|
-
function edgeKey(rel, from, to) {
|
|
97189
|
-
if (rel === "recurs-with" || rel === "contradicts") {
|
|
97190
|
-
const [lo, hi] = from <= to ? [from, to] : [to, from];
|
|
97191
|
-
return `${rel}|${lo}|${hi}`;
|
|
97192
|
-
}
|
|
97193
|
-
return `${rel}|${from}|${to}`;
|
|
97194
|
-
}
|
|
97195
|
-
async function detectMemoryEdges(store, opts, deps = {}) {
|
|
97196
|
-
const repo = opts.repo.trim();
|
|
97197
|
-
if (repo === "") return [];
|
|
97198
|
-
const items = await store.query({ repo, limit: opts.limit ?? 1e3 });
|
|
97199
|
-
const candidates = items.filter((i3) => INCIDENT_DERIVED_KINDS2.has(i3.kind));
|
|
97200
|
-
if (candidates.length < 2) return [];
|
|
97201
|
-
const existing = /* @__PURE__ */ new Set();
|
|
97202
|
-
const investigationOf = /* @__PURE__ */ new Map();
|
|
97203
|
-
for (const item of candidates) {
|
|
97204
|
-
const out2 = await store.links(item.id, { direction: "out" });
|
|
97205
|
-
for (const l2 of out2) {
|
|
97206
|
-
if (l2.toKind === "memory") {
|
|
97207
|
-
existing.add(edgeKey(l2.rel, l2.fromMemoryId, l2.toRef));
|
|
97208
|
-
} else if (l2.toKind === "incident" && l2.rel === "about-incident") {
|
|
97209
|
-
if (!investigationOf.has(item.id)) investigationOf.set(item.id, l2.toRef);
|
|
97210
|
-
}
|
|
97211
|
-
}
|
|
97212
|
-
}
|
|
97213
|
-
const proposals = [];
|
|
97214
|
-
const outcomeCache = /* @__PURE__ */ new Map();
|
|
97215
|
-
const resolveOutcome = async (investigationId) => {
|
|
97216
|
-
if (deps.outcomeFor === void 0) return null;
|
|
97217
|
-
if (outcomeCache.has(investigationId)) return outcomeCache.get(investigationId) ?? null;
|
|
97218
|
-
let v4 = null;
|
|
97219
|
-
try {
|
|
97220
|
-
v4 = await deps.outcomeFor(investigationId);
|
|
97221
|
-
} catch {
|
|
97222
|
-
v4 = null;
|
|
97223
|
-
}
|
|
97224
|
-
outcomeCache.set(investigationId, v4);
|
|
97225
|
-
return v4;
|
|
97226
|
-
};
|
|
97227
|
-
for (let i3 = 0; i3 < candidates.length; i3++) {
|
|
97228
|
-
const a2 = candidates[i3];
|
|
97229
|
-
for (let j3 = i3 + 1; j3 < candidates.length; j3++) {
|
|
97230
|
-
const b5 = candidates[j3];
|
|
97231
|
-
if (!existing.has(edgeKey("recurs-with", a2.id, b5.id))) {
|
|
97232
|
-
const reason = recurrenceReason(a2, b5);
|
|
97233
|
-
if (reason !== null) {
|
|
97234
|
-
const [lo, hi] = a2.id <= b5.id ? [a2.id, b5.id] : [b5.id, a2.id];
|
|
97235
|
-
proposals.push({
|
|
97236
|
-
id: recurrenceEdgeId(a2.id, b5.id),
|
|
97237
|
-
fromMemoryId: lo,
|
|
97238
|
-
toMemoryId: hi,
|
|
97239
|
-
rel: "recurs-with",
|
|
97240
|
-
detection: "auto:recurrence",
|
|
97241
|
-
reason
|
|
97242
|
-
});
|
|
97243
|
-
}
|
|
97244
|
-
}
|
|
97245
|
-
if (deps.outcomeFor !== void 0 && a2.kind === "confirmed-outcome" && b5.kind === "confirmed-outcome" && !existing.has(edgeKey("contradicts", a2.id, b5.id))) {
|
|
97246
|
-
const sigA = usableSignature(a2.signature);
|
|
97247
|
-
const sigB = usableSignature(b5.signature);
|
|
97248
|
-
const invA = investigationOf.get(a2.id);
|
|
97249
|
-
const invB = investigationOf.get(b5.id);
|
|
97250
|
-
if (sigA !== null && sigA === sigB && invA !== void 0 && invB !== void 0) {
|
|
97251
|
-
const oa = await resolveOutcome(invA);
|
|
97252
|
-
const ob = await resolveOutcome(invB);
|
|
97253
|
-
if (oa !== null && ob !== null) {
|
|
97254
|
-
const reason = outcomeConflict(oa, ob);
|
|
97255
|
-
if (reason !== null) {
|
|
97256
|
-
const [lo, hi] = a2.id <= b5.id ? [a2.id, b5.id] : [b5.id, a2.id];
|
|
97257
|
-
const invLo = lo === a2.id ? invA : invB;
|
|
97258
|
-
const invHi = lo === a2.id ? invB : invA;
|
|
97259
|
-
proposals.push({
|
|
97260
|
-
id: "",
|
|
97261
|
-
// directional — the store mints the id
|
|
97262
|
-
fromMemoryId: lo,
|
|
97263
|
-
toMemoryId: hi,
|
|
97264
|
-
rel: "contradicts",
|
|
97265
|
-
detection: "auto:contradiction",
|
|
97266
|
-
reason,
|
|
97267
|
-
detail: { investigationIds: [invLo, invHi] }
|
|
97268
|
-
});
|
|
97269
|
-
}
|
|
97270
|
-
}
|
|
97271
|
-
}
|
|
97272
|
-
}
|
|
97273
|
-
}
|
|
97274
|
-
}
|
|
97275
|
-
return proposals;
|
|
97276
|
-
}
|
|
97277
|
-
|
|
97278
97998
|
// ../../packages/engine/src/memory-recall.ts
|
|
97279
97999
|
init_cjs_shims();
|
|
97280
98000
|
var import_node_crypto4 = require("crypto");
|
|
@@ -101915,8 +102635,13 @@ async function runIndex(opts) {
|
|
|
101915
102635
|
console.error(import_picocolors6.default.red(` ${err2.message}`));
|
|
101916
102636
|
return 1;
|
|
101917
102637
|
}
|
|
101918
|
-
|
|
101919
|
-
|
|
102638
|
+
const staleReason = isAnalyzed(root) ? indexNeedsReanalyze(root) : null;
|
|
102639
|
+
if (!isAnalyzed(root) || staleReason) {
|
|
102640
|
+
console.log(
|
|
102641
|
+
import_picocolors6.default.dim(
|
|
102642
|
+
staleReason ? ` existing index is stale (${staleReason}) \u2014 re-analyzing with source-intelligence backend\u2026` : " analyzing with source-intelligence backend (first time \u2014 this can take a while)\u2026"
|
|
102643
|
+
)
|
|
102644
|
+
);
|
|
101920
102645
|
try {
|
|
101921
102646
|
await analyzeRepo(root);
|
|
101922
102647
|
} catch (err2) {
|
|
@@ -115375,6 +116100,9 @@ async function runOneInvestigation(input, ctx, opts = {}) {
|
|
|
115375
116100
|
{
|
|
115376
116101
|
code: ctx.code,
|
|
115377
116102
|
db: ctx.dbHandle.db,
|
|
116103
|
+
// HOR-432 — the authored-memory store so every investigation auto-captures a recurrence-aware
|
|
116104
|
+
// memory. Best-effort + CONTEXT-ONLY in the engine (never feeds scoring, never blocks delivery).
|
|
116105
|
+
store: createLocalMemoryStore(ctx.dbHandle.db),
|
|
115378
116106
|
logs: ctx.logs,
|
|
115379
116107
|
mongo: ctx.mongo,
|
|
115380
116108
|
postgres: ctx.postgres,
|
|
@@ -117432,6 +118160,39 @@ function createCloudMemoryStore(client, cfg) {
|
|
|
117432
118160
|
tags: null
|
|
117433
118161
|
};
|
|
117434
118162
|
},
|
|
118163
|
+
// HOR-432 consolidation mirror: refresh the scalar fields the cloud accepts (claim/confidence).
|
|
118164
|
+
// PRIVACY: `payload` NEVER leaves the device (it can stash a vector) — it is intentionally dropped
|
|
118165
|
+
// here, exactly like `add`/`toSyncInput`. The return is the server echo (or a synthesized stub) and
|
|
118166
|
+
// is only consumed by the dual-write wrapper, which discards it; reads always come from local.
|
|
118167
|
+
async update(id, patch, _opts) {
|
|
118168
|
+
const input = { clientId: id };
|
|
118169
|
+
if (patch.claim !== void 0) input.claim = patch.claim;
|
|
118170
|
+
if (patch.confidence !== void 0) input.confidence = patch.confidence;
|
|
118171
|
+
const res = await client.syncMemoryItems(projectId, { items: [input] });
|
|
118172
|
+
const record2 = res.items?.find((r) => r.clientId === id);
|
|
118173
|
+
if (record2) return fromWire(record2, repoFallback);
|
|
118174
|
+
return {
|
|
118175
|
+
id,
|
|
118176
|
+
kind: "investigation",
|
|
118177
|
+
claim: patch.claim ?? "",
|
|
118178
|
+
scope: "repo",
|
|
118179
|
+
source: "investigation",
|
|
118180
|
+
evidence: [],
|
|
118181
|
+
confidence: patch.confidence ?? 0,
|
|
118182
|
+
status: "fresh",
|
|
118183
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
118184
|
+
lastVerifiedAt: null,
|
|
118185
|
+
lastVerifiedHash: null,
|
|
118186
|
+
orgId: null,
|
|
118187
|
+
workspaceId: null,
|
|
118188
|
+
repo: repoFallback,
|
|
118189
|
+
userId: null,
|
|
118190
|
+
visibility: "private",
|
|
118191
|
+
payload: null,
|
|
118192
|
+
signature: null,
|
|
118193
|
+
tags: null
|
|
118194
|
+
};
|
|
118195
|
+
},
|
|
117435
118196
|
async get(id) {
|
|
117436
118197
|
try {
|
|
117437
118198
|
return await findInList(id);
|
|
@@ -117539,6 +118300,14 @@ function dualWriteMemoryStore(local, cloud, onCloudError = () => {
|
|
|
117539
118300
|
});
|
|
117540
118301
|
return row;
|
|
117541
118302
|
},
|
|
118303
|
+
async update(id, patch, opts) {
|
|
118304
|
+
const row = await local.update(id, patch, opts);
|
|
118305
|
+
await mirror(async () => {
|
|
118306
|
+
await cloud.update(id, patch, opts);
|
|
118307
|
+
await mirrorLatestAudit(id);
|
|
118308
|
+
});
|
|
118309
|
+
return row;
|
|
118310
|
+
},
|
|
117542
118311
|
async setStatus(id, status, audit) {
|
|
117543
118312
|
await local.setStatus(id, status, audit);
|
|
117544
118313
|
await mirror(async () => {
|
|
@@ -118932,6 +119701,8 @@ function anomalyLabel2(anomaly) {
|
|
|
118932
119701
|
return "QUEUE-GROWTH";
|
|
118933
119702
|
case "change":
|
|
118934
119703
|
return "CHANGE";
|
|
119704
|
+
case "bimodal-population":
|
|
119705
|
+
return "BIMODAL-POPULATION";
|
|
118935
119706
|
case "none":
|
|
118936
119707
|
return "OK";
|
|
118937
119708
|
}
|