@hiveai/core 0.13.8 → 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
@@ -791,6 +791,71 @@ function summarizeImpact(scores) {
791
791
  return summary;
792
792
  }
793
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
+
794
859
  // src/eval.ts
795
860
  function round32(n) {
796
861
  return Math.round(n * 1e3) / 1e3;
@@ -926,13 +991,13 @@ var DEFAULT_CONFIDENCE_THRESHOLDS = {
926
991
  decayDays: 180,
927
992
  hardDecayDays: 365
928
993
  };
929
- var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
994
+ var MS_PER_DAY3 = 24 * 60 * 60 * 1e3;
930
995
  function deriveConfidence(fm, usage, thresholds = DEFAULT_CONFIDENCE_THRESHOLDS, now = /* @__PURE__ */ new Date()) {
931
996
  if (fm.status === "stale" || fm.status === "deprecated" || fm.status === "rejected") return "stale";
932
997
  const baseLevel = baseConfidence(fm, usage, thresholds);
933
998
  if (baseLevel !== "authoritative" && baseLevel !== "trusted") return baseLevel;
934
999
  const anchor = usage.last_read_at ?? fm.created_at;
935
- const ageDays = (now.getTime() - new Date(anchor).getTime()) / MS_PER_DAY2;
1000
+ const ageDays = (now.getTime() - new Date(anchor).getTime()) / MS_PER_DAY3;
936
1001
  if (Number.isNaN(ageDays) || ageDays <= 0) return baseLevel;
937
1002
  if (ageDays >= thresholds.hardDecayDays) {
938
1003
  return "low";
@@ -1226,10 +1291,10 @@ function allocateBudget(parts, maxTokens) {
1226
1291
  }
1227
1292
 
1228
1293
  // src/code-map.ts
1229
- import { mkdir as mkdir2, readFile as readFile4, readdir as readdir3, writeFile as writeFile2 } from "fs/promises";
1230
- 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";
1231
1296
  import { spawnSync } from "child_process";
1232
- import path6 from "path";
1297
+ import path7 from "path";
1233
1298
  var CODE_MAP_FILE = "code-map.json";
1234
1299
  var DEFAULT_INCLUDE = [
1235
1300
  ".ts",
@@ -1271,16 +1336,16 @@ var DEFAULT_EXCLUDE = [
1271
1336
  ];
1272
1337
  var TEST_FILE_RE = /\.(test|spec)\.[a-z]+$/i;
1273
1338
  function codeMapPath(paths) {
1274
- return path6.join(paths.haiveDir, CODE_MAP_FILE);
1339
+ return path7.join(paths.haiveDir, CODE_MAP_FILE);
1275
1340
  }
1276
1341
  async function loadCodeMap(paths) {
1277
1342
  const file = codeMapPath(paths);
1278
- if (!existsSync4(file)) return null;
1279
- return JSON.parse(await readFile4(file, "utf8"));
1343
+ if (!existsSync5(file)) return null;
1344
+ return JSON.parse(await readFile5(file, "utf8"));
1280
1345
  }
1281
1346
  async function saveCodeMap(paths, map) {
1282
1347
  const file = codeMapPath(paths);
1283
- await mkdir2(path6.dirname(file), { recursive: true });
1348
+ await mkdir3(path7.dirname(file), { recursive: true });
1284
1349
  await writeFile2(file, JSON.stringify(map, null, 2), "utf8");
1285
1350
  }
1286
1351
  async function buildCodeMap(root, options = {}) {
@@ -1288,10 +1353,10 @@ async function buildCodeMap(root, options = {}) {
1288
1353
  const exclude = new Set(options.excludeDirs ?? DEFAULT_EXCLUDE);
1289
1354
  const files = {};
1290
1355
  for await (const abs of collectSourceFiles(root, include, exclude, options.includeUntracked)) {
1291
- const rel = path6.relative(root, abs).replace(/\\/g, "/");
1356
+ const rel = path7.relative(root, abs).replace(/\\/g, "/");
1292
1357
  if (rel.startsWith(".ai/")) continue;
1293
- const content = await readFile4(abs, "utf8");
1294
- const ext = path6.extname(abs).toLowerCase();
1358
+ const content = await readFile5(abs, "utf8");
1359
+ const ext = path7.extname(abs).toLowerCase();
1295
1360
  const entry = parseFile(content, ext);
1296
1361
  if (entry.exports.length > 0) files[rel] = entry;
1297
1362
  }
@@ -1305,7 +1370,7 @@ async function buildCodeMap(root, options = {}) {
1305
1370
  async function* collectSourceFiles(root, include, exclude, includeUntracked) {
1306
1371
  const gitFiles = gitSourceFiles(root, include, exclude, includeUntracked === true);
1307
1372
  if (gitFiles) {
1308
- for (const rel of gitFiles) yield path6.join(root, rel);
1373
+ for (const rel of gitFiles) yield path7.join(root, rel);
1309
1374
  return;
1310
1375
  }
1311
1376
  yield* walkSourceFiles(root, include, exclude);
@@ -1332,11 +1397,11 @@ async function* walkSourceFiles(dir, include, exclude) {
1332
1397
  if (entry.isDirectory()) continue;
1333
1398
  }
1334
1399
  if (exclude.has(entry.name)) continue;
1335
- const full = path6.join(dir, entry.name);
1400
+ const full = path7.join(dir, entry.name);
1336
1401
  if (entry.isDirectory()) {
1337
1402
  yield* walkSourceFiles(full, include, exclude);
1338
1403
  } else if (entry.isFile()) {
1339
- const ext = path6.extname(entry.name).toLowerCase();
1404
+ const ext = path7.extname(entry.name).toLowerCase();
1340
1405
  if (include.has(ext) && !TEST_FILE_RE.test(entry.name)) yield full;
1341
1406
  }
1342
1407
  }
@@ -1347,7 +1412,7 @@ function isIncludedSourcePath(rel, include, exclude) {
1347
1412
  const parts = normalized.split("/");
1348
1413
  if (parts.some((part) => exclude.has(part))) return false;
1349
1414
  const base = parts.at(-1) ?? "";
1350
- const ext = path6.extname(base).toLowerCase();
1415
+ const ext = path7.extname(base).toLowerCase();
1351
1416
  return include.has(ext) && !TEST_FILE_RE.test(base);
1352
1417
  }
1353
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;
@@ -1561,10 +1626,10 @@ function queryCodeMap(map, options) {
1561
1626
  }
1562
1627
 
1563
1628
  // src/config.ts
1564
- import { existsSync as existsSync5 } from "fs";
1629
+ import { existsSync as existsSync6 } from "fs";
1565
1630
  import { readFileSync } from "fs";
1566
- import { readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
1567
- import path7 from "path";
1631
+ import { readFile as readFile6, writeFile as writeFile3 } from "fs/promises";
1632
+ import path8 from "path";
1568
1633
  var CONFIG_FILE = "haive.config.json";
1569
1634
  var DEFAULT_CONFIG = {
1570
1635
  autopilot: false,
@@ -1636,13 +1701,13 @@ function antiPatternGateParams(gate) {
1636
1701
  }
1637
1702
  }
1638
1703
  function configPath(paths) {
1639
- return path7.join(paths.haiveDir, CONFIG_FILE);
1704
+ return path8.join(paths.haiveDir, CONFIG_FILE);
1640
1705
  }
1641
1706
  async function loadConfig(paths) {
1642
1707
  const file = configPath(paths);
1643
- if (!existsSync5(file)) return { ...DEFAULT_CONFIG };
1708
+ if (!existsSync6(file)) return { ...DEFAULT_CONFIG };
1644
1709
  try {
1645
- const raw = await readFile5(file, "utf8");
1710
+ const raw = await readFile6(file, "utf8");
1646
1711
  const parsed = JSON.parse(raw);
1647
1712
  const merged = mergeConfig(DEFAULT_CONFIG, parsed);
1648
1713
  if (merged.autopilot) {
@@ -1655,7 +1720,7 @@ async function loadConfig(paths) {
1655
1720
  }
1656
1721
  function loadConfigSync(paths) {
1657
1722
  const file = configPath(paths);
1658
- if (!existsSync5(file)) return { ...DEFAULT_CONFIG };
1723
+ if (!existsSync6(file)) return { ...DEFAULT_CONFIG };
1659
1724
  try {
1660
1725
  const parsed = JSON.parse(readFileSync(file, "utf8"));
1661
1726
  const merged = mergeConfig(DEFAULT_CONFIG, parsed);
@@ -1683,21 +1748,21 @@ function mergeConfig(base, override) {
1683
1748
  }
1684
1749
 
1685
1750
  // src/cross-repo.ts
1686
- import { existsSync as existsSync6 } from "fs";
1687
- import { mkdir as mkdir3, readFile as readFile6, writeFile as writeFile4 } from "fs/promises";
1688
- 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";
1689
1754
  import { spawnSync as spawnSync2 } from "child_process";
1690
1755
  async function loadImportMap(cacheDir) {
1691
- const mapPath = path8.join(cacheDir, "import-map.json");
1692
- if (!existsSync6(mapPath)) return {};
1756
+ const mapPath = path9.join(cacheDir, "import-map.json");
1757
+ if (!existsSync7(mapPath)) return {};
1693
1758
  try {
1694
- return JSON.parse(await readFile6(mapPath, "utf8"));
1759
+ return JSON.parse(await readFile7(mapPath, "utf8"));
1695
1760
  } catch {
1696
1761
  return {};
1697
1762
  }
1698
1763
  }
1699
1764
  async function saveImportMap(cacheDir, map) {
1700
- 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");
1701
1766
  }
1702
1767
  async function pullCrossRepoSources(paths, config, projectRoot) {
1703
1768
  const sources = config.crossRepoSources ?? [];
@@ -1718,8 +1783,8 @@ async function pullFromSource(paths, source, projectRoot) {
1718
1783
  };
1719
1784
  let sourceRoot = null;
1720
1785
  if (source.path) {
1721
- const resolved = path8.resolve(projectRoot, source.path);
1722
- if (!existsSync6(resolved)) {
1786
+ const resolved = path9.resolve(projectRoot, source.path);
1787
+ if (!existsSync7(resolved)) {
1723
1788
  report.errors.push(`Path not found: ${resolved}`);
1724
1789
  return report;
1725
1790
  }
@@ -1732,7 +1797,7 @@ async function pullFromSource(paths, source, projectRoot) {
1732
1797
  return report;
1733
1798
  }
1734
1799
  const sourcePaths = resolveHaivePaths(sourceRoot);
1735
- if (!existsSync6(sourcePaths.memoriesDir)) {
1800
+ if (!existsSync7(sourcePaths.memoriesDir)) {
1736
1801
  report.errors.push(`No .ai/memories/ found at ${sourceRoot}`);
1737
1802
  return report;
1738
1803
  }
@@ -1755,10 +1820,10 @@ async function pullFromSource(paths, source, projectRoot) {
1755
1820
  report.skipped.push("no shared memories found in source");
1756
1821
  return report;
1757
1822
  }
1758
- const destDir = path8.join(paths.memoriesDir, "shared", source.name);
1759
- await mkdir3(destDir, { recursive: true });
1760
- const cacheDir = path8.join(paths.haiveDir, ".cache", "cross-repo", source.name);
1761
- 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 });
1762
1827
  const importMap = await loadImportMap(cacheDir);
1763
1828
  const mapDirty = false;
1764
1829
  let dirty = mapDirty;
@@ -1772,7 +1837,7 @@ async function pullFromSource(paths, source, projectRoot) {
1772
1837
 
1773
1838
  `;
1774
1839
  const existingLocalPath = importMap[sourceId];
1775
- if (existingLocalPath && existsSync6(existingLocalPath)) {
1840
+ if (existingLocalPath && existsSync7(existingLocalPath)) {
1776
1841
  const existingFiles = await loadMemoriesFromDir(destDir);
1777
1842
  const existingEntry = existingFiles.find(({ filePath }) => filePath === existingLocalPath);
1778
1843
  const sourceBodyStripped = memory.body.trim();
@@ -1807,7 +1872,7 @@ async function pullFromSource(paths, source, projectRoot) {
1807
1872
  topic: fm.topic ? `${source.name}:${fm.topic}` : void 0
1808
1873
  });
1809
1874
  const body = importedBodyPrefix + memory.body;
1810
- const destPath = path8.join(destDir, `${newFm.id}.md`);
1875
+ const destPath = path9.join(destDir, `${newFm.id}.md`);
1811
1876
  await writeFile4(destPath, serializeMemory({ frontmatter: newFm, body }), "utf8");
1812
1877
  importMap[sourceId] = destPath;
1813
1878
  dirty = true;
@@ -1818,9 +1883,9 @@ async function pullFromSource(paths, source, projectRoot) {
1818
1883
  return report;
1819
1884
  }
1820
1885
  async function cloneOrFetchGitSource(source, paths, report) {
1821
- const cacheDir = path8.join(paths.haiveDir, ".cache", "cross-repo", source.name);
1822
- await mkdir3(cacheDir, { recursive: true });
1823
- 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"))) {
1824
1889
  const result = spawnSync2("git", ["fetch", "--depth=1", "origin"], {
1825
1890
  cwd: cacheDir,
1826
1891
  encoding: "utf8"
@@ -1845,9 +1910,9 @@ async function cloneOrFetchGitSource(source, paths, report) {
1845
1910
  }
1846
1911
 
1847
1912
  // src/dep-tracker.ts
1848
- import { existsSync as existsSync7 } from "fs";
1849
- import { readFile as readFile7, writeFile as writeFile5, mkdir as mkdir4 } from "fs/promises";
1850
- 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";
1851
1916
  function parsePackageJson(content) {
1852
1917
  try {
1853
1918
  const pkg = JSON.parse(content);
@@ -1919,7 +1984,7 @@ var KNOWN_MANIFESTS = [
1919
1984
  { name: "pom.xml", parser: parsePomXml }
1920
1985
  ];
1921
1986
  function getParser(file) {
1922
- const base = path9.basename(file);
1987
+ const base = path10.basename(file);
1923
1988
  return KNOWN_MANIFESTS.find((m) => m.name === base)?.parser ?? null;
1924
1989
  }
1925
1990
  function extractMajor(version) {
@@ -1937,32 +2002,32 @@ function isMajorBump(from, to) {
1937
2002
  }
1938
2003
  function resolveManifestFiles(projectRoot, configuredFiles) {
1939
2004
  if (configuredFiles !== void 0) {
1940
- return configuredFiles.map((f) => path9.resolve(projectRoot, f)).filter(existsSync7);
2005
+ return configuredFiles.map((f) => path10.resolve(projectRoot, f)).filter(existsSync8);
1941
2006
  }
1942
- return KNOWN_MANIFESTS.map(({ name }) => path9.join(projectRoot, name)).filter(existsSync7);
2007
+ return KNOWN_MANIFESTS.map(({ name }) => path10.join(projectRoot, name)).filter(existsSync8);
1943
2008
  }
1944
2009
  async function trackDependencies(projectRoot, haiveDir, manifestFiles) {
1945
- const contractsDir = path9.join(haiveDir, "contracts");
1946
- await mkdir4(contractsDir, { recursive: true });
2010
+ const contractsDir = path10.join(haiveDir, "contracts");
2011
+ await mkdir5(contractsDir, { recursive: true });
1947
2012
  const results = [];
1948
2013
  for (const manifestPath of manifestFiles) {
1949
2014
  const parser = getParser(manifestPath);
1950
2015
  if (!parser) continue;
1951
- const content = await readFile7(manifestPath, "utf8");
2016
+ const content = await readFile8(manifestPath, "utf8");
1952
2017
  const currentDeps = parser(content);
1953
- const lockName = `deps-${path9.basename(manifestPath)}.lock`;
1954
- const lockPath = path9.join(contractsDir, lockName);
1955
- if (!existsSync7(lockPath)) {
2018
+ const lockName = `deps-${path10.basename(manifestPath)}.lock`;
2019
+ const lockPath = path10.join(contractsDir, lockName);
2020
+ if (!existsSync8(lockPath)) {
1956
2021
  const snapshot2 = {
1957
- file: path9.relative(projectRoot, manifestPath),
1958
- format: path9.basename(manifestPath),
2022
+ file: path10.relative(projectRoot, manifestPath),
2023
+ format: path10.basename(manifestPath),
1959
2024
  captured_at: (/* @__PURE__ */ new Date()).toISOString(),
1960
2025
  deps: currentDeps
1961
2026
  };
1962
2027
  await writeFile5(lockPath, JSON.stringify(snapshot2, null, 2) + "\n", "utf8");
1963
2028
  continue;
1964
2029
  }
1965
- const snapshot = JSON.parse(await readFile7(lockPath, "utf8"));
2030
+ const snapshot = JSON.parse(await readFile8(lockPath, "utf8"));
1966
2031
  const changes = [];
1967
2032
  for (const [name, currentVer] of Object.entries(currentDeps)) {
1968
2033
  const prevVer = snapshot.deps[name];
@@ -1976,7 +2041,7 @@ async function trackDependencies(projectRoot, haiveDir, manifestFiles) {
1976
2041
  }
1977
2042
  }
1978
2043
  if (changes.length > 0) {
1979
- results.push({ file: path9.relative(projectRoot, manifestPath), changes });
2044
+ results.push({ file: path10.relative(projectRoot, manifestPath), changes });
1980
2045
  const updated = {
1981
2046
  ...snapshot,
1982
2047
  captured_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -1989,9 +2054,9 @@ async function trackDependencies(projectRoot, haiveDir, manifestFiles) {
1989
2054
  }
1990
2055
 
1991
2056
  // src/contract-watcher.ts
1992
- import { existsSync as existsSync8 } from "fs";
1993
- import { readFile as readFile8, writeFile as writeFile6, mkdir as mkdir5 } from "fs/promises";
1994
- 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";
1995
2060
  import crypto from "crypto";
1996
2061
  function sha256(content) {
1997
2062
  return crypto.createHash("sha256").update(content).digest("hex");
@@ -2194,14 +2259,14 @@ function diffSnapshots(before, after) {
2194
2259
  return changes;
2195
2260
  }
2196
2261
  function contractLockPath(haiveDir, name) {
2197
- return path10.join(haiveDir, "contracts", `${name}.lock`);
2262
+ return path11.join(haiveDir, "contracts", `${name}.lock`);
2198
2263
  }
2199
2264
  async function snapshotContract(projectRoot, haiveDir, contract) {
2200
- const filePath = path10.resolve(projectRoot, contract.path);
2201
- if (!existsSync8(filePath)) {
2265
+ const filePath = path11.resolve(projectRoot, contract.path);
2266
+ if (!existsSync9(filePath)) {
2202
2267
  throw new Error(`Contract file not found: ${filePath}`);
2203
2268
  }
2204
- const content = await readFile8(filePath, "utf8");
2269
+ const content = await readFile9(filePath, "utf8");
2205
2270
  const parsed = parseByFormat(content, contract.format, filePath);
2206
2271
  const snapshot = {
2207
2272
  name: contract.name,
@@ -2211,23 +2276,23 @@ async function snapshotContract(projectRoot, haiveDir, contract) {
2211
2276
  hash: sha256(content),
2212
2277
  ...parsed
2213
2278
  };
2214
- const contractsDir = path10.join(haiveDir, "contracts");
2215
- await mkdir5(contractsDir, { recursive: true });
2279
+ const contractsDir = path11.join(haiveDir, "contracts");
2280
+ await mkdir6(contractsDir, { recursive: true });
2216
2281
  await writeFile6(contractLockPath(haiveDir, contract.name), JSON.stringify(snapshot, null, 2) + "\n", "utf8");
2217
2282
  return snapshot;
2218
2283
  }
2219
2284
  async function diffContract(projectRoot, haiveDir, contract) {
2220
- const filePath = path10.resolve(projectRoot, contract.path);
2221
- if (!existsSync8(filePath)) {
2285
+ const filePath = path11.resolve(projectRoot, contract.path);
2286
+ if (!existsSync9(filePath)) {
2222
2287
  return { contract: contract.name, file: contract.path, changes: [], unchanged: true };
2223
2288
  }
2224
2289
  const lockPath = contractLockPath(haiveDir, contract.name);
2225
- if (!existsSync8(lockPath)) {
2290
+ if (!existsSync9(lockPath)) {
2226
2291
  await snapshotContract(projectRoot, haiveDir, contract);
2227
2292
  return { contract: contract.name, file: contract.path, changes: [], unchanged: true };
2228
2293
  }
2229
- const content = await readFile8(filePath, "utf8");
2230
- const beforeSnapshot = JSON.parse(await readFile8(lockPath, "utf8"));
2294
+ const content = await readFile9(filePath, "utf8");
2295
+ const beforeSnapshot = JSON.parse(await readFile9(lockPath, "utf8"));
2231
2296
  const afterParsed = parseByFormat(content, contract.format, filePath);
2232
2297
  const afterSnapshot = {
2233
2298
  ...beforeSnapshot,
@@ -2255,27 +2320,27 @@ async function watchContracts(projectRoot, haiveDir, contractFiles) {
2255
2320
  }
2256
2321
 
2257
2322
  // src/usage-log.ts
2258
- import { appendFile, mkdir as mkdir6, readFile as readFile9, stat as stat2 } from "fs/promises";
2259
- import { existsSync as existsSync9 } from "fs";
2260
- 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";
2261
2326
  var USAGE_LOG_FILE = "tool-usage.jsonl";
2262
2327
  var USAGE_LOG_DIR = ".usage";
2263
2328
  function usageLogPath(paths) {
2264
- return path11.join(paths.haiveDir, USAGE_LOG_DIR, USAGE_LOG_FILE);
2329
+ return path12.join(paths.haiveDir, USAGE_LOG_DIR, USAGE_LOG_FILE);
2265
2330
  }
2266
2331
  async function appendUsageEvent(paths, event) {
2267
2332
  try {
2268
2333
  const file = usageLogPath(paths);
2269
- const dir = path11.dirname(file);
2270
- if (!existsSync9(dir)) await mkdir6(dir, { recursive: true });
2271
- 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");
2272
2337
  } catch {
2273
2338
  }
2274
2339
  }
2275
2340
  async function readUsageEvents(paths) {
2276
2341
  const file = usageLogPath(paths);
2277
- if (!existsSync9(file)) return [];
2278
- const raw = await readFile9(file, "utf8");
2342
+ if (!existsSync10(file)) return [];
2343
+ const raw = await readFile10(file, "utf8");
2279
2344
  const out = [];
2280
2345
  for (const line of raw.split("\n")) {
2281
2346
  if (!line) continue;
@@ -2324,9 +2389,9 @@ function parseSince(input) {
2324
2389
  }
2325
2390
  async function usageLogSize(paths) {
2326
2391
  const file = usageLogPath(paths);
2327
- if (!existsSync9(file)) return { exists: false, size_bytes: 0, lines: 0 };
2392
+ if (!existsSync10(file)) return { exists: false, size_bytes: 0, lines: 0 };
2328
2393
  const st = await stat2(file);
2329
- const raw = await readFile9(file, "utf8");
2394
+ const raw = await readFile10(file, "utf8");
2330
2395
  return { exists: true, size_bytes: st.size, lines: raw.split("\n").filter((l) => l).length };
2331
2396
  }
2332
2397
 
@@ -2396,21 +2461,21 @@ function extractActionsBriefBody(markdown, maxChars = MAX_DEFAULT_CHARS) {
2396
2461
  }
2397
2462
 
2398
2463
  // src/resolve-project.ts
2399
- import { existsSync as existsSync10 } from "fs";
2400
- import path12 from "path";
2464
+ import { existsSync as existsSync11 } from "fs";
2465
+ import path13 from "path";
2401
2466
  var ROOT_MARKERS2 = [".ai", ".git", "package.json"];
2402
2467
  function markersAtRoot(root) {
2403
2468
  const found = [];
2404
2469
  for (const m of ROOT_MARKERS2) {
2405
- if (existsSync10(path12.join(root, m))) found.push(m);
2470
+ if (existsSync11(path13.join(root, m))) found.push(m);
2406
2471
  }
2407
2472
  return found;
2408
2473
  }
2409
2474
  function resolveProjectInfo(opts = {}) {
2410
2475
  const env = opts.env ?? process.env;
2411
- const cwd = path12.resolve(opts.cwd ?? process.cwd());
2476
+ const cwd = path13.resolve(opts.cwd ?? process.cwd());
2412
2477
  const raw = env.HAIVE_PROJECT_ROOT;
2413
- const explicit = raw !== void 0 && raw !== "" ? path12.resolve(raw) : null;
2478
+ const explicit = raw !== void 0 && raw !== "" ? path13.resolve(raw) : null;
2414
2479
  const resolvedRoot = explicit ?? findProjectRoot(cwd);
2415
2480
  const paths = resolveHaivePaths(resolvedRoot);
2416
2481
  return {
@@ -2418,8 +2483,8 @@ function resolveProjectInfo(opts = {}) {
2418
2483
  resolved_root: resolvedRoot,
2419
2484
  haive_project_root_env: explicit,
2420
2485
  explicit_root: explicit != null,
2421
- haive_dir_exists: existsSync10(paths.haiveDir),
2422
- memories_dir_exists: existsSync10(paths.memoriesDir),
2486
+ haive_dir_exists: existsSync11(paths.haiveDir),
2487
+ memories_dir_exists: existsSync11(paths.memoriesDir),
2423
2488
  runtime_dir: paths.runtimeDir,
2424
2489
  markers_found: markersAtRoot(resolvedRoot)
2425
2490
  };
@@ -2674,16 +2739,16 @@ function findLexicalConflictPairs(memories, opts) {
2674
2739
  }
2675
2740
 
2676
2741
  // src/runtime-journal.ts
2677
- import { mkdir as mkdir7, readFile as readFile10, appendFile as appendFile2 } from "fs/promises";
2678
- import { existsSync as existsSync11 } from "fs";
2679
- 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";
2680
2745
  var RUNTIME_JOURNAL_FILENAME = "session-journal.ndjson";
2681
2746
  function runtimeJournalPath(paths) {
2682
- return path13.join(paths.runtimeDir, RUNTIME_JOURNAL_FILENAME);
2747
+ return path14.join(paths.runtimeDir, RUNTIME_JOURNAL_FILENAME);
2683
2748
  }
2684
2749
  async function appendRuntimeJournalEntry(paths, entry) {
2685
2750
  try {
2686
- await mkdir7(paths.runtimeDir, { recursive: true });
2751
+ await mkdir8(paths.runtimeDir, { recursive: true });
2687
2752
  const line = {
2688
2753
  ts: entry.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
2689
2754
  kind: entry.kind,
@@ -2691,7 +2756,7 @@ async function appendRuntimeJournalEntry(paths, entry) {
2691
2756
  ...entry.tool !== void 0 ? { tool: entry.tool } : {},
2692
2757
  ...entry.meta !== void 0 ? { meta: entry.meta } : {}
2693
2758
  };
2694
- await appendFile2(
2759
+ await appendFile3(
2695
2760
  runtimeJournalPath(paths),
2696
2761
  JSON.stringify(line) + "\n",
2697
2762
  "utf8"
@@ -2701,9 +2766,9 @@ async function appendRuntimeJournalEntry(paths, entry) {
2701
2766
  }
2702
2767
  async function readRuntimeJournalTail(paths, limit) {
2703
2768
  const file = runtimeJournalPath(paths);
2704
- if (!existsSync11(file) || limit <= 0) return [];
2769
+ if (!existsSync12(file) || limit <= 0) return [];
2705
2770
  try {
2706
- const raw = await readFile10(file, "utf8");
2771
+ const raw = await readFile11(file, "utf8");
2707
2772
  const lines = raw.trim().split("\n").filter(Boolean);
2708
2773
  const parsed = [];
2709
2774
  for (const line of lines.slice(-limit)) {
@@ -2719,22 +2784,22 @@ async function readRuntimeJournalTail(paths, limit) {
2719
2784
  }
2720
2785
 
2721
2786
  // src/enforcement.ts
2722
- import { mkdir as mkdir8, readdir as readdir4, readFile as readFile11, writeFile as writeFile7 } from "fs/promises";
2723
- import { existsSync as existsSync12 } from "fs";
2724
- 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";
2725
2790
  var BRIEFING_MARKER_TTL_MS = 12 * 60 * 60 * 1e3;
2726
2791
  var SESSION_RECAP_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
2727
2792
  function enforcementDir(paths) {
2728
- return path14.join(paths.runtimeDir, "enforcement");
2793
+ return path15.join(paths.runtimeDir, "enforcement");
2729
2794
  }
2730
2795
  function briefingMarkersDir(paths) {
2731
- return path14.join(enforcementDir(paths), "briefings");
2796
+ return path15.join(enforcementDir(paths), "briefings");
2732
2797
  }
2733
2798
  function normalizeSessionId(sessionId) {
2734
2799
  return (sessionId?.trim() || "default").replace(/[^a-zA-Z0-9_.-]+/g, "-").slice(0, 120);
2735
2800
  }
2736
2801
  function briefingMarkerPath(paths, sessionId) {
2737
- return path14.join(briefingMarkersDir(paths), `${normalizeSessionId(sessionId)}.json`);
2802
+ return path15.join(briefingMarkersDir(paths), `${normalizeSessionId(sessionId)}.json`);
2738
2803
  }
2739
2804
  async function writeBriefingMarker(paths, input) {
2740
2805
  const marker = {
@@ -2746,7 +2811,7 @@ async function writeBriefingMarker(paths, input) {
2746
2811
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
2747
2812
  root: paths.root
2748
2813
  };
2749
- await mkdir8(briefingMarkersDir(paths), { recursive: true });
2814
+ await mkdir9(briefingMarkersDir(paths), { recursive: true });
2750
2815
  await writeFile7(
2751
2816
  briefingMarkerPath(paths, marker.session_id),
2752
2817
  JSON.stringify(marker, null, 2) + "\n",
@@ -2758,18 +2823,18 @@ async function hasRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKER
2758
2823
  const now = Date.now();
2759
2824
  const candidates = [];
2760
2825
  const exact = briefingMarkerPath(paths, sessionId);
2761
- if (existsSync12(exact)) candidates.push(exact);
2826
+ if (existsSync13(exact)) candidates.push(exact);
2762
2827
  try {
2763
2828
  const dir = briefingMarkersDir(paths);
2764
2829
  const files = await readdir4(dir);
2765
2830
  for (const file of files) {
2766
- if (file.endsWith(".json")) candidates.push(path14.join(dir, file));
2831
+ if (file.endsWith(".json")) candidates.push(path15.join(dir, file));
2767
2832
  }
2768
2833
  } catch {
2769
2834
  }
2770
2835
  for (const file of new Set(candidates)) {
2771
2836
  try {
2772
- const marker = JSON.parse(await readFile11(file, "utf8"));
2837
+ const marker = JSON.parse(await readFile12(file, "utf8"));
2773
2838
  const created = Date.parse(marker.created_at);
2774
2839
  if (Number.isFinite(created) && now - created <= ttlMs) return true;
2775
2840
  } catch {
@@ -2781,12 +2846,12 @@ async function readRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKE
2781
2846
  const now = Date.now();
2782
2847
  const candidates = [];
2783
2848
  const exact = briefingMarkerPath(paths, sessionId);
2784
- if (existsSync12(exact)) candidates.push(exact);
2849
+ if (existsSync13(exact)) candidates.push(exact);
2785
2850
  try {
2786
2851
  const dir = briefingMarkersDir(paths);
2787
2852
  const files = await readdir4(dir);
2788
2853
  for (const file of files) {
2789
- if (file.endsWith(".json")) candidates.push(path14.join(dir, file));
2854
+ if (file.endsWith(".json")) candidates.push(path15.join(dir, file));
2790
2855
  }
2791
2856
  } catch {
2792
2857
  }
@@ -2794,7 +2859,7 @@ async function readRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKE
2794
2859
  let freshestTs = 0;
2795
2860
  for (const file of new Set(candidates)) {
2796
2861
  try {
2797
- const marker = JSON.parse(await readFile11(file, "utf8"));
2862
+ const marker = JSON.parse(await readFile12(file, "utf8"));
2798
2863
  const created = Date.parse(marker.created_at);
2799
2864
  if (!Number.isFinite(created) || now - created > ttlMs) continue;
2800
2865
  if (created > freshestTs) {
@@ -2847,10 +2912,10 @@ function isRetiredMemory(fm, body = "", now = /* @__PURE__ */ new Date()) {
2847
2912
  function normalizeProjectPath(value) {
2848
2913
  return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^[ab]\//, "").replace(/\/+$/g, "");
2849
2914
  }
2850
- function sensorAppliesToPath(sensor, anchorPaths, path15) {
2915
+ function sensorAppliesToPath(sensor, anchorPaths, path16) {
2851
2916
  const scopes = sensor.paths.length > 0 ? sensor.paths : anchorPaths;
2852
2917
  if (scopes.length === 0) return true;
2853
- const target = normalizeProjectPath(path15);
2918
+ const target = normalizeProjectPath(path16);
2854
2919
  return scopes.some((rawScope) => {
2855
2920
  const scope = normalizeProjectPath(rawScope);
2856
2921
  if (!scope) return false;
@@ -3124,8 +3189,8 @@ function normalizeFindingSeverity(raw) {
3124
3189
  return "info";
3125
3190
  }
3126
3191
  }
3127
- function findingKey(tool, ruleId, path15) {
3128
- return `${tool}:${ruleId}:${path15}`;
3192
+ function findingKey(tool, ruleId, path16) {
3193
+ return `${tool}:${ruleId}:${path16}`;
3129
3194
  }
3130
3195
  function coerceJson(input) {
3131
3196
  if (typeof input === "string") {
@@ -3159,8 +3224,8 @@ function parseSarif(input) {
3159
3224
  const physical = asRecord(location.physicalLocation);
3160
3225
  const artifact = asRecord(physical.artifactLocation);
3161
3226
  const region = asRecord(physical.region);
3162
- const path15 = typeof artifact.uri === "string" ? normalizeUri(artifact.uri) : "";
3163
- if (!path15) continue;
3227
+ const path16 = typeof artifact.uri === "string" ? normalizeUri(artifact.uri) : "";
3228
+ if (!path16) continue;
3164
3229
  const line = typeof region.startLine === "number" ? region.startLine : void 0;
3165
3230
  const snippet = typeof asRecord(region.snippet).text === "string" ? asRecord(region.snippet).text.trim() : void 0;
3166
3231
  findings.push({
@@ -3168,10 +3233,10 @@ function parseSarif(input) {
3168
3233
  ruleId,
3169
3234
  message: message.trim(),
3170
3235
  severity,
3171
- path: path15,
3236
+ path: path16,
3172
3237
  ...line !== void 0 ? { line } : {},
3173
3238
  ...snippet ? { snippet } : {},
3174
- key: findingKey(tool, ruleId, path15)
3239
+ key: findingKey(tool, ruleId, path16)
3175
3240
  });
3176
3241
  }
3177
3242
  }
@@ -3190,17 +3255,17 @@ function parseSonar(input) {
3190
3255
  (typeof issue.severity === "string" ? issue.severity : void 0) ?? impactSeverity
3191
3256
  );
3192
3257
  const component = typeof issue.component === "string" ? issue.component : "";
3193
- const path15 = componentToPath(component);
3194
- if (!path15) continue;
3258
+ const path16 = componentToPath(component);
3259
+ if (!path16) continue;
3195
3260
  const line = typeof issue.line === "number" ? issue.line : void 0;
3196
3261
  findings.push({
3197
3262
  tool: "sonar",
3198
3263
  ruleId,
3199
3264
  message,
3200
3265
  severity,
3201
- path: path15,
3266
+ path: path16,
3202
3267
  ...line !== void 0 ? { line } : {},
3203
- key: findingKey("sonar", ruleId, path15)
3268
+ key: findingKey("sonar", ruleId, path16)
3204
3269
  });
3205
3270
  }
3206
3271
  return findings;
@@ -3278,7 +3343,7 @@ function filterNewDrafts(drafts, existingTopics) {
3278
3343
  }
3279
3344
 
3280
3345
  // src/dashboard.ts
3281
- var MS_PER_DAY3 = 24 * 60 * 60 * 1e3;
3346
+ var MS_PER_DAY4 = 24 * 60 * 60 * 1e3;
3282
3347
  function isAnchorless(fm) {
3283
3348
  if (!["decision", "gotcha", "architecture"].includes(fm.type)) return false;
3284
3349
  if (fm.status !== "validated") return false;
@@ -3372,7 +3437,7 @@ function buildDashboard(memories, usage, options = {}) {
3372
3437
  if (isDecaying(memUsage, fm.created_at)) decaying += 1;
3373
3438
  if (impact.tier === "dormant") {
3374
3439
  const anchor = memUsage.last_read_at ?? fm.created_at;
3375
- 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);
3376
3441
  dormantRows.push({ id: fm.id, last_read_at: memUsage.last_read_at, age_days: ageDays });
3377
3442
  }
3378
3443
  }
@@ -3411,7 +3476,12 @@ function buildDashboard(memories, usage, options = {}) {
3411
3476
  prevention: {
3412
3477
  total_events: preventionEvents,
3413
3478
  memories_with_catches: preventionRows.length,
3414
- top: preventionRows.sort((a, b) => b.prevented_count - a.prevented_count).slice(0, top)
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
+ }
3415
3485
  },
3416
3486
  corpus: {
3417
3487
  memory_files: inventory.total,
@@ -3459,6 +3529,7 @@ export {
3459
3529
  aggregateUsage,
3460
3530
  allocateBudget,
3461
3531
  antiPatternGateParams,
3532
+ appendPreventionEvent,
3462
3533
  appendRuntimeJournalEntry,
3463
3534
  appendUsageEvent,
3464
3535
  briefingMarkerPath,
@@ -3475,6 +3546,8 @@ export {
3475
3546
  compareImpact,
3476
3547
  compileRegexSensor,
3477
3548
  computeImpact,
3549
+ computePreventionTrend,
3550
+ computeRecurrence,
3478
3551
  configPath,
3479
3552
  contractLockPath,
3480
3553
  deriveConfidence,
@@ -3518,6 +3591,7 @@ export {
3518
3591
  loadConfigSync,
3519
3592
  loadMemoriesFromDir,
3520
3593
  loadMemory,
3594
+ loadPreventionEvents,
3521
3595
  loadUsageIndex,
3522
3596
  memoryFilePath,
3523
3597
  memoryMatchesAnchorPaths,
@@ -3532,6 +3606,7 @@ export {
3532
3606
  parseSonar,
3533
3607
  pathsOverlap,
3534
3608
  pickSnippetNeedle,
3609
+ preventionLogPath,
3535
3610
  pullCrossRepoSources,
3536
3611
  queryCodeMap,
3537
3612
  rankMemoriesLexical,