@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.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 out = [];
257
+ const loaded = [];
258
+ const invalid = [];
255
259
  for (const file of files) {
256
260
  try {
257
- out.push(await loadMemory(file));
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 out;
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 DEFAULT_INCLUDE = [
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 DEFAULT_EXCLUDE = [
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 ?? DEFAULT_INCLUDE);
1944
- const exclude = new Set(options.excludeDirs ?? DEFAULT_EXCLUDE);
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(DEFAULT_INCLUDE);
1963
- const exclude = /* @__PURE__ */ new Set([...DEFAULT_EXCLUDE, ...options.excludeDirs ?? []]);
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, path19) {
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(path19);
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, path19) {
4424
- return `${tool}:${ruleId}:${path19}`;
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 path19 = typeof artifact.uri === "string" ? normalizeUri(artifact.uri) : "";
4459
- if (!path19) continue;
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: path19,
4665
+ path: path20,
4468
4666
  ...line !== void 0 ? { line } : {},
4469
4667
  ...snippet ? { snippet } : {},
4470
- key: findingKey(tool, ruleId, path19)
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 path19 = componentToPath(component);
4490
- if (!path19) continue;
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: path19,
4695
+ path: path20,
4498
4696
  ...line !== void 0 ? { line } : {},
4499
- key: findingKey("sonar", ruleId, path19)
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 path19 = cwd && rawPath.startsWith(cwd) ? rawPath.slice(cwd.length) : rawPath;
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: path19,
4722
+ path: path20,
4525
4723
  ...line !== void 0 ? { line } : {},
4526
- key: findingKey("eslint", ruleId, path19)
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(([path19, changes]) => ({ path: path19, changes, source })).sort((a, b) => b.changes - a.changes);
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 appendFile4, mkdir as mkdir11, readFile as readFile14 } from "fs/promises";
4965
- import { existsSync as existsSync15 } from "fs";
4966
- import path17 from "path";
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 path17.join(paths.haiveDir, ".cache", "eval-history.jsonl");
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 mkdir11(path17.dirname(file), { recursive: true });
4973
- await appendFile4(file, JSON.stringify(entry) + "\n", "utf8");
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 (!existsSync15(file)) return [];
4978
- const raw = await readFile14(file, "utf8").catch(() => "");
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 writeFile9, readFile as readFile15, stat as stat3 } from "fs/promises";
5247
- import { existsSync as existsSync16 } from "fs";
5248
- import path18 from "path";
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 path18.join(root, HANDOFF_FILENAME);
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 writeFile9(file, buildHandoffMarkdown(data), "utf8");
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 (!existsSync16(file)) return null;
5306
- const raw = await readFile15(file, "utf8").catch(() => "");
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 (!existsSync16(file)) return null;
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
  };