@hiveai/core 0.13.7 → 0.13.9

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
@@ -596,7 +596,9 @@ function emptyUsage() {
596
596
  last_rejected_at: null,
597
597
  rejection_reason: null,
598
598
  applied_count: 0,
599
- last_applied_at: null
599
+ last_applied_at: null,
600
+ prevented_count: 0,
601
+ last_prevented_at: null
600
602
  };
601
603
  }
602
604
  function normalizeUsage(stored) {
@@ -667,6 +669,21 @@ function recordApplied(index, id) {
667
669
  };
668
670
  return index;
669
671
  }
672
+ var PREVENTION_DEBOUNCE_MS = 5 * 60 * 1e3;
673
+ function recordPrevention(index, id, now = Date.now()) {
674
+ const current = normalizeUsage(index.by_id[id]);
675
+ const last = current.last_prevented_at ? Date.parse(current.last_prevented_at) : 0;
676
+ if (Number.isFinite(last) && last > 0 && now - last < PREVENTION_DEBOUNCE_MS) {
677
+ index.by_id[id] = current;
678
+ return false;
679
+ }
680
+ index.by_id[id] = {
681
+ ...current,
682
+ prevented_count: current.prevented_count + 1,
683
+ last_prevented_at: new Date(now).toISOString()
684
+ };
685
+ return true;
686
+ }
670
687
  var DECAY_DAYS = 90;
