@hivelore/core 0.33.0 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +130 -2
- package/dist/index.js +335 -44
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -250,15 +250,20 @@ async function loadMemory(filePath) {
|
|
|
250
250
|
return { memory: parseMemory(raw), filePath };
|
|
251
251
|
}
|
|
252
252
|
async function loadMemoriesFromDir(dir) {
|
|
253
|
+
return (await loadMemoriesFromDirDetailed(dir)).loaded;
|
|
254
|
+
}
|
|
255
|
+
async function loadMemoriesFromDirDetailed(dir) {
|
|
253
256
|
const files = await listMarkdownFilesRecursive(dir);
|
|
254
|
-
const
|
|
257
|
+
const loaded = [];
|
|
258
|
+
const invalid = [];
|
|
255
259
|
for (const file of files) {
|
|
256
260
|
try {
|
|
257
|
-
|
|
258
|
-
} catch {
|
|
261
|
+
loaded.push(await loadMemory(file));
|
|
262
|
+
} catch (err) {
|
|
263
|
+
invalid.push({ filePath: file, error: err instanceof Error ? err.message.split("\n")[0] : String(err) });
|
|
259
264
|
}
|
|
260
265
|
}
|
|
261
|
-
return
|
|
266
|
+
return { loaded, invalid };
|
|
262
267
|
}
|
|
263
268
|
|
|
264
269
|
// src/search.ts
|
|
@@ -886,7 +891,7 @@ async function appendPreventionEvent(paths, event) {
|
|
|
886
891
|
await mkdir2(path6.dirname(file), { recursive: true });
|
|
887
892
|
await appendFile(file, JSON.stringify(event) + "\n", "utf8");
|
|
888
893
|
}
|
|
889
|
-
async function recordPreventionHits(paths, firedIds, source, now = /* @__PURE__ */ new Date()) {
|
|
894
|
+
async function recordPreventionHits(paths, firedIds, source, now = /* @__PURE__ */ new Date(), details = {}) {
|
|
890
895
|
const unique = [...new Set(firedIds)].filter(Boolean);
|
|
891
896
|
if (unique.length === 0) return [];
|
|
892
897
|
const usage = await loadUsageIndex(paths).catch(() => null);
|
|
@@ -900,11 +905,68 @@ async function recordPreventionHits(paths, firedIds, source, now = /* @__PURE__
|
|
|
900
905
|
});
|
|
901
906
|
const at = now.toISOString();
|
|
902
907
|
for (const id of recordedIds) {
|
|
903
|
-
await appendPreventionEvent(paths, { at, id, source }).catch(() => {
|
|
908
|
+
await appendPreventionEvent(paths, { at, id, source, ...details[id] }).catch(() => {
|
|
904
909
|
});
|
|
905
910
|
}
|
|
906
911
|
return recordedIds;
|
|
907
912
|
}
|
|
913
|
+
function buildPreventionReceipt(events, memories, usage, options) {
|
|
914
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
915
|
+
const sinceMs = options.since.getTime();
|
|
916
|
+
const nowMs = now.getTime();
|
|
917
|
+
const windowMs = Math.max(1, nowMs - sinceMs);
|
|
918
|
+
const previousSince = sinceMs - windowMs;
|
|
919
|
+
const byId = new Map(memories.map((m) => [m.memory.frontmatter.id, m]));
|
|
920
|
+
const current = events.filter((e) => {
|
|
921
|
+
const at = Date.parse(e.at);
|
|
922
|
+
return Number.isFinite(at) && at >= sinceMs && at <= nowMs;
|
|
923
|
+
});
|
|
924
|
+
const previousTotal = events.filter((e) => {
|
|
925
|
+
const at = Date.parse(e.at);
|
|
926
|
+
return Number.isFinite(at) && at >= previousSince && at < sinceMs;
|
|
927
|
+
}).length;
|
|
928
|
+
const rows = current.map((event) => {
|
|
929
|
+
const loaded = byId.get(event.id);
|
|
930
|
+
const sensor = loaded?.memory.frontmatter.sensor;
|
|
931
|
+
return {
|
|
932
|
+
at: event.at,
|
|
933
|
+
id: event.id,
|
|
934
|
+
title: titleFromMemory(loaded) || event.id,
|
|
935
|
+
source: event.source,
|
|
936
|
+
kind: event.kind ?? sensor?.kind ?? (event.source === "sensor" ? "regex" : null),
|
|
937
|
+
stage: event.stage ?? null,
|
|
938
|
+
exit_code: event.exit_code ?? null,
|
|
939
|
+
message: sensor?.message ?? null
|
|
940
|
+
};
|
|
941
|
+
}).sort((a, b) => b.at.localeCompare(a.at));
|
|
942
|
+
const preventedCountTotal = Object.values(usage.by_id).reduce((sum, item) => sum + item.prevented_count, 0);
|
|
943
|
+
return {
|
|
944
|
+
generated_at: now.toISOString(),
|
|
945
|
+
since: options.since.toISOString(),
|
|
946
|
+
window_days: Math.max(1, Math.round(windowMs / MS_PER_DAY2)),
|
|
947
|
+
total: rows.length,
|
|
948
|
+
previous_total: previousTotal,
|
|
949
|
+
prevented_count_total: preventedCountTotal,
|
|
950
|
+
trend: computePreventionTrend(events, now),
|
|
951
|
+
events: rows
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
function renderPreventionReceipt(receipt) {
|
|
955
|
+
const lines = [
|
|
956
|
+
`Hivelore prevention receipt \u2014 last ${receipt.window_days} days`,
|
|
957
|
+
` ${receipt.total} repeat mistake${receipt.total === 1 ? "" : "s"} refused before ${receipt.total === 1 ? "it" : "they"} reached review.`
|
|
958
|
+
];
|
|
959
|
+
for (const row of receipt.events) {
|
|
960
|
+
const kind = row.kind ? `${row.kind} sensor` : row.source;
|
|
961
|
+
const exit = row.exit_code === null ? "" : `, exit ${row.exit_code}`;
|
|
962
|
+
const stage = row.stage ? ` \u2014 caught at ${row.stage}` : "";
|
|
963
|
+
lines.push(` \u2717\u2192\u2713 ${row.at.slice(0, 10)} ${row.id.padEnd(32)} (${kind}${exit}${stage})`);
|
|
964
|
+
}
|
|
965
|
+
lines.push(
|
|
966
|
+
` Trend: ${receipt.total} this window vs ${receipt.previous_total} previous window (${receipt.total <= receipt.previous_total ? "recurrences declining" : "recurrences rising"}).`
|
|
967
|
+
);
|
|
968
|
+
return lines.join("\n");
|
|
969
|
+
}
|
|
908
970
|
async function loadPreventionEvents(paths) {
|
|
909
971
|
const file = preventionLogPath(paths);
|
|
910
972
|
if (!existsSync4(file)) return [];
|
|
@@ -1885,7 +1947,7 @@ function childIsKeyword(node, keyword) {
|
|
|
1885
1947
|
|
|
1886
1948
|
// src/code-map.ts
|
|
1887
1949
|
var CODE_MAP_FILE = "code-map.json";
|
|
1888
|
-
var
|
|
1950
|
+
var CODE_MAP_DEFAULT_INCLUDE = [
|
|
1889
1951
|
".ts",
|
|
1890
1952
|
".tsx",
|
|
1891
1953
|
".js",
|
|
@@ -1903,7 +1965,7 @@ var DEFAULT_INCLUDE = [
|
|
|
1903
1965
|
".cs",
|
|
1904
1966
|
".php"
|
|
1905
1967
|
];
|
|
1906
|
-
var
|
|
1968
|
+
var CODE_MAP_DEFAULT_EXCLUDE = [
|
|
1907
1969
|
"node_modules",
|
|
1908
1970
|
"dist",
|
|
1909
1971
|
"build",
|
|
@@ -1940,8 +2002,8 @@ async function saveCodeMap(paths, map) {
|
|
|
1940
2002
|
await writeFile3(file, JSON.stringify(map, null, 2), "utf8");
|
|
1941
2003
|
}
|
|
1942
2004
|
async function buildCodeMap(root, options = {}) {
|
|
1943
|
-
const include = new Set(options.includeExtensions ??
|
|
1944
|
-
const exclude = new Set(options.excludeDirs ??
|
|
2005
|
+
const include = new Set(options.includeExtensions ?? CODE_MAP_DEFAULT_INCLUDE);
|
|
2006
|
+
const exclude = new Set(options.excludeDirs ?? CODE_MAP_DEFAULT_EXCLUDE);
|
|
1945
2007
|
const files = {};
|
|
1946
2008
|
for await (const abs of collectSourceFiles(root, include, exclude, options.includeUntracked)) {
|
|
1947
2009
|
const rel = path8.relative(root, abs).replace(/\\/g, "/");
|
|
@@ -1959,8 +2021,8 @@ async function buildCodeMap(root, options = {}) {
|
|
|
1959
2021
|
};
|
|
1960
2022
|
}
|
|
1961
2023
|
async function countSourceFilesOnDisk(root, options = {}) {
|
|
1962
|
-
const include = new Set(
|
|
1963
|
-
const exclude = /* @__PURE__ */ new Set([...
|
|
2024
|
+
const include = new Set(CODE_MAP_DEFAULT_INCLUDE);
|
|
2025
|
+
const exclude = /* @__PURE__ */ new Set([...CODE_MAP_DEFAULT_EXCLUDE, ...options.excludeDirs ?? []]);
|
|
1964
2026
|
let count = 0;
|
|
1965
2027
|
for await (const _file of walkSourceFiles(root, include, exclude)) count++;
|
|
1966
2028
|
return count;
|
|
@@ -3777,10 +3839,10 @@ function sensorPatternBrittleness(pattern) {
|
|
|
3777
3839
|
function normalizeProjectPath(value) {
|
|
3778
3840
|
return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^[ab]\//, "").replace(/\/+$/g, "");
|
|
3779
3841
|
}
|
|
3780
|
-
function sensorAppliesToPath(sensor, anchorPaths,
|
|
3842
|
+
function sensorAppliesToPath(sensor, anchorPaths, path20) {
|
|
3781
3843
|
const scopes = sensor.paths.length > 0 ? sensor.paths : anchorPaths;
|
|
3782
3844
|
if (scopes.length === 0) return true;
|
|
3783
|
-
const target = normalizeProjectPath(
|
|
3845
|
+
const target = normalizeProjectPath(path20);
|
|
3784
3846
|
return scopes.some((rawScope) => {
|
|
3785
3847
|
const scope = normalizeProjectPath(rawScope);
|
|
3786
3848
|
if (!scope) return false;
|
|
@@ -3973,6 +4035,142 @@ function addedLinesFromDiff(diff) {
|
|
|
3973
4035
|
return diff.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).map((l) => l.slice(1)).join("\n");
|
|
3974
4036
|
}
|
|
3975
4037
|
|
|
4038
|
+
// src/sensor-ledger.ts
|
|
4039
|
+
import { createHash as createHash2 } from "crypto";
|
|
4040
|
+
import { existsSync as existsSync15, readFileSync as readFileSync2 } from "fs";
|
|
4041
|
+
import { appendFile as appendFile4, mkdir as mkdir11, readFile as readFile14, rename, writeFile as writeFile9 } from "fs/promises";
|
|
4042
|
+
import path17 from "path";
|
|
4043
|
+
var MAX_LINES = 1e4;
|
|
4044
|
+
var RETAINED_LINES = 8e3;
|
|
4045
|
+
var DAY_MS = 864e5;
|
|
4046
|
+
function sensorLedgerPath(paths) {
|
|
4047
|
+
return path17.join(paths.runtimeDir, "enforcement", "sensor-ledger.ndjson");
|
|
4048
|
+
}
|
|
4049
|
+
function isEvaluation(value) {
|
|
4050
|
+
if (!value || typeof value !== "object") return false;
|
|
4051
|
+
const v = value;
|
|
4052
|
+
return typeof v.at === "string" && typeof v.memory_id === "string" && (v.kind === "regex" || v.kind === "shell" || v.kind === "test") && (v.stage === "pre-commit" || v.stage === "pre-push" || v.stage === "ci" || v.stage === "manual") && typeof v.head_sha === "string" && typeof v.scope_hash === "string" && (v.outcome === "fired" || v.outcome === "silent" || v.outcome === "unrunnable");
|
|
4053
|
+
}
|
|
4054
|
+
async function appendSensorEvaluations(paths, evaluations) {
|
|
4055
|
+
if (evaluations.length === 0) return;
|
|
4056
|
+
try {
|
|
4057
|
+
const file = sensorLedgerPath(paths);
|
|
4058
|
+
await mkdir11(path17.dirname(file), { recursive: true });
|
|
4059
|
+
await appendFile4(file, evaluations.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf8");
|
|
4060
|
+
const raw = await readFile14(file, "utf8");
|
|
4061
|
+
const lines = raw.split("\n").filter(Boolean);
|
|
4062
|
+
if (lines.length > MAX_LINES) {
|
|
4063
|
+
const temp = `${file}.${process.pid}.tmp`;
|
|
4064
|
+
await writeFile9(temp, lines.slice(-RETAINED_LINES).join("\n") + "\n", "utf8");
|
|
4065
|
+
await rename(temp, file);
|
|
4066
|
+
}
|
|
4067
|
+
} catch {
|
|
4068
|
+
}
|
|
4069
|
+
}
|
|
4070
|
+
async function loadSensorLedger(paths, opts = {}) {
|
|
4071
|
+
try {
|
|
4072
|
+
const file = sensorLedgerPath(paths);
|
|
4073
|
+
if (!existsSync15(file)) return [];
|
|
4074
|
+
const since = opts.since ? Date.parse(opts.since) : Number.NEGATIVE_INFINITY;
|
|
4075
|
+
const raw = await readFile14(file, "utf8");
|
|
4076
|
+
const out = [];
|
|
4077
|
+
for (const line of raw.split("\n")) {
|
|
4078
|
+
if (!line.trim()) continue;
|
|
4079
|
+
try {
|
|
4080
|
+
const parsed = JSON.parse(line);
|
|
4081
|
+
if (!isEvaluation(parsed)) continue;
|
|
4082
|
+
const at = Date.parse(parsed.at);
|
|
4083
|
+
if (!Number.isFinite(at) || at < since) continue;
|
|
4084
|
+
out.push(parsed);
|
|
4085
|
+
} catch {
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
return out;
|
|
4089
|
+
} catch {
|
|
4090
|
+
return [];
|
|
4091
|
+
}
|
|
4092
|
+
}
|
|
4093
|
+
function computeScopeHash(root, scopedFiles) {
|
|
4094
|
+
try {
|
|
4095
|
+
const files = [...new Set(scopedFiles.map((f) => f.replace(/\\/g, "/")))].sort();
|
|
4096
|
+
if (files.length === 0) return "";
|
|
4097
|
+
const hash = createHash2("sha256");
|
|
4098
|
+
let included = 0;
|
|
4099
|
+
for (const rel of files) {
|
|
4100
|
+
const abs = path17.resolve(root, rel);
|
|
4101
|
+
if (!existsSync15(abs)) continue;
|
|
4102
|
+
try {
|
|
4103
|
+
hash.update(rel);
|
|
4104
|
+
hash.update("\0");
|
|
4105
|
+
hash.update(readFileSync2(abs));
|
|
4106
|
+
hash.update("\0");
|
|
4107
|
+
included++;
|
|
4108
|
+
} catch {
|
|
4109
|
+
}
|
|
4110
|
+
}
|
|
4111
|
+
return included === 0 ? "" : hash.digest("hex");
|
|
4112
|
+
} catch {
|
|
4113
|
+
return "";
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4116
|
+
function assessSensorHealth(evaluations, now = /* @__PURE__ */ new Date()) {
|
|
4117
|
+
const cutoff = now.getTime() - 30 * DAY_MS;
|
|
4118
|
+
const byMemory = /* @__PURE__ */ new Map();
|
|
4119
|
+
for (const e of evaluations) {
|
|
4120
|
+
if (e.memory_id === "__gate__" || e.kind !== "shell" && e.kind !== "test") continue;
|
|
4121
|
+
const list = byMemory.get(e.memory_id) ?? [];
|
|
4122
|
+
list.push(e);
|
|
4123
|
+
byMemory.set(e.memory_id, list);
|
|
4124
|
+
}
|
|
4125
|
+
const out = [];
|
|
4126
|
+
for (const [memoryId, all] of byMemory) {
|
|
4127
|
+
all.sort((a, b) => Date.parse(a.at) - Date.parse(b.at));
|
|
4128
|
+
const recent = all.filter((e) => Date.parse(e.at) >= cutoff && Date.parse(e.at) <= now.getTime());
|
|
4129
|
+
const byHash = /* @__PURE__ */ new Map();
|
|
4130
|
+
for (const e of recent) {
|
|
4131
|
+
if (e.outcome === "unrunnable") continue;
|
|
4132
|
+
const list = byHash.get(e.scope_hash) ?? [];
|
|
4133
|
+
list.push(e);
|
|
4134
|
+
byHash.set(e.scope_hash, list);
|
|
4135
|
+
}
|
|
4136
|
+
const flaps = [];
|
|
4137
|
+
for (const [scopeHash, rows] of byHash) {
|
|
4138
|
+
for (let i = 1; i < rows.length; i++) {
|
|
4139
|
+
const previous = rows[i - 1];
|
|
4140
|
+
const current = rows[i];
|
|
4141
|
+
if (previous.outcome !== current.outcome) {
|
|
4142
|
+
flaps.push({ memory_id: memoryId, scope_hash: scopeHash, previous, current });
|
|
4143
|
+
}
|
|
4144
|
+
}
|
|
4145
|
+
}
|
|
4146
|
+
flaps.sort((a, b) => Date.parse(a.current.at) - Date.parse(b.current.at));
|
|
4147
|
+
const runnable = all.filter((e) => e.outcome !== "unrunnable");
|
|
4148
|
+
const span = runnable.length > 1 ? Date.parse(runnable[runnable.length - 1].at) - Date.parse(runnable[0].at) : 0;
|
|
4149
|
+
out.push({
|
|
4150
|
+
memory_id: memoryId,
|
|
4151
|
+
flap_count: flaps.length,
|
|
4152
|
+
flaps,
|
|
4153
|
+
quarantine_pending: flaps.length >= 2,
|
|
4154
|
+
never_fired: runnable.length >= 20 && span >= 30 * DAY_MS && runnable.every((e) => e.outcome === "silent"),
|
|
4155
|
+
evaluation_count: runnable.length
|
|
4156
|
+
});
|
|
4157
|
+
}
|
|
4158
|
+
return out.sort((a, b) => a.memory_id.localeCompare(b.memory_id));
|
|
4159
|
+
}
|
|
4160
|
+
function quarantineNote(at, flapCount) {
|
|
4161
|
+
return `> Quarantined ${at}: oracle flapped ${flapCount}\xD7 on identical inputs \u2014 demoted block\u2192warn. Fix the test, then re-promote with \`hivelore sensors promote <id>\`.`;
|
|
4162
|
+
}
|
|
4163
|
+
function withQuarantineNote(body, at, flapCount) {
|
|
4164
|
+
const without = body.split("\n").filter((line) => !line.startsWith("> Quarantined ")).join("\n").trimEnd();
|
|
4165
|
+
return `${without}
|
|
4166
|
+
|
|
4167
|
+
${quarantineNote(at, flapCount)}
|
|
4168
|
+
`;
|
|
4169
|
+
}
|
|
4170
|
+
function withoutQuarantineNote(body) {
|
|
4171
|
+
return body.split("\n").filter((line) => !line.startsWith("> Quarantined ")).join("\n").replace(/\n{3,}/g, "\n\n").trimEnd() + "\n";
|
|
4172
|
+
}
|
|
4173
|
+
|
|
3976
4174
|
// src/sensor-suggest.ts
|
|
3977
4175
|
var CODE_TOKEN_RE = /`([^`\n]{3,80})`|["']([A-Za-z0-9_.:-]{3,80})["']|\b([A-Za-z][A-Za-z0-9_.:-]{2,79})\b/g;
|
|
3978
4176
|
var SENSOR_STOPWORDS = /* @__PURE__ */ new Set([
|
|
@@ -4420,8 +4618,8 @@ function normalizeFindingSeverity(raw) {
|
|
|
4420
4618
|
return "info";
|
|
4421
4619
|
}
|
|
4422
4620
|
}
|
|
4423
|
-
function findingKey(tool, ruleId,
|
|
4424
|
-
return `${tool}:${ruleId}:${
|
|
4621
|
+
function findingKey(tool, ruleId, path20) {
|
|
4622
|
+
return `${tool}:${ruleId}:${path20}`;
|
|
4425
4623
|
}
|
|
4426
4624
|
function coerceJson(input) {
|
|
4427
4625
|
if (typeof input === "string") {
|
|
@@ -4455,8 +4653,8 @@ function parseSarif(input) {
|
|
|
4455
4653
|
const physical = asRecord(location.physicalLocation);
|
|
4456
4654
|
const artifact = asRecord(physical.artifactLocation);
|
|
4457
4655
|
const region = asRecord(physical.region);
|
|
4458
|
-
const
|
|
4459
|
-
if (!
|
|
4656
|
+
const path20 = typeof artifact.uri === "string" ? normalizeUri(artifact.uri) : "";
|
|
4657
|
+
if (!path20) continue;
|
|
4460
4658
|
const line = typeof region.startLine === "number" ? region.startLine : void 0;
|
|
4461
4659
|
const snippet = typeof asRecord(region.snippet).text === "string" ? asRecord(region.snippet).text.trim() : void 0;
|
|
4462
4660
|
findings.push({
|
|
@@ -4464,10 +4662,10 @@ function parseSarif(input) {
|
|
|
4464
4662
|
ruleId,
|
|
4465
4663
|
message: message.trim(),
|
|
4466
4664
|
severity,
|
|
4467
|
-
path:
|
|
4665
|
+
path: path20,
|
|
4468
4666
|
...line !== void 0 ? { line } : {},
|
|
4469
4667
|
...snippet ? { snippet } : {},
|
|
4470
|
-
key: findingKey(tool, ruleId,
|
|
4668
|
+
key: findingKey(tool, ruleId, path20)
|
|
4471
4669
|
});
|
|
4472
4670
|
}
|
|
4473
4671
|
}
|
|
@@ -4486,17 +4684,17 @@ function parseSonar(input) {
|
|
|
4486
4684
|
(typeof issue.severity === "string" ? issue.severity : void 0) ?? impactSeverity
|
|
4487
4685
|
);
|
|
4488
4686
|
const component = typeof issue.component === "string" ? issue.component : "";
|
|
4489
|
-
const
|
|
4490
|
-
if (!
|
|
4687
|
+
const path20 = componentToPath(component);
|
|
4688
|
+
if (!path20) continue;
|
|
4491
4689
|
const line = typeof issue.line === "number" ? issue.line : void 0;
|
|
4492
4690
|
findings.push({
|
|
4493
4691
|
tool: "sonar",
|
|
4494
4692
|
ruleId,
|
|
4495
4693
|
message,
|
|
4496
4694
|
severity,
|
|
4497
|
-
path:
|
|
4695
|
+
path: path20,
|
|
4498
4696
|
...line !== void 0 ? { line } : {},
|
|
4499
|
-
key: findingKey("sonar", ruleId,
|
|
4697
|
+
key: findingKey("sonar", ruleId, path20)
|
|
4500
4698
|
});
|
|
4501
4699
|
}
|
|
4502
4700
|
return findings;
|
|
@@ -4509,7 +4707,7 @@ function parseEslintJson(input, opts = {}) {
|
|
|
4509
4707
|
const file = asRecord(fileRaw);
|
|
4510
4708
|
const rawPath = typeof file.filePath === "string" ? file.filePath : "";
|
|
4511
4709
|
if (!rawPath) continue;
|
|
4512
|
-
const
|
|
4710
|
+
const path20 = cwd && rawPath.startsWith(cwd) ? rawPath.slice(cwd.length) : rawPath;
|
|
4513
4711
|
for (const msgRaw of asArray(file.messages)) {
|
|
4514
4712
|
const msg = asRecord(msgRaw);
|
|
4515
4713
|
const ruleId = typeof msg.ruleId === "string" && msg.ruleId ? msg.ruleId : "parse-error";
|
|
@@ -4521,9 +4719,9 @@ function parseEslintJson(input, opts = {}) {
|
|
|
4521
4719
|
ruleId,
|
|
4522
4720
|
message,
|
|
4523
4721
|
severity,
|
|
4524
|
-
path:
|
|
4722
|
+
path: path20,
|
|
4525
4723
|
...line !== void 0 ? { line } : {},
|
|
4526
|
-
key: findingKey("eslint", ruleId,
|
|
4724
|
+
key: findingKey("eslint", ruleId, path20)
|
|
4527
4725
|
});
|
|
4528
4726
|
}
|
|
4529
4727
|
}
|
|
@@ -4941,7 +5139,7 @@ function tallyHotFiles(paths, source = "agent") {
|
|
|
4941
5139
|
if (!norm) continue;
|
|
4942
5140
|
counts.set(norm, (counts.get(norm) ?? 0) + 1);
|
|
4943
5141
|
}
|
|
4944
|
-
return [...counts.entries()].map(([
|
|
5142
|
+
return [...counts.entries()].map(([path20, changes]) => ({ path: path20, changes, source })).sort((a, b) => b.changes - a.changes);
|
|
4945
5143
|
}
|
|
4946
5144
|
function mergeHotFiles(a, b) {
|
|
4947
5145
|
const merged = /* @__PURE__ */ new Map();
|
|
@@ -4961,21 +5159,21 @@ function mergeHotFiles(a, b) {
|
|
|
4961
5159
|
}
|
|
4962
5160
|
|
|
4963
5161
|
// src/eval-history.ts
|
|
4964
|
-
import { appendFile as
|
|
4965
|
-
import { existsSync as
|
|
4966
|
-
import
|
|
5162
|
+
import { appendFile as appendFile5, mkdir as mkdir12, readFile as readFile15 } from "fs/promises";
|
|
5163
|
+
import { existsSync as existsSync16 } from "fs";
|
|
5164
|
+
import path18 from "path";
|
|
4967
5165
|
function evalHistoryPath(paths) {
|
|
4968
|
-
return
|
|
5166
|
+
return path18.join(paths.haiveDir, ".cache", "eval-history.jsonl");
|
|
4969
5167
|
}
|
|
4970
5168
|
async function appendEvalHistory(paths, entry) {
|
|
4971
5169
|
const file = evalHistoryPath(paths);
|
|
4972
|
-
await
|
|
4973
|
-
await
|
|
5170
|
+
await mkdir12(path18.dirname(file), { recursive: true });
|
|
5171
|
+
await appendFile5(file, JSON.stringify(entry) + "\n", "utf8");
|
|
4974
5172
|
}
|
|
4975
5173
|
async function loadEvalHistory(paths) {
|
|
4976
5174
|
const file = evalHistoryPath(paths);
|
|
4977
|
-
if (!
|
|
4978
|
-
const raw = await
|
|
5175
|
+
if (!existsSync16(file)) return [];
|
|
5176
|
+
const raw = await readFile15(file, "utf8").catch(() => "");
|
|
4979
5177
|
const out = [];
|
|
4980
5178
|
for (const line of raw.split("\n")) {
|
|
4981
5179
|
const trimmed = line.trim();
|
|
@@ -5119,6 +5317,81 @@ function proposeSeedsFromCommits(commits, limit = 20) {
|
|
|
5119
5317
|
return out;
|
|
5120
5318
|
}
|
|
5121
5319
|
|
|
5320
|
+
// src/gate-miss.ts
|
|
5321
|
+
function planGitWatch(state, headSha) {
|
|
5322
|
+
const next = { last_scanned_sha: headSha };
|
|
5323
|
+
if (!state?.last_scanned_sha) return { action: "initialize", next };
|
|
5324
|
+
if (state.last_scanned_sha === headSha) return { action: "idle", next };
|
|
5325
|
+
return { action: "scan", range: `${state.last_scanned_sha}..${headSha}`, next };
|
|
5326
|
+
}
|
|
5327
|
+
var REVERTED_SHA_RE = /\bThis reverts commit ([0-9a-f]{7,40})\b/i;
|
|
5328
|
+
var BODY_REVERTED_SHA_RE = /^Reverted SHA:\s*([0-9a-f]{7,40})\s*$/im;
|
|
5329
|
+
function revertedShaFromCommit(commit) {
|
|
5330
|
+
return commit.reverted_sha ?? REVERTED_SHA_RE.exec(commit.body ?? "")?.[1] ?? null;
|
|
5331
|
+
}
|
|
5332
|
+
function existingGateMissShas(memories) {
|
|
5333
|
+
const shas = /* @__PURE__ */ new Set();
|
|
5334
|
+
for (const loaded of memories) {
|
|
5335
|
+
if (!loaded.memory.frontmatter.tags.includes("gate-miss")) continue;
|
|
5336
|
+
const sha = BODY_REVERTED_SHA_RE.exec(loaded.memory.body)?.[1];
|
|
5337
|
+
if (sha) shas.add(sha);
|
|
5338
|
+
}
|
|
5339
|
+
return shas;
|
|
5340
|
+
}
|
|
5341
|
+
function gatePassedShas(evaluations) {
|
|
5342
|
+
return new Set(
|
|
5343
|
+
evaluations.filter((e) => e.memory_id === "__gate__" && e.outcome === "silent" && e.head_sha).map((e) => e.head_sha)
|
|
5344
|
+
);
|
|
5345
|
+
}
|
|
5346
|
+
function shaMatches(set, sha) {
|
|
5347
|
+
for (const candidate of set) {
|
|
5348
|
+
if (candidate === sha || candidate.startsWith(sha) || sha.startsWith(candidate)) return true;
|
|
5349
|
+
}
|
|
5350
|
+
return false;
|
|
5351
|
+
}
|
|
5352
|
+
function proposeGateMissDrafts(commits, existingRevertedShas, passedShas) {
|
|
5353
|
+
const seeds = proposeSeedsFromCommits(commits, commits.length);
|
|
5354
|
+
const bySha = new Map(commits.map((commit) => [commit.sha, commit]));
|
|
5355
|
+
const seen = new Set(existingRevertedShas);
|
|
5356
|
+
const out = [];
|
|
5357
|
+
for (const seed of seeds) {
|
|
5358
|
+
const commit = bySha.get(seed.source_sha);
|
|
5359
|
+
if (!commit) continue;
|
|
5360
|
+
const failedSha = revertedShaFromCommit(commit) ?? commit.sha;
|
|
5361
|
+
if (shaMatches(seen, failedSha)) continue;
|
|
5362
|
+
seen.add(failedSha);
|
|
5363
|
+
const gatePassed = shaMatches(passedShas, failedSha);
|
|
5364
|
+
const paths = (commit.files ?? []).slice(0, 8);
|
|
5365
|
+
const base = `# Gate miss: ${seed.what}
|
|
5366
|
+
|
|
5367
|
+
A git ${seed.kind} indicates that a change escaped the existing harness. This is a proposed lesson only; review the actual regression before validating it.
|
|
5368
|
+
|
|
5369
|
+
Reverted SHA: ${failedSha}
|
|
5370
|
+
Revert SHA: ${commit.sha}
|
|
5371
|
+
Subject: ${seed.what}
|
|
5372
|
+
` + (paths.length > 0 ? `Top paths: ${paths.join(", ")}
|
|
5373
|
+
` : "") + `
|
|
5374
|
+
**Why it failed / do NOT use:** ${seed.why_failed}
|
|
5375
|
+
`;
|
|
5376
|
+
const gateLine = gatePassed ? "\nThe gate PASSED this commit \u2014 a validated sensor here upgrades the harness.\n" : "";
|
|
5377
|
+
const candidate = suggestSensorSeed(base, paths);
|
|
5378
|
+
const sensorHint = candidate ? `
|
|
5379
|
+
proposed_sensor_seed: ${JSON.stringify(candidate)}
|
|
5380
|
+
` : "\nproposed_sensor_seed: inspect the revert diff, then author a deterministic candidate with `hivelore sensors propose <id>`.\n";
|
|
5381
|
+
out.push({
|
|
5382
|
+
slug: `gate-miss-${failedSha.slice(0, 12)}`,
|
|
5383
|
+
reverted_sha: failedSha,
|
|
5384
|
+
revert_sha: commit.sha,
|
|
5385
|
+
subject: seed.what,
|
|
5386
|
+
paths,
|
|
5387
|
+
kind: seed.kind,
|
|
5388
|
+
gate_passed: gatePassed,
|
|
5389
|
+
body: base + gateLine + sensorHint
|
|
5390
|
+
});
|
|
5391
|
+
}
|
|
5392
|
+
return out;
|
|
5393
|
+
}
|
|
5394
|
+
|
|
5122
5395
|
// src/seed.ts
|
|
5123
5396
|
var JS_DETECTORS = [
|
|
5124
5397
|
["nestjs", ["@nestjs/core"]],
|
|
@@ -5243,12 +5516,12 @@ ${trimmed}`;
|
|
|
5243
5516
|
}
|
|
5244
5517
|
|
|
5245
5518
|
// src/handoff.ts
|
|
5246
|
-
import { writeFile as
|
|
5247
|
-
import { existsSync as
|
|
5248
|
-
import
|
|
5519
|
+
import { writeFile as writeFile10, readFile as readFile16, stat as stat3 } from "fs/promises";
|
|
5520
|
+
import { existsSync as existsSync17 } from "fs";
|
|
5521
|
+
import path19 from "path";
|
|
5249
5522
|
var HANDOFF_FILENAME = "NEXT.md";
|
|
5250
5523
|
function handoffFilePath(root) {
|
|
5251
|
-
return
|
|
5524
|
+
return path19.join(root, HANDOFF_FILENAME);
|
|
5252
5525
|
}
|
|
5253
5526
|
function buildHandoffMarkdown(data) {
|
|
5254
5527
|
const at = (data.at ?? /* @__PURE__ */ new Date()).toISOString();
|
|
@@ -5297,18 +5570,18 @@ function buildHandoffMarkdown(data) {
|
|
|
5297
5570
|
}
|
|
5298
5571
|
async function writeSessionHandoff(root, data) {
|
|
5299
5572
|
const file = handoffFilePath(root);
|
|
5300
|
-
await
|
|
5573
|
+
await writeFile10(file, buildHandoffMarkdown(data), "utf8");
|
|
5301
5574
|
return file;
|
|
5302
5575
|
}
|
|
5303
5576
|
async function readSessionHandoff(root) {
|
|
5304
5577
|
const file = handoffFilePath(root);
|
|
5305
|
-
if (!
|
|
5306
|
-
const raw = await
|
|
5578
|
+
if (!existsSync17(file)) return null;
|
|
5579
|
+
const raw = await readFile16(file, "utf8").catch(() => "");
|
|
5307
5580
|
return raw.trim() ? raw : null;
|
|
5308
5581
|
}
|
|
5309
5582
|
async function handoffAgeMs(root, now = /* @__PURE__ */ new Date()) {
|
|
5310
5583
|
const file = handoffFilePath(root);
|
|
5311
|
-
if (!
|
|
5584
|
+
if (!existsSync17(file)) return null;
|
|
5312
5585
|
try {
|
|
5313
5586
|
const s = await stat3(file);
|
|
5314
5587
|
return Math.max(0, now.getTime() - s.mtimeMs);
|
|
@@ -5378,6 +5651,8 @@ export {
|
|
|
5378
5651
|
BRIEFING_MARKER_TTL_MS,
|
|
5379
5652
|
BRIEFING_PRESET_DEFAULTS,
|
|
5380
5653
|
CHARS_PER_TOKEN,
|
|
5654
|
+
CODE_MAP_DEFAULT_EXCLUDE,
|
|
5655
|
+
CODE_MAP_DEFAULT_INCLUDE,
|
|
5381
5656
|
CODE_MAP_FILE,
|
|
5382
5657
|
CODE_STOPWORDS,
|
|
5383
5658
|
CONFIG_FILE,
|
|
@@ -5422,10 +5697,12 @@ export {
|
|
|
5422
5697
|
appendEvalHistory,
|
|
5423
5698
|
appendPreventionEvent,
|
|
5424
5699
|
appendRuntimeJournalEntry,
|
|
5700
|
+
appendSensorEvaluations,
|
|
5425
5701
|
appendUsageEvent,
|
|
5426
5702
|
applyConflictResolution,
|
|
5427
5703
|
applyFeedbackAdjustment,
|
|
5428
5704
|
assessBootstrapState,
|
|
5705
|
+
assessSensorHealth,
|
|
5429
5706
|
bridgeMemorySummary,
|
|
5430
5707
|
briefingMarkerPath,
|
|
5431
5708
|
briefingMarkersDir,
|
|
@@ -5436,6 +5713,7 @@ export {
|
|
|
5436
5713
|
buildDocFrequency,
|
|
5437
5714
|
buildFrontmatter,
|
|
5438
5715
|
buildHandoffMarkdown,
|
|
5716
|
+
buildPreventionReceipt,
|
|
5439
5717
|
buildReport,
|
|
5440
5718
|
bumpRead,
|
|
5441
5719
|
classifyMemoryPriority,
|
|
@@ -5452,6 +5730,7 @@ export {
|
|
|
5452
5730
|
computeImpact,
|
|
5453
5731
|
computePreventionTrend,
|
|
5454
5732
|
computeRecurrence,
|
|
5733
|
+
computeScopeHash,
|
|
5455
5734
|
configPath,
|
|
5456
5735
|
contractLockPath,
|
|
5457
5736
|
countSourceFilesOnDisk,
|
|
@@ -5468,6 +5747,7 @@ export {
|
|
|
5468
5747
|
estimateTokens,
|
|
5469
5748
|
evalHistoryPath,
|
|
5470
5749
|
evaluateSkillActivation,
|
|
5750
|
+
existingGateMissShas,
|
|
5471
5751
|
extractActionsBriefBody,
|
|
5472
5752
|
extractReferencedPaths,
|
|
5473
5753
|
extractSensorExamples,
|
|
@@ -5481,6 +5761,7 @@ export {
|
|
|
5481
5761
|
findingBody,
|
|
5482
5762
|
findingToDraft,
|
|
5483
5763
|
firstMemoryOneLine,
|
|
5764
|
+
gatePassedShas,
|
|
5484
5765
|
generateBridges,
|
|
5485
5766
|
getUsage,
|
|
5486
5767
|
globToRegExp,
|
|
@@ -5515,8 +5796,10 @@ export {
|
|
|
5515
5796
|
loadConfigSync,
|
|
5516
5797
|
loadEvalHistory,
|
|
5517
5798
|
loadMemoriesFromDir,
|
|
5799
|
+
loadMemoriesFromDirDetailed,
|
|
5518
5800
|
loadMemory,
|
|
5519
5801
|
loadPreventionEvents,
|
|
5802
|
+
loadSensorLedger,
|
|
5520
5803
|
loadUsageIndex,
|
|
5521
5804
|
looksLikeGenericAdvice,
|
|
5522
5805
|
meetsSeedQualityFloor,
|
|
@@ -5541,13 +5824,16 @@ export {
|
|
|
5541
5824
|
pathsOverlap,
|
|
5542
5825
|
pickSnippetNeedle,
|
|
5543
5826
|
planConflictResolution,
|
|
5827
|
+
planGitWatch,
|
|
5544
5828
|
prepareBridgeData,
|
|
5545
5829
|
preventionLogPath,
|
|
5546
5830
|
priorityRank,
|
|
5547
5831
|
prioritySignals,
|
|
5548
5832
|
projectContextRecentlyEmitted,
|
|
5833
|
+
proposeGateMissDrafts,
|
|
5549
5834
|
proposeSeedsFromCommits,
|
|
5550
5835
|
pullCrossRepoSources,
|
|
5836
|
+
quarantineNote,
|
|
5551
5837
|
queryCodeMap,
|
|
5552
5838
|
rankMemoriesLexical,
|
|
5553
5839
|
readRecentBriefingMarker,
|
|
@@ -5563,11 +5849,13 @@ export {
|
|
|
5563
5849
|
relPathFrom,
|
|
5564
5850
|
renderBootstrapChecklist,
|
|
5565
5851
|
renderCaughtForYou,
|
|
5852
|
+
renderPreventionReceipt,
|
|
5566
5853
|
resolveBriefingBudget,
|
|
5567
5854
|
resolveHaivePaths,
|
|
5568
5855
|
resolveManifestFiles,
|
|
5569
5856
|
resolveProjectInfo,
|
|
5570
5857
|
retirementSignal,
|
|
5858
|
+
revertedShaFromCommit,
|
|
5571
5859
|
runRegexSensor,
|
|
5572
5860
|
runSensors,
|
|
5573
5861
|
runtimeJournalPath,
|
|
@@ -5579,6 +5867,7 @@ export {
|
|
|
5579
5867
|
scoreSensorCase,
|
|
5580
5868
|
selectCommandSensors,
|
|
5581
5869
|
sensorAppliesToPath,
|
|
5870
|
+
sensorLedgerPath,
|
|
5582
5871
|
sensorPatternBrittleness,
|
|
5583
5872
|
sensorSelfCheck,
|
|
5584
5873
|
sensorTargetsFromDiff,
|
|
@@ -5605,6 +5894,8 @@ export {
|
|
|
5605
5894
|
usagePath,
|
|
5606
5895
|
verifyAnchor,
|
|
5607
5896
|
watchContracts,
|
|
5897
|
+
withQuarantineNote,
|
|
5898
|
+
withoutQuarantineNote,
|
|
5608
5899
|
writeBriefingMarker,
|
|
5609
5900
|
writeSessionHandoff
|
|
5610
5901
|
};
|