@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.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, path19) {
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(path19);
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, path19) {
4429
- return `${tool}:${ruleId}:${path19}`;
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 path19 = typeof artifact.uri === "string" ? normalizeUri(artifact.uri) : "";
4464
- if (!path19) continue;
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: path19,
4665
+ path: path20,
4473
4666
  ...line !== void 0 ? { line } : {},
4474
4667
  ...snippet ? { snippet } : {},
4475
- key: findingKey(tool, ruleId, path19)
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 path19 = componentToPath(component);
4495
- if (!path19) continue;
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: path19,
4695
+ path: path20,
4503
4696
  ...line !== void 0 ? { line } : {},
4504
- key: findingKey("sonar", ruleId, path19)
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 path19 = cwd && rawPath.startsWith(cwd) ? rawPath.slice(cwd.length) : rawPath;
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: path19,
4722
+ path: path20,
4530
4723
  ...line !== void 0 ? { line } : {},
4531
- key: findingKey("eslint", ruleId, path19)
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(([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);
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 appendFile4, mkdir as mkdir11, readFile as readFile14 } from "fs/promises";
4970
- import { existsSync as existsSync15 } from "fs";
4971
- 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";
4972
5165
  function evalHistoryPath(paths) {
4973
- return path17.join(paths.haiveDir, ".cache", "eval-history.jsonl");
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 mkdir11(path17.dirname(file), { recursive: true });
4978
- 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");
4979
5172
  }
4980
5173
  async function loadEvalHistory(paths) {
4981
5174
  const file = evalHistoryPath(paths);
4982
- if (!existsSync15(file)) return [];
4983
- const raw = await readFile14(file, "utf8").catch(() => "");
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 writeFile9, readFile as readFile15, stat as stat3 } from "fs/promises";
5252
- import { existsSync as existsSync16 } from "fs";
5253
- 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";
5254
5522
  var HANDOFF_FILENAME = "NEXT.md";
5255
5523
  function handoffFilePath(root) {
5256
- return path18.join(root, HANDOFF_FILENAME);
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 writeFile9(file, buildHandoffMarkdown(data), "utf8");
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 (!existsSync16(file)) return null;
5311
- const raw = await readFile15(file, "utf8").catch(() => "");
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 (!existsSync16(file)) return null;
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
  };