@hivelore/core 0.34.1 → 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 +115 -2
- package/dist/index.js +317 -34
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -891,7 +891,7 @@ async function appendPreventionEvent(paths, event) {
|
|
|
891
891
|
await mkdir2(path6.dirname(file), { recursive: true });
|
|
892
892
|
await appendFile(file, JSON.stringify(event) + "\n", "utf8");
|
|
893
893
|
}
|
|
894
|
-
async function recordPreventionHits(paths, firedIds, source, now = /* @__PURE__ */ new Date()) {
|
|
894
|
+
async function recordPreventionHits(paths, firedIds, source, now = /* @__PURE__ */ new Date(), details = {}) {
|
|
895
895
|
const unique = [...new Set(firedIds)].filter(Boolean);
|
|
896
896
|
if (unique.length === 0) return [];
|
|
897
897
|
const usage = await loadUsageIndex(paths).catch(() => null);
|
|
@@ -905,11 +905,68 @@ async function recordPreventionHits(paths, firedIds, source, now = /* @__PURE__
|
|
|
905
905
|
});
|
|
906
906
|
const at = now.toISOString();
|
|
907
907
|
for (const id of recordedIds) {
|
|
908
|
-
await appendPreventionEvent(paths, { at, id, source }).catch(() => {
|
|
908
|
+
await appendPreventionEvent(paths, { at, id, source, ...details[id] }).catch(() => {
|
|
909
909
|
});
|
|
910
910
|
}
|
|
911
911
|
return recordedIds;
|
|
912
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
|
+
}
|
|
913
970
|
async function loadPreventionEvents(paths) {
|
|
914
971
|
const file = preventionLogPath(paths);
|
|
915
972
|
if (!existsSync4(file)) return [];
|
|
@@ -3782,10 +3839,10 @@ function sensorPatternBrittleness(pattern) {
|
|
|
3782
3839
|
function normalizeProjectPath(value) {
|
|
3783
3840
|
return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^[ab]\//, "").replace(/\/+$/g, "");
|
|
3784
3841
|
}
|
|
3785
|
-
function sensorAppliesToPath(sensor, anchorPaths,
|
|
3842
|
+
function sensorAppliesToPath(sensor, anchorPaths, path20) {
|
|
3786
3843
|
const scopes = sensor.paths.length > 0 ? sensor.paths : anchorPaths;
|
|
3787
3844
|
if (scopes.length === 0) return true;
|
|
3788
|
-
const target = normalizeProjectPath(
|
|
3845
|
+
const target = normalizeProjectPath(path20);
|
|
3789
3846
|
return scopes.some((rawScope) => {
|
|
3790
3847
|
const scope = normalizeProjectPath(rawScope);
|
|
3791
3848
|
if (!scope) return false;
|
|
@@ -3978,6 +4035,142 @@ function addedLinesFromDiff(diff) {
|
|
|
3978
4035
|
return diff.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).map((l) => l.slice(1)).join("\n");
|
|
3979
4036
|
}
|
|
3980
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
|
+
|
|
3981
4174
|
// src/sensor-suggest.ts
|
|
3982
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;
|
|
3983
4176
|
var SENSOR_STOPWORDS = /* @__PURE__ */ new Set([
|
|
@@ -4425,8 +4618,8 @@ function normalizeFindingSeverity(raw) {
|
|
|
4425
4618
|
return "info";
|
|
4426
4619
|
}
|
|
4427
4620
|
}
|
|
4428
|
-
function findingKey(tool, ruleId,
|
|
4429
|
-
return `${tool}:${ruleId}:${
|
|
4621
|
+
function findingKey(tool, ruleId, path20) {
|
|
4622
|
+
return `${tool}:${ruleId}:${path20}`;
|
|
4430
4623
|
}
|
|
4431
4624
|
function coerceJson(input) {
|
|
4432
4625
|
if (typeof input === "string") {
|
|
@@ -4460,8 +4653,8 @@ function parseSarif(input) {
|
|
|
4460
4653
|
const physical = asRecord(location.physicalLocation);
|
|
4461
4654
|
const artifact = asRecord(physical.artifactLocation);
|
|
4462
4655
|
const region = asRecord(physical.region);
|
|
4463
|
-
const
|
|
4464
|
-
if (!
|
|
4656
|
+
const path20 = typeof artifact.uri === "string" ? normalizeUri(artifact.uri) : "";
|
|
4657
|
+
if (!path20) continue;
|
|
4465
4658
|
const line = typeof region.startLine === "number" ? region.startLine : void 0;
|
|
4466
4659
|
const snippet = typeof asRecord(region.snippet).text === "string" ? asRecord(region.snippet).text.trim() : void 0;
|
|
4467
4660
|
findings.push({
|
|
@@ -4469,10 +4662,10 @@ function parseSarif(input) {
|
|
|
4469
4662
|
ruleId,
|
|
4470
4663
|
message: message.trim(),
|
|
4471
4664
|
severity,
|
|
4472
|
-
path:
|
|
4665
|
+
path: path20,
|
|
4473
4666
|
...line !== void 0 ? { line } : {},
|
|
4474
4667
|
...snippet ? { snippet } : {},
|
|
4475
|
-
key: findingKey(tool, ruleId,
|
|
4668
|
+
key: findingKey(tool, ruleId, path20)
|
|
4476
4669
|
});
|
|
4477
4670
|
}
|
|
4478
4671
|
}
|
|
@@ -4491,17 +4684,17 @@ function parseSonar(input) {
|
|
|
4491
4684
|
(typeof issue.severity === "string" ? issue.severity : void 0) ?? impactSeverity
|
|
4492
4685
|
);
|
|
4493
4686
|
const component = typeof issue.component === "string" ? issue.component : "";
|
|
4494
|
-
const
|
|
4495
|
-
if (!
|
|
4687
|
+
const path20 = componentToPath(component);
|
|
4688
|
+
if (!path20) continue;
|
|
4496
4689
|
const line = typeof issue.line === "number" ? issue.line : void 0;
|
|
4497
4690
|
findings.push({
|
|
4498
4691
|
tool: "sonar",
|
|
4499
4692
|
ruleId,
|
|
4500
4693
|
message,
|
|
4501
4694
|
severity,
|
|
4502
|
-
path:
|
|
4695
|
+
path: path20,
|
|
4503
4696
|
...line !== void 0 ? { line } : {},
|
|
4504
|
-
key: findingKey("sonar", ruleId,
|
|
4697
|
+
key: findingKey("sonar", ruleId, path20)
|
|
4505
4698
|
});
|
|
4506
4699
|
}
|
|
4507
4700
|
return findings;
|
|
@@ -4514,7 +4707,7 @@ function parseEslintJson(input, opts = {}) {
|
|
|
4514
4707
|
const file = asRecord(fileRaw);
|
|
4515
4708
|
const rawPath = typeof file.filePath === "string" ? file.filePath : "";
|
|
4516
4709
|
if (!rawPath) continue;
|
|
4517
|
-
const
|
|
4710
|
+
const path20 = cwd && rawPath.startsWith(cwd) ? rawPath.slice(cwd.length) : rawPath;
|
|
4518
4711
|
for (const msgRaw of asArray(file.messages)) {
|
|
4519
4712
|
const msg = asRecord(msgRaw);
|
|
4520
4713
|
const ruleId = typeof msg.ruleId === "string" && msg.ruleId ? msg.ruleId : "parse-error";
|
|
@@ -4526,9 +4719,9 @@ function parseEslintJson(input, opts = {}) {
|
|
|
4526
4719
|
ruleId,
|
|
4527
4720
|
message,
|
|
4528
4721
|
severity,
|
|
4529
|
-
path:
|
|
4722
|
+
path: path20,
|
|
4530
4723
|
...line !== void 0 ? { line } : {},
|
|
4531
|
-
key: findingKey("eslint", ruleId,
|
|
4724
|
+
key: findingKey("eslint", ruleId, path20)
|
|
4532
4725
|
});
|
|
4533
4726
|
}
|
|
4534
4727
|
}
|
|
@@ -4946,7 +5139,7 @@ function tallyHotFiles(paths, source = "agent") {
|
|
|
4946
5139
|
if (!norm) continue;
|
|
4947
5140
|
counts.set(norm, (counts.get(norm) ?? 0) + 1);
|
|
4948
5141
|
}
|
|
4949
|
-
return [...counts.entries()].map(([
|
|
5142
|
+
return [...counts.entries()].map(([path20, changes]) => ({ path: path20, changes, source })).sort((a, b) => b.changes - a.changes);
|
|
4950
5143
|
}
|
|
4951
5144
|
function mergeHotFiles(a, b) {
|
|
4952
5145
|
const merged = /* @__PURE__ */ new Map();
|
|
@@ -4966,21 +5159,21 @@ function mergeHotFiles(a, b) {
|
|
|
4966
5159
|
}
|
|
4967
5160
|
|
|
4968
5161
|
// src/eval-history.ts
|
|
4969
|
-
import { appendFile as
|
|
4970
|
-
import { existsSync as
|
|
4971
|
-
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";
|
|
4972
5165
|
function evalHistoryPath(paths) {
|
|
4973
|
-
return
|
|
5166
|
+
return path18.join(paths.haiveDir, ".cache", "eval-history.jsonl");
|
|
4974
5167
|
}
|
|
4975
5168
|
async function appendEvalHistory(paths, entry) {
|
|
4976
5169
|
const file = evalHistoryPath(paths);
|
|
4977
|
-
await
|
|
4978
|
-
await
|
|
5170
|
+
await mkdir12(path18.dirname(file), { recursive: true });
|
|
5171
|
+
await appendFile5(file, JSON.stringify(entry) + "\n", "utf8");
|
|
4979
5172
|
}
|
|
4980
5173
|
async function loadEvalHistory(paths) {
|
|
4981
5174
|
const file = evalHistoryPath(paths);
|
|
4982
|
-
if (!
|
|
4983
|
-
const raw = await
|
|
5175
|
+
if (!existsSync16(file)) return [];
|
|
5176
|
+
const raw = await readFile15(file, "utf8").catch(() => "");
|
|
4984
5177
|
const out = [];
|
|
4985
5178
|
for (const line of raw.split("\n")) {
|
|
4986
5179
|
const trimmed = line.trim();
|
|
@@ -5124,6 +5317,81 @@ function proposeSeedsFromCommits(commits, limit = 20) {
|
|
|
5124
5317
|
return out;
|
|
5125
5318
|
}
|
|
5126
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
|
+
|
|
5127
5395
|
// src/seed.ts
|
|
5128
5396
|
var JS_DETECTORS = [
|
|
5129
5397
|
["nestjs", ["@nestjs/core"]],
|
|
@@ -5248,12 +5516,12 @@ ${trimmed}`;
|
|
|
5248
5516
|
}
|
|
5249
5517
|
|
|
5250
5518
|
// src/handoff.ts
|
|
5251
|
-
import { writeFile as
|
|
5252
|
-
import { existsSync as
|
|
5253
|
-
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";
|
|
5254
5522
|
var HANDOFF_FILENAME = "NEXT.md";
|
|
5255
5523
|
function handoffFilePath(root) {
|
|
5256
|
-
return
|
|
5524
|
+
return path19.join(root, HANDOFF_FILENAME);
|
|
5257
5525
|
}
|
|
5258
5526
|
function buildHandoffMarkdown(data) {
|
|
5259
5527
|
const at = (data.at ?? /* @__PURE__ */ new Date()).toISOString();
|
|
@@ -5302,18 +5570,18 @@ function buildHandoffMarkdown(data) {
|
|
|
5302
5570
|
}
|
|
5303
5571
|
async function writeSessionHandoff(root, data) {
|
|
5304
5572
|
const file = handoffFilePath(root);
|
|
5305
|
-
await
|
|
5573
|
+
await writeFile10(file, buildHandoffMarkdown(data), "utf8");
|
|
5306
5574
|
return file;
|
|
5307
5575
|
}
|
|
5308
5576
|
async function readSessionHandoff(root) {
|
|
5309
5577
|
const file = handoffFilePath(root);
|
|
5310
|
-
if (!
|
|
5311
|
-
const raw = await
|
|
5578
|
+
if (!existsSync17(file)) return null;
|
|
5579
|
+
const raw = await readFile16(file, "utf8").catch(() => "");
|
|
5312
5580
|
return raw.trim() ? raw : null;
|
|
5313
5581
|
}
|
|
5314
5582
|
async function handoffAgeMs(root, now = /* @__PURE__ */ new Date()) {
|
|
5315
5583
|
const file = handoffFilePath(root);
|
|
5316
|
-
if (!
|
|
5584
|
+
if (!existsSync17(file)) return null;
|
|
5317
5585
|
try {
|
|
5318
5586
|
const s = await stat3(file);
|
|
5319
5587
|
return Math.max(0, now.getTime() - s.mtimeMs);
|
|
@@ -5429,10 +5697,12 @@ export {
|
|
|
5429
5697
|
appendEvalHistory,
|
|
5430
5698
|
appendPreventionEvent,
|
|
5431
5699
|
appendRuntimeJournalEntry,
|
|
5700
|
+
appendSensorEvaluations,
|
|
5432
5701
|
appendUsageEvent,
|
|
5433
5702
|
applyConflictResolution,
|
|
5434
5703
|
applyFeedbackAdjustment,
|
|
5435
5704
|
assessBootstrapState,
|
|
5705
|
+
assessSensorHealth,
|
|
5436
5706
|
bridgeMemorySummary,
|
|
5437
5707
|
briefingMarkerPath,
|
|
5438
5708
|
briefingMarkersDir,
|
|
@@ -5443,6 +5713,7 @@ export {
|
|
|
5443
5713
|
buildDocFrequency,
|
|
5444
5714
|
buildFrontmatter,
|
|
5445
5715
|
buildHandoffMarkdown,
|
|
5716
|
+
buildPreventionReceipt,
|
|
5446
5717
|
buildReport,
|
|
5447
5718
|
bumpRead,
|
|
5448
5719
|
classifyMemoryPriority,
|
|
@@ -5459,6 +5730,7 @@ export {
|
|
|
5459
5730
|
computeImpact,
|
|
5460
5731
|
computePreventionTrend,
|
|
5461
5732
|
computeRecurrence,
|
|
5733
|
+
computeScopeHash,
|
|
5462
5734
|
configPath,
|
|
5463
5735
|
contractLockPath,
|
|
5464
5736
|
countSourceFilesOnDisk,
|
|
@@ -5475,6 +5747,7 @@ export {
|
|
|
5475
5747
|
estimateTokens,
|
|
5476
5748
|
evalHistoryPath,
|
|
5477
5749
|
evaluateSkillActivation,
|
|
5750
|
+
existingGateMissShas,
|
|
5478
5751
|
extractActionsBriefBody,
|
|
5479
5752
|
extractReferencedPaths,
|
|
5480
5753
|
extractSensorExamples,
|
|
@@ -5488,6 +5761,7 @@ export {
|
|
|
5488
5761
|
findingBody,
|
|
5489
5762
|
findingToDraft,
|
|
5490
5763
|
firstMemoryOneLine,
|
|
5764
|
+
gatePassedShas,
|
|
5491
5765
|
generateBridges,
|
|
5492
5766
|
getUsage,
|
|
5493
5767
|
globToRegExp,
|
|
@@ -5525,6 +5799,7 @@ export {
|
|
|
5525
5799
|
loadMemoriesFromDirDetailed,
|
|
5526
5800
|
loadMemory,
|
|
5527
5801
|
loadPreventionEvents,
|
|
5802
|
+
loadSensorLedger,
|
|
5528
5803
|
loadUsageIndex,
|
|
5529
5804
|
looksLikeGenericAdvice,
|
|
5530
5805
|
meetsSeedQualityFloor,
|
|
@@ -5549,13 +5824,16 @@ export {
|
|
|
5549
5824
|
pathsOverlap,
|
|
5550
5825
|
pickSnippetNeedle,
|
|
5551
5826
|
planConflictResolution,
|
|
5827
|
+
planGitWatch,
|
|
5552
5828
|
prepareBridgeData,
|
|
5553
5829
|
preventionLogPath,
|
|
5554
5830
|
priorityRank,
|
|
5555
5831
|
prioritySignals,
|
|
5556
5832
|
projectContextRecentlyEmitted,
|
|
5833
|
+
proposeGateMissDrafts,
|
|
5557
5834
|
proposeSeedsFromCommits,
|
|
5558
5835
|
pullCrossRepoSources,
|
|
5836
|
+
quarantineNote,
|
|
5559
5837
|
queryCodeMap,
|
|
5560
5838
|
rankMemoriesLexical,
|
|
5561
5839
|
readRecentBriefingMarker,
|
|
@@ -5571,11 +5849,13 @@ export {
|
|
|
5571
5849
|
relPathFrom,
|
|
5572
5850
|
renderBootstrapChecklist,
|
|
5573
5851
|
renderCaughtForYou,
|
|
5852
|
+
renderPreventionReceipt,
|
|
5574
5853
|
resolveBriefingBudget,
|
|
5575
5854
|
resolveHaivePaths,
|
|
5576
5855
|
resolveManifestFiles,
|
|
5577
5856
|
resolveProjectInfo,
|
|
5578
5857
|
retirementSignal,
|
|
5858
|
+
revertedShaFromCommit,
|
|
5579
5859
|
runRegexSensor,
|
|
5580
5860
|
runSensors,
|
|
5581
5861
|
runtimeJournalPath,
|
|
@@ -5587,6 +5867,7 @@ export {
|
|
|
5587
5867
|
scoreSensorCase,
|
|
5588
5868
|
selectCommandSensors,
|
|
5589
5869
|
sensorAppliesToPath,
|
|
5870
|
+
sensorLedgerPath,
|
|
5590
5871
|
sensorPatternBrittleness,
|
|
5591
5872
|
sensorSelfCheck,
|
|
5592
5873
|
sensorTargetsFromDiff,
|
|
@@ -5613,6 +5894,8 @@ export {
|
|
|
5613
5894
|
usagePath,
|
|
5614
5895
|
verifyAnchor,
|
|
5615
5896
|
watchContracts,
|
|
5897
|
+
withQuarantineNote,
|
|
5898
|
+
withoutQuarantineNote,
|
|
5616
5899
|
writeBriefingMarker,
|
|
5617
5900
|
writeSessionHandoff
|
|
5618
5901
|
};
|