671
688
  function isDecaying(usage, createdAt) {
672
689
  const threshold = Date.now() - DECAY_DAYS * 24 * 60 * 60 * 1e3;
@@ -710,7 +727,10 @@ function computeImpact(fm, usage, options = {}) {
710
727
  raw += Math.min(1, usage.applied_count / 4) * 0.6;
711
728
  signals.push(`applied ${usage.applied_count}\xD7`);
712
729
  }
713
- if (hasSensorFired(fm)) {
730
+ if (usage.prevented_count > 0) {
731
+ raw += Math.min(1, usage.prevented_count / 3) * 0.6;
732
+ signals.push(`prevented ${usage.prevented_count}\xD7`);
733
+ } else if (hasSensorFired(fm)) {
714
734
  raw += 0.25;
715
735
  signals.push("sensor fired");
716
736
  }
@@ -771,6 +791,71 @@ function summarizeImpact(scores) {
771
791
  return summary;
772
792
  }
773
793
 
794
+ // src/prevention.ts
795
+ import { appendFile, mkdir as mkdir2, readFile as readFile4 } from "fs/promises";
796
+ import { existsSync as existsSync4 } from "fs";
797
+ import path6 from "path";
798
+ function preventionLogPath(paths) {
799
+ return path6.join(paths.haiveDir, ".cache", "prevention-log.jsonl");
800
+ }
801
+ async function appendPreventionEvent(paths, event) {
802
+ const file = preventionLogPath(paths);
803
+ await mkdir2(path6.dirname(file), { recursive: true });
804
+ await appendFile(file, JSON.stringify(event) + "\n", "utf8");
805
+ }
806
+ async function loadPreventionEvents(paths) {
807
+ const file = preventionLogPath(paths);
808
+ if (!existsSync4(file)) return [];
809
+ const raw = await readFile4(file, "utf8").catch(() => "");
810
+ const events = [];
811
+ for (const line of raw.split("\n")) {
812
+ const trimmed = line.trim();
813
+ if (!trimmed) continue;
814
+ try {
815
+ const e = JSON.parse(trimmed);
816
+ if (e && typeof e.at === "string" && typeof e.id === "string") events.push(e);
817
+ } catch {
818
+ }
819
+ }
820
+ return events;
821
+ }
822
+ var MS_PER_DAY2 = 864e5;
823
+ function computePreventionTrend(events, now = /* @__PURE__ */ new Date(), weeks = 6) {
824
+ const nowMs = now.getTime();
825
+ let last7 = 0;
826
+ let last30 = 0;
827
+ const weekly = new Array(weeks).fill(0);
828
+ for (const e of events) {
829
+ const t = Date.parse(e.at);
830
+ if (!Number.isFinite(t)) continue;
831
+ const ageDays = (nowMs - t) / MS_PER_DAY2;
832
+ if (ageDays < 0) continue;
833
+ if (ageDays <= 7) last7 += 1;
834
+ if (ageDays <= 30) last30 += 1;
835
+ const weekIdx = weeks - 1 - Math.floor(ageDays / 7);
836
+ if (weekIdx >= 0 && weekIdx < weeks) weekly[weekIdx] = (weekly[weekIdx] ?? 0) + 1;
837
+ }
838
+ return { last_7d: last7, last_30d: last30, weekly };
839
+ }
840
+ function computeRecurrence(events) {
841
+ const byId = /* @__PURE__ */ new Map();
842
+ for (const e of events) {
843
+ const cur = byId.get(e.id) ?? { catches: 0, days: /* @__PURE__ */ new Set(), last: e.at };
844
+ cur.catches += 1;
845
+ cur.days.add(e.at.slice(0, 10));
846
+ if (e.at > cur.last) cur.last = e.at;
847
+ byId.set(e.id, cur);
848
+ }
849
+ const rows = [];
850
+ for (const [id, v] of byId) {
851
+ if (v.days.size >= 2) {
852
+ rows.push({ id, catches: v.catches, distinct_days: v.days.size, last_at: v.last });
853
+ }
854
+ }
855
+ rows.sort((a, b) => b.distinct_days - a.distinct_days || b.catches - a.catches);
856
+ return { recurring_count: rows.length, top: rows };
857
+ }
858
+
774
859
  // src/eval.ts
775
860
  function round32(n) {
776
861
  return Math.round(n * 1e3) / 1e3;
@@ -906,13 +991,13 @@ var DEFAULT_CONFIDENCE_THRESHOLDS = {
906
991
  decayDays: 180,
907
992
  hardDecayDays: 365
908
993
  };
909
- var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
994
+ var MS_PER_DAY3 = 24 * 60 * 60 * 1e3;
910
995
  function deriveConfidence(fm, usage, thresholds = DEFAULT_CONFIDENCE_THRESHOLDS, now = /* @__PURE__ */ new Date()) {
911
996
  if (fm.status === "stale" || fm.status === "deprecated" || fm.status === "rejected") return "stale";
912
997
  const baseLevel = baseConfidence(fm, usage, thresholds);
913
998
  if (baseLevel !== "authoritative" && baseLevel !== "trusted") return baseLevel;
914
999
  const anchor = usage.last_read_at ?? fm.created_at;
915
- const ageDays = (now.getTime() - new Date(anchor).getTime()) / MS_PER_DAY2;
1000
+ const ageDays = (now.getTime() - new Date(anchor).getTime()) / MS_PER_DAY3;
916
1001
  if (Number.isNaN(ageDays) || ageDays <= 0) return baseLevel;
917
1002
  if (ageDays >= thresholds.hardDecayDays) {
918
1003
  return "low";
@@ -1206,10 +1291,10 @@ function allocateBudget(parts, maxTokens) {
1206
1291
  }
1207
1292
 
1208
1293
  // src/code-map.ts
1209
- import { mkdir as mkdir2, readFile as readFile4, readdir as readdir3, writeFile as writeFile2 } from "fs/promises";
1210
- import { existsSync as existsSync4 } from "fs";
1294
+ import { mkdir as mkdir3, readFile as readFile5, readdir as readdir3, writeFile as writeFile2 } from "fs/promises";
1295
+ import { existsSync as existsSync5 } from "fs";
1211
1296
  import { spawnSync } from "child_process";
1212
- import path6 from "path";
1297
+ import path7 from "path";
1213
1298
  var CODE_MAP_FILE = "code-map.json";
1214
1299
  var DEFAULT_INCLUDE = [
1215
1300
  ".ts",
@@ -1251,16 +1336,16 @@ var DEFAULT_EXCLUDE = [
1251
1336
  ];
1252
1337
  var TEST_FILE_RE = /\.(test|spec)\.[a-z]+$/i;
1253
1338
  function codeMapPath(paths) {
1254
- return path6.join(paths.haiveDir, CODE_MAP_FILE);
1339
+ return path7.join(paths.haiveDir, CODE_MAP_FILE);
1255
1340
  }
1256
1341
  async function loadCodeMap(paths) {
1257
1342
  const file = codeMapPath(paths);
1258
- if (!existsSync4(file)) return null;
1259
- return JSON.parse(await readFile4(file, "utf8"));
1343
+ if (!existsSync5(file)) return null;
1344
+ return JSON.parse(await readFile5(file, "utf8"));
1260
1345
  }
1261
1346
  async function saveCodeMap(paths, map) {
1262
1347
  const file = codeMapPath(paths);
1263
- await mkdir2(path6.dirname(file), { recursive: true });
1348
+ await mkdir3(path7.dirname(file), { recursive: true });
1264
1349
  await writeFile2(file, JSON.stringify(map, null, 2), "utf8");
1265
1350
  }
1266
1351
  async function buildCodeMap(root, options = {}) {
@@ -1268,10 +1353,10 @@ async function buildCodeMap(root, options = {}) {
1268
1353
  const exclude = new Set(options.excludeDirs ?? DEFAULT_EXCLUDE);
1269
1354
  const files = {};
1270
1355
  for await (const abs of collectSourceFiles(root, include, exclude, options.includeUntracked)) {
1271
- const rel = path6.relative(root, abs).replace(/\\/g, "/");
1356
+ const rel = path7.relative(root, abs).replace(/\\/g, "/");
1272
1357
  if (rel.startsWith(".ai/")) continue;
1273
- const content = await readFile4(abs, "utf8");
1274
- const ext = path6.extname(abs).toLowerCase();
1358
+ const content = await readFile5(abs, "utf8");
1359
+ const ext = path7.extname(abs).toLowerCase();
1275
1360
  const entry = parseFile(content, ext);
1276
1361
  if (entry.exports.length > 0) files[rel] = entry;
1277
1362
  }
@@ -1285,7 +1370,7 @@ async function buildCodeMap(root, options = {}) {
1285
1370
  async function* collectSourceFiles(root, include, exclude, includeUntracked) {
1286
1371
  const gitFiles = gitSourceFiles(root, include, exclude, includeUntracked === true);
1287
1372
  if (gitFiles) {
1288
- for (const rel of gitFiles) yield path6.join(root, rel);
1373
+ for (const rel of gitFiles) yield path7.join(root, rel);
1289
1374
  return;
1290
1375
  }
1291
1376
  yield* walkSourceFiles(root, include, exclude);
@@ -1312,11 +1397,11 @@ async function* walkSourceFiles(dir, include, exclude) {
1312
1397
  if (entry.isDirectory()) continue;
1313
1398
  }
1314
1399
  if (exclude.has(entry.name)) continue;
1315
- const full = path6.join(dir, entry.name);
1400
+ const full = path7.join(dir, entry.name);
1316
1401
  if (entry.isDirectory()) {
1317
1402
  yield* walkSourceFiles(full, include, exclude);
1318
1403
  } else if (entry.isFile()) {
1319
- const ext = path6.extname(entry.name).toLowerCase();
1404
+ const ext = path7.extname(entry.name).toLowerCase();
1320
1405
  if (include.has(ext) && !TEST_FILE_RE.test(entry.name)) yield full;
1321
1406
  }
1322
1407
  }
@@ -1327,7 +1412,7 @@ function isIncludedSourcePath(rel, include, exclude) {
1327
1412
  const parts = normalized.split("/");
1328
1413
  if (parts.some((part) => exclude.has(part))) return false;
1329
1414
  const base = parts.at(-1) ?? "";
1330
- const ext = path6.extname(base).toLowerCase();
1415
+ const ext = path7.extname(base).toLowerCase();
1331
1416
  return include.has(ext) && !TEST_FILE_RE.test(base);
1332
1417
  }
1333
1418
  var EXPORT_RE = /^export\s+(?:default\s+)?(async\s+)?(function|class|interface|type|const|let|var|enum)\s+(\*?)\s*([A-Za-z_$][\w$]*)/gm;
@@ -1541,10 +1626,10 @@ function queryCodeMap(map, options) {
1541
1626
  }
1542
1627
 
1543
1628
  // src/config.ts
1544
- import { existsSync as existsSync5 } from "fs";
1629
+ import { existsSync as existsSync6 } from "fs";
1545
1630
  import { readFileSync } from "fs";
1546
- import { readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
1547
- import path7 from "path";
1631
+ import { readFile as readFile6, writeFile as writeFile3 } from "fs/promises";
1632
+ import path8 from "path";
1548
1633
  var CONFIG_FILE = "haive.config.json";
1549
1634
  var DEFAULT_CONFIG = {
1550
1635
  autopilot: false,
@@ -1616,13 +1701,13 @@ function antiPatternGateParams(gate) {
1616
1701
  }
1617
1702
  }
1618
1703
  function configPath(paths) {
1619
- return path7.join(paths.haiveDir, CONFIG_FILE);
1704
+ return path8.join(paths.haiveDir, CONFIG_FILE);
1620
1705
  }
1621
1706
  async function loadConfig(paths) {
1622
1707
  const file = configPath(paths);
1623
- if (!existsSync5(file)) return { ...DEFAULT_CONFIG };
1708
+ if (!existsSync6(file)) return { ...DEFAULT_CONFIG };
1624
1709
  try {
1625
- const raw = await readFile5(file, "utf8");
1710
+ const raw = await readFile6(file, "utf8");
1626
1711
  const parsed = JSON.parse(raw);
1627
1712
  const merged = mergeConfig(DEFAULT_CONFIG, parsed);
1628
1713
  if (merged.autopilot) {
@@ -1635,7 +1720,7 @@ async function loadConfig(paths) {
1635
1720
  }
1636
1721
  function loadConfigSync(paths) {
1637
1722
  const file = configPath(paths);
1638
- if (!existsSync5(file)) return { ...DEFAULT_CONFIG };
1723
+ if (!existsSync6(file)) return { ...DEFAULT_CONFIG };
1639
1724
  try {
1640
1725
  const parsed = JSON.parse(readFileSync(file, "utf8"));
1641
1726
  const merged = mergeConfig(DEFAULT_CONFIG, parsed);
@@ -1663,21 +1748,21 @@ function mergeConfig(base, override) {
1663
1748
  }
1664
1749
 
1665
1750
  // src/cross-repo.ts
1666
- import { existsSync as existsSync6 } from "fs";
1667
- import { mkdir as mkdir3, readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
1668
- import path8 from "path";
1751
+ import { existsSync as existsSync7 } from "fs";
1752
+ import { mkdir as mkdir4, readFile as readFile7, writeFile as writeFile4 } from "fs/promises";
1753
+ import path9 from "path";
1669
1754
  import { spawnSync as spawnSync2 } from "child_process";
1670
1755
  async function loadImportMap(cacheDir) {
1671
- const mapPath = path8.join(cacheDir, "import-map.json");
1672
- if (!existsSync6(mapPath)) return {};
1756
+ const mapPath = path9.join(cacheDir, "import-map.json");
1757
+ if (!existsSync7(mapPath)) return {};
1673
1758
  try {
1674
- return JSON.parse(await readFile6(mapPath, "utf8"));
1759
+ return JSON.parse(await readFile7(mapPath, "utf8"));
1675
1760
  } catch {
1676
1761
  return {};
1677
1762
  }
1678
1763
  }
1679
1764
  async function saveImportMap(cacheDir, map) {
1680
- await writeFile4(path8.join(cacheDir, "import-map.json"), JSON.stringify(map, null, 2) + "\n", "utf8");
1765
+ await writeFile4(path9.join(cacheDir, "import-map.json"), JSON.stringify(map, null, 2) + "\n", "utf8");
1681
1766
  }
1682
1767
  async function pullCrossRepoSources(paths, config, projectRoot) {
1683
1768
  const sources = config.crossRepoSources ?? [];
@@ -1698,8 +1783,8 @@ async function pullFromSource(paths, source, projectRoot) {
1698
1783
  };
1699
1784
  let sourceRoot = null;
1700
1785
  if (source.path) {
1701
- const resolved = path8.resolve(projectRoot, source.path);
1702
- if (!existsSync6(resolved)) {
1786
+ const resolved = path9.resolve(projectRoot, source.path);
1787
+ if (!existsSync7(resolved)) {
1703
1788
  report.errors.push(`Path not found: ${resolved}`);
1704
1789
  return report;
1705
1790
  }
@@ -1712,7 +1797,7 @@ async function pullFromSource(paths, source, projectRoot) {
1712
1797
  return report;
1713
1798
  }
1714
1799
  const sourcePaths = resolveHaivePaths(sourceRoot);
1715
- if (!existsSync6(sourcePaths.memoriesDir)) {
1800
+ if (!existsSync7(sourcePaths.memoriesDir)) {
1716
1801
  report.errors.push(`No .ai/memories/ found at ${sourceRoot}`);
1717
1802
  return report;
1718
1803
  }
@@ -1735,10 +1820,10 @@ async function pullFromSource(paths, source, projectRoot) {
1735
1820
  report.skipped.push("no shared memories found in source");
1736
1821
  return report;
1737
1822
  }
1738
- const destDir = path8.join(paths.memoriesDir, "shared", source.name);
1739
- await mkdir3(destDir, { recursive: true });
1740
- const cacheDir = path8.join(paths.haiveDir, ".cache", "cross-repo", source.name);
1741
- await mkdir3(cacheDir, { recursive: true });
1823
+ const destDir = path9.join(paths.memoriesDir, "shared", source.name);
1824
+ await mkdir4(destDir, { recursive: true });
1825
+ const cacheDir = path9.join(paths.haiveDir, ".cache", "cross-repo", source.name);
1826
+ await mkdir4(cacheDir, { recursive: true });
1742
1827
  const importMap = await loadImportMap(cacheDir);
1743
1828
  const mapDirty = false;
1744
1829
  let dirty = mapDirty;
@@ -1752,7 +1837,7 @@ async function pullFromSource(paths, source, projectRoot) {
1752
1837
 
1753
1838
  `;
1754
1839
  const existingLocalPath = importMap[sourceId];
1755
- if (existingLocalPath && existsSync6(existingLocalPath)) {
1840
+ if (existingLocalPath && existsSync7(existingLocalPath)) {
1756
1841
  const existingFiles = await loadMemoriesFromDir(destDir);
1757
1842
  const existingEntry = existingFiles.find(({ filePath }) => filePath === existingLocalPath);
1758
1843
  const sourceBodyStripped = memory.body.trim();
@@ -1787,7 +1872,7 @@ async function pullFromSource(paths, source, projectRoot) {
1787
1872
  topic: fm.topic ? `${source.name}:${fm.topic}` : void 0
1788
1873
  });
1789
1874
  const body = importedBodyPrefix + memory.body;
1790
- const destPath = path8.join(destDir, `${newFm.id}.md`);
1875
+ const destPath = path9.join(destDir, `${newFm.id}.md`);
1791
1876
  await writeFile4(destPath, serializeMemory({ frontmatter: newFm, body }), "utf8");
1792
1877
  importMap[sourceId] = destPath;
1793
1878
  dirty = true;
@@ -1798,9 +1883,9 @@ async function pullFromSource(paths, source, projectRoot) {
1798
1883
  return report;
1799
1884
  }
1800
1885
  async function cloneOrFetchGitSource(source, paths, report) {
1801
- const cacheDir = path8.join(paths.haiveDir, ".cache", "cross-repo", source.name);
1802
- await mkdir3(cacheDir, { recursive: true });
1803
- if (existsSync6(path8.join(cacheDir, ".git"))) {
1886
+ const cacheDir = path9.join(paths.haiveDir, ".cache", "cross-repo", source.name);
1887
+ await mkdir4(cacheDir, { recursive: true });
1888
+ if (existsSync7(path9.join(cacheDir, ".git"))) {
1804
1889
  const result = spawnSync2("git", ["fetch", "--depth=1", "origin"], {
1805
1890
  cwd: cacheDir,
1806
1891
  encoding: "utf8"
@@ -1825,9 +1910,9 @@ async function cloneOrFetchGitSource(source, paths, report) {
1825
1910
  }
1826
1911
 
1827
1912
  // src/dep-tracker.ts
1828
- import { existsSync as existsSync7 } from "fs";
1829
- import { readFile as readFile7, writeFile as writeFile5, mkdir as mkdir4 } from "fs/promises";
1830
- import path9 from "path";
1913
+ import { existsSync as existsSync8 } from "fs";
1914
+ import { readFile as readFile8, writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
1915
+ import path10 from "path";
1831
1916
  function parsePackageJson(content) {
1832
1917
  try {
1833
1918
  const pkg = JSON.parse(content);
@@ -1899,7 +1984,7 @@ var KNOWN_MANIFESTS = [
1899
1984
  { name: "pom.xml", parser: parsePomXml }
1900
1985
  ];
1901
1986
  function getParser(file) {
1902
- const base = path9.basename(file);
1987
+ const base = path10.basename(file);
1903
1988
  return KNOWN_MANIFESTS.find((m) => m.name === base)?.parser ?? null;
1904
1989
  }
1905
1990
  function extractMajor(version) {
@@ -1917,32 +2002,32 @@ function isMajorBump(from, to) {
1917
2002
  }
1918
2003
  function resolveManifestFiles(projectRoot, configuredFiles) {
1919
2004
  if (configuredFiles !== void 0) {
1920
- return configuredFiles.map((f) => path9.resolve(projectRoot, f)).filter(existsSync7);
2005
+ return configuredFiles.map((f) => path10.resolve(projectRoot, f)).filter(existsSync8);
1921
2006
  }
1922
- return KNOWN_MANIFESTS.map(({ name }) => path9.join(projectRoot, name)).filter(existsSync7);
2007
+ return KNOWN_MANIFESTS.map(({ name }) => path10.join(projectRoot, name)).filter(existsSync8);
1923
2008
  }
1924
2009
  async function trackDependencies(projectRoot, haiveDir, manifestFiles) {
1925
- const contractsDir = path9.join(haiveDir, "contracts");
1926
- await mkdir4(contractsDir, { recursive: true });
2010
+ const contractsDir = path10.join(haiveDir, "contracts");
2011
+ await mkdir5(contractsDir, { recursive: true });
1927
2012
  const results = [];
1928
2013
  for (const manifestPath of manifestFiles) {
1929
2014
  const parser = getParser(manifestPath);
1930
2015
  if (!parser) continue;
1931
- const content = await readFile7(manifestPath, "utf8");
2016
+ const content = await readFile8(manifestPath, "utf8");
1932
2017
  const currentDeps = parser(content);
1933
- const lockName = `deps-${path9.basename(manifestPath)}.lock`;
1934
- const lockPath = path9.join(contractsDir, lockName);
1935
- if (!existsSync7(lockPath)) {
2018
+ const lockName = `deps-${path10.basename(manifestPath)}.lock`;
2019
+ const lockPath = path10.join(contractsDir, lockName);
2020
+ if (!existsSync8(lockPath)) {
1936
2021
  const snapshot2 = {
1937
- file: path9.relative(projectRoot, manifestPath),
1938
- format: path9.basename(manifestPath),
2022
+ file: path10.relative(projectRoot, manifestPath),
2023
+ format: path10.basename(manifestPath),
1939
2024
  captured_at: (/* @__PURE__ */ new Date()).toISOString(),
1940
2025
  deps: currentDeps
1941
2026
  };
1942
2027
  await writeFile5(lockPath, JSON.stringify(snapshot2, null, 2) + "\n", "utf8");
1943
2028
  continue;
1944
2029
  }
1945
- const snapshot = JSON.parse(await readFile7(lockPath, "utf8"));
2030
+ const snapshot = JSON.parse(await readFile8(lockPath, "utf8"));
1946
2031
  const changes = [];
1947
2032
  for (const [name, currentVer] of Object.entries(currentDeps)) {
1948
2033
  const prevVer = snapshot.deps[name];
@@ -1956,7 +2041,7 @@ async function trackDependencies(projectRoot, haiveDir, manifestFiles) {
1956
2041
  }
1957
2042
  }
1958
2043
  if (changes.length > 0) {
1959
- results.push({ file: path9.relative(projectRoot, manifestPath), changes });
2044
+ results.push({ file: path10.relative(projectRoot, manifestPath), changes });
1960
2045
  const updated = {
1961
2046
  ...snapshot,
1962
2047
  captured_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1969,9 +2054,9 @@ async function trackDependencies(projectRoot, haiveDir, manifestFiles) {
1969
2054
  }
1970
2055
 
1971
2056
  // src/contract-watcher.ts
1972
- import { existsSync as existsSync8 } from "fs";
1973
- import { readFile as readFile8, writeFile as writeFile6, mkdir as mkdir5 } from "fs/promises";
1974
- import path10 from "path";
2057
+ import { existsSync as existsSync9 } from "fs";
2058
+ import { readFile as readFile9, writeFile as writeFile6, mkdir as mkdir6 } from "fs/promises";
2059
+ import path11 from "path";
1975
2060
  import crypto from "crypto";
1976
2061
  function sha256(content) {
1977
2062
  return crypto.createHash("sha256").update(content).digest("hex");
@@ -2174,14 +2259,14 @@ function diffSnapshots(before, after) {
2174
2259
  return changes;
2175
2260
  }
2176
2261
  function contractLockPath(haiveDir, name) {
2177
- return path10.join(haiveDir, "contracts", `${name}.lock`);
2262
+ return path11.join(haiveDir, "contracts", `${name}.lock`);
2178
2263
  }
2179
2264
  async function snapshotContract(projectRoot, haiveDir, contract) {
2180
- const filePath = path10.resolve(projectRoot, contract.path);
2181
- if (!existsSync8(filePath)) {
2265
+ const filePath = path11.resolve(projectRoot, contract.path);
2266
+ if (!existsSync9(filePath)) {
2182
2267
  throw new Error(`Contract file not found: ${filePath}`);
2183
2268
  }
2184
- const content = await readFile8(filePath, "utf8");
2269
+ const content = await readFile9(filePath, "utf8");
2185
2270
  const parsed = parseByFormat(content, contract.format, filePath);
2186
2271
  const snapshot = {
2187
2272
  name: contract.name,
@@ -2191,23 +2276,23 @@ async function snapshotContract(projectRoot, haiveDir, contract) {
2191
2276
  hash: sha256(content),
2192
2277
  ...parsed
2193
2278
  };
2194
- const contractsDir = path10.join(haiveDir, "contracts");
2195
- await mkdir5(contractsDir, { recursive: true });
2279
+ const contractsDir = path11.join(haiveDir, "contracts");
2280
+ await mkdir6(contractsDir, { recursive: true });
2196
2281
  await writeFile6(contractLockPath(haiveDir, contract.name), JSON.stringify(snapshot, null, 2) + "\n", "utf8");
2197
2282
  return snapshot;
2198
2283
  }
2199
2284
  async function diffContract(projectRoot, haiveDir, contract) {
2200
- const filePath = path10.resolve(projectRoot, contract.path);
2201
- if (!existsSync8(filePath)) {
2285
+ const filePath = path11.resolve(projectRoot, contract.path);
2286
+ if (!existsSync9(filePath)) {
2202
2287
  return { contract: contract.name, file: contract.path, changes: [], unchanged: true };
2203
2288
  }
2204
2289
  const lockPath = contractLockPath(haiveDir, contract.name);
2205
- if (!existsSync8(lockPath)) {
2290
+ if (!existsSync9(lockPath)) {
2206
2291
  await snapshotContract(projectRoot, haiveDir, contract);
2207
2292
  return { contract: contract.name, file: contract.path, changes: [], unchanged: true };
2208
2293
  }
2209
- const content = await readFile8(filePath, "utf8");
2210
- const beforeSnapshot = JSON.parse(await readFile8(lockPath, "utf8"));
2294
+ const content = await readFile9(filePath, "utf8");
2295
+ const beforeSnapshot = JSON.parse(await readFile9(lockPath, "utf8"));
2211
2296
  const afterParsed = parseByFormat(content, contract.format, filePath);
2212
2297
  const afterSnapshot = {
2213
2298
  ...beforeSnapshot,
@@ -2235,27 +2320,27 @@ async function watchContracts(projectRoot, haiveDir, contractFiles) {
2235
2320
  }
2236
2321
 
2237
2322
  // src/usage-log.ts
2238
- import { appendFile, mkdir as mkdir6, readFile as readFile9, stat as stat2 } from "fs/promises";
2239
- import { existsSync as existsSync9 } from "fs";
2240
- import path11 from "path";
2323
+ import { appendFile as appendFile2, mkdir as mkdir7, readFile as readFile10, stat as stat2 } from "fs/promises";
2324
+ import { existsSync as existsSync10 } from "fs";
2325
+ import path12 from "path";
2241
2326
  var USAGE_LOG_FILE = "tool-usage.jsonl";
2242
2327
  var USAGE_LOG_DIR = ".usage";
2243
2328
  function usageLogPath(paths) {
2244
- return path11.join(paths.haiveDir, USAGE_LOG_DIR, USAGE_LOG_FILE);
2329
+ return path12.join(paths.haiveDir, USAGE_LOG_DIR, USAGE_LOG_FILE);
2245
2330
  }
2246
2331
  async function appendUsageEvent(paths, event) {
2247
2332
  try {
2248
2333
  const file = usageLogPath(paths);
2249
- const dir = path11.dirname(file);
2250
- if (!existsSync9(dir)) await mkdir6(dir, { recursive: true });
2251
- await appendFile(file, JSON.stringify(event) + "\n", "utf8");
2334
+ const dir = path12.dirname(file);
2335
+ if (!existsSync10(dir)) await mkdir7(dir, { recursive: true });
2336
+ await appendFile2(file, JSON.stringify(event) + "\n", "utf8");
2252
2337
  } catch {
2253
2338
  }
2254
2339
  }
2255
2340
  async function readUsageEvents(paths) {
2256
2341
  const file = usageLogPath(paths);
2257
- if (!existsSync9(file)) return [];
2258
- const raw = await readFile9(file, "utf8");
2342
+ if (!existsSync10(file)) return [];
2343
+ const raw = await readFile10(file, "utf8");
2259
2344
  const out = [];
2260
2345
  for (const line of raw.split("\n")) {
2261
2346
  if (!line) continue;
@@ -2304,9 +2389,9 @@ function parseSince(input) {
2304
2389
  }
2305
2390
  async function usageLogSize(paths) {
2306
2391
  const file = usageLogPath(paths);
2307
- if (!existsSync9(file)) return { exists: false, size_bytes: 0, lines: 0 };
2392
+ if (!existsSync10(file)) return { exists: false, size_bytes: 0, lines: 0 };
2308
2393
  const st = await stat2(file);
2309
- const raw = await readFile9(file, "utf8");
2394
+ const raw = await readFile10(file, "utf8");
2310
2395
  return { exists: true, size_bytes: st.size, lines: raw.split("\n").filter((l) => l).length };
2311
2396
  }
2312
2397
 
@@ -2376,21 +2461,21 @@ function extractActionsBriefBody(markdown, maxChars = MAX_DEFAULT_CHARS) {
2376
2461
  }
2377
2462
 
2378
2463
  // src/resolve-project.ts
2379
- import { existsSync as existsSync10 } from "fs";
2380
- import path12 from "path";
2464
+ import { existsSync as existsSync11 } from "fs";
2465
+ import path13 from "path";
2381
2466
  var ROOT_MARKERS2 = [".ai", ".git", "package.json"];
2382
2467
  function markersAtRoot(root) {
2383
2468
  const found = [];
2384
2469
  for (const m of ROOT_MARKERS2) {
2385
- if (existsSync10(path12.join(root, m))) found.push(m);
2470
+ if (existsSync11(path13.join(root, m))) found.push(m);
2386
2471
  }
2387
2472
  return found;
2388
2473
  }
2389
2474
  function resolveProjectInfo(opts = {}) {
2390
2475
  const env = opts.env ?? process.env;
2391
- const cwd = path12.resolve(opts.cwd ?? process.cwd());
2476
+ const cwd = path13.resolve(opts.cwd ?? process.cwd());
2392
2477
  const raw = env.HAIVE_PROJECT_ROOT;
2393
- const explicit = raw !== void 0 && raw !== "" ? path12.resolve(raw) : null;
2478
+ const explicit = raw !== void 0 && raw !== "" ? path13.resolve(raw) : null;
2394
2479
  const resolvedRoot = explicit ?? findProjectRoot(cwd);
2395
2480
  const paths = resolveHaivePaths(resolvedRoot);
2396
2481
  return {
@@ -2398,8 +2483,8 @@ function resolveProjectInfo(opts = {}) {
2398
2483
  resolved_root: resolvedRoot,
2399
2484
  haive_project_root_env: explicit,
2400
2485
  explicit_root: explicit != null,
2401
- haive_dir_exists: existsSync10(paths.haiveDir),
2402
- memories_dir_exists: existsSync10(paths.memoriesDir),
2486
+ haive_dir_exists: existsSync11(paths.haiveDir),
2487
+ memories_dir_exists: existsSync11(paths.memoriesDir),
2403
2488
  runtime_dir: paths.runtimeDir,
2404
2489
  markers_found: markersAtRoot(resolvedRoot)
2405
2490
  };
@@ -2654,16 +2739,16 @@ function findLexicalConflictPairs(memories, opts) {
2654
2739
  }
2655
2740
 
2656
2741
  // src/runtime-journal.ts
2657
- import { mkdir as mkdir7, readFile as readFile10, appendFile as appendFile2 } from "fs/promises";
2658
- import { existsSync as existsSync11 } from "fs";
2659
- import path13 from "path";
2742
+ import { mkdir as mkdir8, readFile as readFile11, appendFile as appendFile3 } from "fs/promises";
2743
+ import { existsSync as existsSync12 } from "fs";
2744
+ import path14 from "path";
2660
2745
  var RUNTIME_JOURNAL_FILENAME = "session-journal.ndjson";
2661
2746
  function runtimeJournalPath(paths) {
2662
- return path13.join(paths.runtimeDir, RUNTIME_JOURNAL_FILENAME);
2747
+ return path14.join(paths.runtimeDir, RUNTIME_JOURNAL_FILENAME);
2663
2748
  }
2664
2749
  async function appendRuntimeJournalEntry(paths, entry) {
2665
2750
  try {
2666
- await mkdir7(paths.runtimeDir, { recursive: true });
2751
+ await mkdir8(paths.runtimeDir, { recursive: true });
2667
2752
  const line = {
2668
2753
  ts: entry.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
2669
2754
  kind: entry.kind,
@@ -2671,7 +2756,7 @@ async function appendRuntimeJournalEntry(paths, entry) {
2671
2756
  ...entry.tool !== void 0 ? { tool: entry.tool } : {},
2672
2757
  ...entry.meta !== void 0 ? { meta: entry.meta } : {}
2673
2758
  };
2674
- await appendFile2(
2759
+ await appendFile3(
2675
2760
  runtimeJournalPath(paths),
2676
2761
  JSON.stringify(line) + "\n",
2677
2762
  "utf8"
@@ -2681,9 +2766,9 @@ async function appendRuntimeJournalEntry(paths, entry) {
2681
2766
  }
2682
2767
  async function readRuntimeJournalTail(paths, limit) {
2683
2768
  const file = runtimeJournalPath(paths);
2684
- if (!existsSync11(file) || limit <= 0) return [];
2769
+ if (!existsSync12(file) || limit <= 0) return [];
2685
2770
  try {
2686
- const raw = await readFile10(file, "utf8");
2771
+ const raw = await readFile11(file, "utf8");
2687
2772
  const lines = raw.trim().split("\n").filter(Boolean);
2688
2773
  const parsed = [];
2689
2774
  for (const line of lines.slice(-limit)) {
@@ -2699,22 +2784,22 @@ async function readRuntimeJournalTail(paths, limit) {
2699
2784
  }
2700
2785
 
2701
2786
  // src/enforcement.ts
2702
- import { mkdir as mkdir8, readdir as readdir4, readFile as readFile11, writeFile as writeFile7 } from "fs/promises";
2703
- import { existsSync as existsSync12 } from "fs";
2704
- import path14 from "path";
2787
+ import { mkdir as mkdir9, readdir as readdir4, readFile as readFile12, writeFile as writeFile7 } from "fs/promises";
2788
+ import { existsSync as existsSync13 } from "fs";
2789
+ import path15 from "path";
2705
2790
  var BRIEFING_MARKER_TTL_MS = 12 * 60 * 60 * 1e3;
2706
2791
  var SESSION_RECAP_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
2707
2792
  function enforcementDir(paths) {
2708
- return path14.join(paths.runtimeDir, "enforcement");
2793
+ return path15.join(paths.runtimeDir, "enforcement");
2709
2794
  }
2710
2795
  function briefingMarkersDir(paths) {
2711
- return path14.join(enforcementDir(paths), "briefings");
2796
+ return path15.join(enforcementDir(paths), "briefings");
2712
2797
  }
2713
2798
  function normalizeSessionId(sessionId) {
2714
2799
  return (sessionId?.trim() || "default").replace(/[^a-zA-Z0-9_.-]+/g, "-").slice(0, 120);
2715
2800
  }
2716
2801
  function briefingMarkerPath(paths, sessionId) {
2717
- return path14.join(briefingMarkersDir(paths), `${normalizeSessionId(sessionId)}.json`);
2802
+ return path15.join(briefingMarkersDir(paths), `${normalizeSessionId(sessionId)}.json`);
2718
2803
  }
2719
2804
  async function writeBriefingMarker(paths, input) {
2720
2805
  const marker = {
@@ -2726,7 +2811,7 @@ async function writeBriefingMarker(paths, input) {
2726
2811
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
2727
2812
  root: paths.root
2728
2813
  };
2729
- await mkdir8(briefingMarkersDir(paths), { recursive: true });
2814
+ await mkdir9(briefingMarkersDir(paths), { recursive: true });
2730
2815
  await writeFile7(
2731
2816
  briefingMarkerPath(paths, marker.session_id),
2732
2817
  JSON.stringify(marker, null, 2) + "\n",
@@ -2738,18 +2823,18 @@ async function hasRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKER
2738
2823
  const now = Date.now();
2739
2824
  const candidates = [];
2740
2825
  const exact = briefingMarkerPath(paths, sessionId);
2741
- if (existsSync12(exact)) candidates.push(exact);
2826
+ if (existsSync13(exact)) candidates.push(exact);
2742
2827
  try {
2743
2828
  const dir = briefingMarkersDir(paths);
2744
2829
  const files = await readdir4(dir);
2745
2830
  for (const file of files) {
2746
- if (file.endsWith(".json")) candidates.push(path14.join(dir, file));
2831
+ if (file.endsWith(".json")) candidates.push(path15.join(dir, file));
2747
2832
  }
2748
2833
  } catch {
2749
2834
  }
2750
2835
  for (const file of new Set(candidates)) {
2751
2836
  try {
2752
- const marker = JSON.parse(await readFile11(file, "utf8"));
2837
+ const marker = JSON.parse(await readFile12(file, "utf8"));
2753
2838
  const created = Date.parse(marker.created_at);
2754
2839
  if (Number.isFinite(created) && now - created <= ttlMs) return true;
2755
2840
  } catch {
@@ -2761,12 +2846,12 @@ async function readRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKE
2761
2846
  const now = Date.now();
2762
2847
  const candidates = [];
2763
2848
  const exact = briefingMarkerPath(paths, sessionId);
2764
- if (existsSync12(exact)) candidates.push(exact);
2849
+ if (existsSync13(exact)) candidates.push(exact);
2765
2850
  try {
2766
2851
  const dir = briefingMarkersDir(paths);
2767
2852
  const files = await readdir4(dir);
2768
2853
  for (const file of files) {
2769
- if (file.endsWith(".json")) candidates.push(path14.join(dir, file));
2854
+ if (file.endsWith(".json")) candidates.push(path15.join(dir, file));
2770
2855
  }
2771
2856
  } catch {
2772
2857
  }
@@ -2774,7 +2859,7 @@ async function readRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKE
2774
2859
  let freshestTs = 0;
2775
2860
  for (const file of new Set(candidates)) {
2776
2861
  try {
2777
- const marker = JSON.parse(await readFile11(file, "utf8"));
2862
+ const marker = JSON.parse(await readFile12(file, "utf8"));
2778
2863
  const created = Date.parse(marker.created_at);
2779
2864
  if (!Number.isFinite(created) || now - created > ttlMs) continue;
2780
2865
  if (created > freshestTs) {
@@ -2827,10 +2912,10 @@ function isRetiredMemory(fm, body = "", now = /* @__PURE__ */ new Date()) {
2827
2912
  function normalizeProjectPath(value) {
2828
2913
  return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^[ab]\//, "").replace(/\/+$/g, "");
2829
2914
  }
2830
- function sensorAppliesToPath(sensor, anchorPaths, path15) {
2915
+ function sensorAppliesToPath(sensor, anchorPaths, path16) {
2831
2916
  const scopes = sensor.paths.length > 0 ? sensor.paths : anchorPaths;
2832
2917
  if (scopes.length === 0) return true;
2833
- const target = normalizeProjectPath(path15);
2918
+ const target = normalizeProjectPath(path16);
2834
2919
  return scopes.some((rawScope) => {
2835
2920
  const scope = normalizeProjectPath(rawScope);
2836
2921
  if (!scope) return false;
@@ -3104,8 +3189,8 @@ function normalizeFindingSeverity(raw) {
3104
3189
  return "info";
3105
3190
  }
3106
3191
  }
3107
- function findingKey(tool, ruleId, path15) {
3108
- return `${tool}:${ruleId}:${path15}`;
3192
+ function findingKey(tool, ruleId, path16) {
3193
+ return `${tool}:${ruleId}:${path16}`;
3109
3194
  }
3110
3195
  function coerceJson(input) {
3111
3196
  if (typeof input === "string") {
@@ -3139,8 +3224,8 @@ function parseSarif(input) {
3139
3224
  const physical = asRecord(location.physicalLocation);
3140
3225
  const artifact = asRecord(physical.artifactLocation);
3141
3226
  const region = asRecord(physical.region);
3142
- const path15 = typeof artifact.uri === "string" ? normalizeUri(artifact.uri) : "";
3143
- if (!path15) continue;
3227
+ const path16 = typeof artifact.uri === "string" ? normalizeUri(artifact.uri) : "";
3228
+ if (!path16) continue;
3144
3229
  const line = typeof region.startLine === "number" ? region.startLine : void 0;
3145
3230
  const snippet = typeof asRecord(region.snippet).text === "string" ? asRecord(region.snippet).text.trim() : void 0;
3146
3231
  findings.push({
@@ -3148,10 +3233,10 @@ function parseSarif(input) {
3148
3233
  ruleId,
3149
3234
  message: message.trim(),
3150
3235
  severity,
3151
- path: path15,
3236
+ path: path16,
3152
3237
  ...line !== void 0 ? { line } : {},
3153
3238
  ...snippet ? { snippet } : {},
3154
- key: findingKey(tool, ruleId, path15)
3239
+ key: findingKey(tool, ruleId, path16)
3155
3240
  });
3156
3241
  }
3157
3242
  }
@@ -3170,17 +3255,17 @@ function parseSonar(input) {
3170
3255
  (typeof issue.severity === "string" ? issue.severity : void 0) ?? impactSeverity
3171
3256
  );
3172
3257
  const component = typeof issue.component === "string" ? issue.component : "";
3173
- const path15 = componentToPath(component);
3174
- if (!path15) continue;
3258
+ const path16 = componentToPath(component);
3259
+ if (!path16) continue;
3175
3260
  const line = typeof issue.line === "number" ? issue.line : void 0;
3176
3261
  findings.push({
3177
3262
  tool: "sonar",
3178
3263
  ruleId,
3179
3264
  message,
3180
3265
  severity,
3181
- path: path15,
3266
+ path: path16,
3182
3267
  ...line !== void 0 ? { line } : {},
3183
- key: findingKey("sonar", ruleId, path15)
3268
+ key: findingKey("sonar", ruleId, path16)
3184
3269
  });
3185
3270
  }
3186
3271
  return findings;
@@ -3258,7 +3343,7 @@ function filterNewDrafts(drafts, existingTopics) {
3258
3343
  }
3259
3344
 
3260
3345
  // src/dashboard.ts
3261
- var MS_PER_DAY3 = 24 * 60 * 60 * 1e3;
3346
+ var MS_PER_DAY4 = 24 * 60 * 60 * 1e3;
3262
3347
  function isAnchorless(fm) {
3263
3348
  if (!["decision", "gotcha", "architecture"].includes(fm.type)) return false;
3264
3349
  if (fm.status !== "validated") return false;
@@ -3294,6 +3379,8 @@ function buildDashboard(memories, usage, options = {}) {
3294
3379
  let decaying = 0;
3295
3380
  let bodyChars = 0;
3296
3381
  const dormantRows = [];
3382
+ let preventionEvents = 0;
3383
+ const preventionRows = [];
3297
3384
  for (const { memory } of memories) {
3298
3385
  const fm = memory.frontmatter;
3299
3386
  if (fm.type === "session_recap") {
@@ -3338,10 +3425,19 @@ function buildDashboard(memories, usage, options = {}) {
3338
3425
  signals: impact.signals,
3339
3426
  prune_candidate: impact.pruneCandidate
3340
3427
  });
3428
+ if (memUsage.prevented_count > 0) {
3429
+ preventionEvents += memUsage.prevented_count;
3430
+ preventionRows.push({
3431
+ id: fm.id,
3432
+ type: fm.type,
3433
+ prevented_count: memUsage.prevented_count,
3434
+ last_prevented_at: memUsage.last_prevented_at
3435
+ });
3436
+ }
3341
3437
  if (isDecaying(memUsage, fm.created_at)) decaying += 1;
3342
3438
  if (impact.tier === "dormant") {
3343
3439
  const anchor = memUsage.last_read_at ?? fm.created_at;
3344
- const ageDays = Math.floor((now.getTime() - new Date(anchor).getTime()) / MS_PER_DAY3);
3440
+ const ageDays = Math.floor((now.getTime() - new Date(anchor).getTime()) / MS_PER_DAY4);
3345
3441
  dormantRows.push({ id: fm.id, last_read_at: memUsage.last_read_at, age_days: ageDays });
3346
3442
  }
3347
3443
  }
@@ -3377,6 +3473,16 @@ function buildDashboard(memories, usage, options = {}) {
3377
3473
  decaying,
3378
3474
  top_dormant: dormantRows.slice(0, top)
3379
3475
  },
3476
+ prevention: {
3477
+ total_events: preventionEvents,
3478
+ memories_with_catches: preventionRows.length,
3479
+ top: preventionRows.sort((a, b) => b.prevented_count - a.prevented_count).slice(0, top),
3480
+ trend: computePreventionTrend(options.preventionEvents ?? [], now),
3481
+ recurrence: {
3482
+ ...computeRecurrence(options.preventionEvents ?? []),
3483
+ top: computeRecurrence(options.preventionEvents ?? []).top.slice(0, top)
3484
+ }
3485
+ },
3380
3486
  corpus: {
3381
3487
  memory_files: inventory.total,
3382
3488
  body_chars: bodyChars,
@@ -3408,6 +3514,7 @@ export {
3408
3514
  MemoryScopeSchema,
3409
3515
  MemoryStatusSchema,
3410
3516
  MemoryTypeSchema,
3517
+ PREVENTION_DEBOUNCE_MS,
3411
3518
  PROJECT_CONTEXT_FILE,
3412
3519
  RUNTIME_JOURNAL_FILENAME,
3413
3520
  SESSION_RECAP_TTL_MS,
@@ -3422,6 +3529,7 @@ export {
3422
3529
  aggregateUsage,
3423
3530
  allocateBudget,
3424
3531
  antiPatternGateParams,
3532
+ appendPreventionEvent,
3425
3533
  appendRuntimeJournalEntry,
3426
3534
  appendUsageEvent,
3427
3535
  briefingMarkerPath,
@@ -3438,6 +3546,8 @@ export {
3438
3546
  compareImpact,
3439
3547
  compileRegexSensor,
3440
3548
  computeImpact,
3549
+ computePreventionTrend,
3550
+ computeRecurrence,
3441
3551
  configPath,
3442
3552
  contractLockPath,
3443
3553
  deriveConfidence,
@@ -3481,6 +3591,7 @@ export {
3481
3591
  loadConfigSync,
3482
3592
  loadMemoriesFromDir,
3483
3593
  loadMemory,
3594
+ loadPreventionEvents,
3484
3595
  loadUsageIndex,
3485
3596
  memoryFilePath,
3486
3597
  memoryMatchesAnchorPaths,
@@ -3495,6 +3606,7 @@ export {
3495
3606
  parseSonar,
3496
3607
  pathsOverlap,
3497
3608
  pickSnippetNeedle,
3609
+ preventionLogPath,
3498
3610
  pullCrossRepoSources,
3499
3611
  queryCodeMap,
3500
3612
  rankMemoriesLexical,
@@ -3502,6 +3614,7 @@ export {
3502
3614
  readRuntimeJournalTail,
3503
3615
  readUsageEvents,
3504
3616
  recordApplied,
3617
+ recordPrevention,
3505
3618
  recordRejection,
3506
3619
  relPathFrom,
3507
3620
  resolveBriefingBudget,