@hiveai/core 0.13.9 → 0.15.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
@@ -856,6 +856,37 @@ function computeRecurrence(events) {
856
856
  return { recurring_count: rows.length, top: rows };
857
857
  }
858
858
 
859
+ // src/context-throttle.ts
860
+ import { createHash } from "crypto";
861
+ import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile2 } from "fs/promises";
862
+ import { existsSync as existsSync5 } from "fs";
863
+ import path7 from "path";
864
+ var PROJECT_CONTEXT_THROTTLE_MS = 8 * 60 * 1e3;
865
+ function throttleMarkerPath(paths) {
866
+ return path7.join(paths.haiveDir, ".cache", "briefing-context.json");
867
+ }
868
+ function hashProjectContext(content) {
869
+ return createHash("sha1").update(content).digest("hex").slice(0, 16);
870
+ }
871
+ async function projectContextRecentlyEmitted(paths, hash, now = Date.now()) {
872
+ const file = throttleMarkerPath(paths);
873
+ if (!existsSync5(file)) return false;
874
+ try {
875
+ const m = JSON.parse(await readFile5(file, "utf8"));
876
+ if (m.hash !== hash || !m.at) return false;
877
+ return now - Date.parse(m.at) < PROJECT_CONTEXT_THROTTLE_MS;
878
+ } catch {
879
+ return false;
880
+ }
881
+ }
882
+ async function recordProjectContextEmission(paths, hash, now = Date.now()) {
883
+ const file = throttleMarkerPath(paths);
884
+ await mkdir3(path7.dirname(file), { recursive: true }).catch(() => {
885
+ });
886
+ await writeFile2(file, JSON.stringify({ hash, at: new Date(now).toISOString() }), "utf8").catch(() => {
887
+ });
888
+ }
889
+
859
890
  // src/eval.ts
860
891
  function round32(n) {
861
892
  return Math.round(n * 1e3) / 1e3;
@@ -1291,10 +1322,10 @@ function allocateBudget(parts, maxTokens) {
1291
1322
  }
1292
1323
 
1293
1324
  // src/code-map.ts
1294
- import { mkdir as mkdir3, readFile as readFile5, readdir as readdir3, writeFile as writeFile2 } from "fs/promises";
1295
- import { existsSync as existsSync5 } from "fs";
1325
+ import { mkdir as mkdir4, readFile as readFile6, readdir as readdir3, writeFile as writeFile3 } from "fs/promises";
1326
+ import { existsSync as existsSync6 } from "fs";
1296
1327
  import { spawnSync } from "child_process";
1297
- import path7 from "path";
1328
+ import path8 from "path";
1298
1329
  var CODE_MAP_FILE = "code-map.json";
1299
1330
  var DEFAULT_INCLUDE = [
1300
1331
  ".ts",
@@ -1336,27 +1367,27 @@ var DEFAULT_EXCLUDE = [
1336
1367
  ];
1337
1368
  var TEST_FILE_RE = /\.(test|spec)\.[a-z]+$/i;
1338
1369
  function codeMapPath(paths) {
1339
- return path7.join(paths.haiveDir, CODE_MAP_FILE);
1370
+ return path8.join(paths.haiveDir, CODE_MAP_FILE);
1340
1371
  }
1341
1372
  async function loadCodeMap(paths) {
1342
1373
  const file = codeMapPath(paths);
1343
- if (!existsSync5(file)) return null;
1344
- return JSON.parse(await readFile5(file, "utf8"));
1374
+ if (!existsSync6(file)) return null;
1375
+ return JSON.parse(await readFile6(file, "utf8"));
1345
1376
  }
1346
1377
  async function saveCodeMap(paths, map) {
1347
1378
  const file = codeMapPath(paths);
1348
- await mkdir3(path7.dirname(file), { recursive: true });
1349
- await writeFile2(file, JSON.stringify(map, null, 2), "utf8");
1379
+ await mkdir4(path8.dirname(file), { recursive: true });
1380
+ await writeFile3(file, JSON.stringify(map, null, 2), "utf8");
1350
1381
  }
1351
1382
  async function buildCodeMap(root, options = {}) {
1352
1383
  const include = new Set(options.includeExtensions ?? DEFAULT_INCLUDE);
1353
1384
  const exclude = new Set(options.excludeDirs ?? DEFAULT_EXCLUDE);
1354
1385
  const files = {};
1355
1386
  for await (const abs of collectSourceFiles(root, include, exclude, options.includeUntracked)) {
1356
- const rel = path7.relative(root, abs).replace(/\\/g, "/");
1387
+ const rel = path8.relative(root, abs).replace(/\\/g, "/");
1357
1388
  if (rel.startsWith(".ai/")) continue;
1358
- const content = await readFile5(abs, "utf8");
1359
- const ext = path7.extname(abs).toLowerCase();
1389
+ const content = await readFile6(abs, "utf8");
1390
+ const ext = path8.extname(abs).toLowerCase();
1360
1391
  const entry = parseFile(content, ext);
1361
1392
  if (entry.exports.length > 0) files[rel] = entry;
1362
1393
  }
@@ -1370,7 +1401,7 @@ async function buildCodeMap(root, options = {}) {
1370
1401
  async function* collectSourceFiles(root, include, exclude, includeUntracked) {
1371
1402
  const gitFiles = gitSourceFiles(root, include, exclude, includeUntracked === true);
1372
1403
  if (gitFiles) {
1373
- for (const rel of gitFiles) yield path7.join(root, rel);
1404
+ for (const rel of gitFiles) yield path8.join(root, rel);
1374
1405
  return;
1375
1406
  }
1376
1407
  yield* walkSourceFiles(root, include, exclude);
@@ -1397,11 +1428,11 @@ async function* walkSourceFiles(dir, include, exclude) {
1397
1428
  if (entry.isDirectory()) continue;
1398
1429
  }
1399
1430
  if (exclude.has(entry.name)) continue;
1400
- const full = path7.join(dir, entry.name);
1431
+ const full = path8.join(dir, entry.name);
1401
1432
  if (entry.isDirectory()) {
1402
1433
  yield* walkSourceFiles(full, include, exclude);
1403
1434
  } else if (entry.isFile()) {
1404
- const ext = path7.extname(entry.name).toLowerCase();
1435
+ const ext = path8.extname(entry.name).toLowerCase();
1405
1436
  if (include.has(ext) && !TEST_FILE_RE.test(entry.name)) yield full;
1406
1437
  }
1407
1438
  }
@@ -1412,7 +1443,7 @@ function isIncludedSourcePath(rel, include, exclude) {
1412
1443
  const parts = normalized.split("/");
1413
1444
  if (parts.some((part) => exclude.has(part))) return false;
1414
1445
  const base = parts.at(-1) ?? "";
1415
- const ext = path7.extname(base).toLowerCase();
1446
+ const ext = path8.extname(base).toLowerCase();
1416
1447
  return include.has(ext) && !TEST_FILE_RE.test(base);
1417
1448
  }
1418
1449
  var EXPORT_RE = /^export\s+(?:default\s+)?(async\s+)?(function|class|interface|type|const|let|var|enum)\s+(\*?)\s*([A-Za-z_$][\w$]*)/gm;
@@ -1626,10 +1657,10 @@ function queryCodeMap(map, options) {
1626
1657
  }
1627
1658
 
1628
1659
  // src/config.ts
1629
- import { existsSync as existsSync6 } from "fs";
1660
+ import { existsSync as existsSync7 } from "fs";
1630
1661
  import { readFileSync } from "fs";
1631
- import { readFile as readFile6, writeFile as writeFile3 } from "fs/promises";
1632
- import path8 from "path";
1662
+ import { readFile as readFile7, writeFile as writeFile4 } from "fs/promises";
1663
+ import path9 from "path";
1633
1664
  var CONFIG_FILE = "haive.config.json";
1634
1665
  var DEFAULT_CONFIG = {
1635
1666
  autopilot: false,
@@ -1701,13 +1732,13 @@ function antiPatternGateParams(gate) {
1701
1732
  }
1702
1733
  }
1703
1734
  function configPath(paths) {
1704
- return path8.join(paths.haiveDir, CONFIG_FILE);
1735
+ return path9.join(paths.haiveDir, CONFIG_FILE);
1705
1736
  }
1706
1737
  async function loadConfig(paths) {
1707
1738
  const file = configPath(paths);
1708
- if (!existsSync6(file)) return { ...DEFAULT_CONFIG };
1739
+ if (!existsSync7(file)) return { ...DEFAULT_CONFIG };
1709
1740
  try {
1710
- const raw = await readFile6(file, "utf8");
1741
+ const raw = await readFile7(file, "utf8");
1711
1742
  const parsed = JSON.parse(raw);
1712
1743
  const merged = mergeConfig(DEFAULT_CONFIG, parsed);
1713
1744
  if (merged.autopilot) {
@@ -1720,7 +1751,7 @@ async function loadConfig(paths) {
1720
1751
  }
1721
1752
  function loadConfigSync(paths) {
1722
1753
  const file = configPath(paths);
1723
- if (!existsSync6(file)) return { ...DEFAULT_CONFIG };
1754
+ if (!existsSync7(file)) return { ...DEFAULT_CONFIG };
1724
1755
  try {
1725
1756
  const parsed = JSON.parse(readFileSync(file, "utf8"));
1726
1757
  const merged = mergeConfig(DEFAULT_CONFIG, parsed);
@@ -1730,7 +1761,7 @@ function loadConfigSync(paths) {
1730
1761
  }
1731
1762
  }
1732
1763
  async function saveConfig(paths, config) {
1733
- await writeFile3(configPath(paths), JSON.stringify(config, null, 2) + "\n", "utf8");
1764
+ await writeFile4(configPath(paths), JSON.stringify(config, null, 2) + "\n", "utf8");
1734
1765
  }
1735
1766
  function mergeConfig(base, override) {
1736
1767
  return {
@@ -1748,21 +1779,21 @@ function mergeConfig(base, override) {
1748
1779
  }
1749
1780
 
1750
1781
  // src/cross-repo.ts
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";
1782
+ import { existsSync as existsSync8 } from "fs";
1783
+ import { mkdir as mkdir5, readFile as readFile8, writeFile as writeFile5 } from "fs/promises";
1784
+ import path10 from "path";
1754
1785
  import { spawnSync as spawnSync2 } from "child_process";
1755
1786
  async function loadImportMap(cacheDir) {
1756
- const mapPath = path9.join(cacheDir, "import-map.json");
1757
- if (!existsSync7(mapPath)) return {};
1787
+ const mapPath = path10.join(cacheDir, "import-map.json");
1788
+ if (!existsSync8(mapPath)) return {};
1758
1789
  try {
1759
- return JSON.parse(await readFile7(mapPath, "utf8"));
1790
+ return JSON.parse(await readFile8(mapPath, "utf8"));
1760
1791
  } catch {
1761
1792
  return {};
1762
1793
  }
1763
1794
  }
1764
1795
  async function saveImportMap(cacheDir, map) {
1765
- await writeFile4(path9.join(cacheDir, "import-map.json"), JSON.stringify(map, null, 2) + "\n", "utf8");
1796
+ await writeFile5(path10.join(cacheDir, "import-map.json"), JSON.stringify(map, null, 2) + "\n", "utf8");
1766
1797
  }
1767
1798
  async function pullCrossRepoSources(paths, config, projectRoot) {
1768
1799
  const sources = config.crossRepoSources ?? [];
@@ -1783,8 +1814,8 @@ async function pullFromSource(paths, source, projectRoot) {
1783
1814
  };
1784
1815
  let sourceRoot = null;
1785
1816
  if (source.path) {
1786
- const resolved = path9.resolve(projectRoot, source.path);
1787
- if (!existsSync7(resolved)) {
1817
+ const resolved = path10.resolve(projectRoot, source.path);
1818
+ if (!existsSync8(resolved)) {
1788
1819
  report.errors.push(`Path not found: ${resolved}`);
1789
1820
  return report;
1790
1821
  }
@@ -1797,7 +1828,7 @@ async function pullFromSource(paths, source, projectRoot) {
1797
1828
  return report;
1798
1829
  }
1799
1830
  const sourcePaths = resolveHaivePaths(sourceRoot);
1800
- if (!existsSync7(sourcePaths.memoriesDir)) {
1831
+ if (!existsSync8(sourcePaths.memoriesDir)) {
1801
1832
  report.errors.push(`No .ai/memories/ found at ${sourceRoot}`);
1802
1833
  return report;
1803
1834
  }
@@ -1820,10 +1851,10 @@ async function pullFromSource(paths, source, projectRoot) {
1820
1851
  report.skipped.push("no shared memories found in source");
1821
1852
  return report;
1822
1853
  }
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 });
1854
+ const destDir = path10.join(paths.memoriesDir, "shared", source.name);
1855
+ await mkdir5(destDir, { recursive: true });
1856
+ const cacheDir = path10.join(paths.haiveDir, ".cache", "cross-repo", source.name);
1857
+ await mkdir5(cacheDir, { recursive: true });
1827
1858
  const importMap = await loadImportMap(cacheDir);
1828
1859
  const mapDirty = false;
1829
1860
  let dirty = mapDirty;
@@ -1837,7 +1868,7 @@ async function pullFromSource(paths, source, projectRoot) {
1837
1868
 
1838
1869
  `;
1839
1870
  const existingLocalPath = importMap[sourceId];
1840
- if (existingLocalPath && existsSync7(existingLocalPath)) {
1871
+ if (existingLocalPath && existsSync8(existingLocalPath)) {
1841
1872
  const existingFiles = await loadMemoriesFromDir(destDir);
1842
1873
  const existingEntry = existingFiles.find(({ filePath }) => filePath === existingLocalPath);
1843
1874
  const sourceBodyStripped = memory.body.trim();
@@ -1848,7 +1879,7 @@ async function pullFromSource(paths, source, projectRoot) {
1848
1879
  }
1849
1880
  const updatedBody = importedBodyPrefix + memory.body;
1850
1881
  if (existingEntry) {
1851
- await writeFile4(
1882
+ await writeFile5(
1852
1883
  existingLocalPath,
1853
1884
  serializeMemory({ frontmatter: existingEntry.memory.frontmatter, body: updatedBody }),
1854
1885
  "utf8"
@@ -1872,8 +1903,8 @@ async function pullFromSource(paths, source, projectRoot) {
1872
1903
  topic: fm.topic ? `${source.name}:${fm.topic}` : void 0
1873
1904
  });
1874
1905
  const body = importedBodyPrefix + memory.body;
1875
- const destPath = path9.join(destDir, `${newFm.id}.md`);
1876
- await writeFile4(destPath, serializeMemory({ frontmatter: newFm, body }), "utf8");
1906
+ const destPath = path10.join(destDir, `${newFm.id}.md`);
1907
+ await writeFile5(destPath, serializeMemory({ frontmatter: newFm, body }), "utf8");
1877
1908
  importMap[sourceId] = destPath;
1878
1909
  dirty = true;
1879
1910
  report.imported.push(sourceId);
@@ -1883,9 +1914,9 @@ async function pullFromSource(paths, source, projectRoot) {
1883
1914
  return report;
1884
1915
  }
1885
1916
  async function cloneOrFetchGitSource(source, paths, report) {
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"))) {
1917
+ const cacheDir = path10.join(paths.haiveDir, ".cache", "cross-repo", source.name);
1918
+ await mkdir5(cacheDir, { recursive: true });
1919
+ if (existsSync8(path10.join(cacheDir, ".git"))) {
1889
1920
  const result = spawnSync2("git", ["fetch", "--depth=1", "origin"], {
1890
1921
  cwd: cacheDir,
1891
1922
  encoding: "utf8"
@@ -1910,9 +1941,9 @@ async function cloneOrFetchGitSource(source, paths, report) {
1910
1941
  }
1911
1942
 
1912
1943
  // src/dep-tracker.ts
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";
1944
+ import { existsSync as existsSync9 } from "fs";
1945
+ import { readFile as readFile9, writeFile as writeFile6, mkdir as mkdir6 } from "fs/promises";
1946
+ import path11 from "path";
1916
1947
  function parsePackageJson(content) {
1917
1948
  try {
1918
1949
  const pkg = JSON.parse(content);
@@ -1984,7 +2015,7 @@ var KNOWN_MANIFESTS = [
1984
2015
  { name: "pom.xml", parser: parsePomXml }
1985
2016
  ];
1986
2017
  function getParser(file) {
1987
- const base = path10.basename(file);
2018
+ const base = path11.basename(file);
1988
2019
  return KNOWN_MANIFESTS.find((m) => m.name === base)?.parser ?? null;
1989
2020
  }
1990
2021
  function extractMajor(version) {
@@ -2002,32 +2033,32 @@ function isMajorBump(from, to) {
2002
2033
  }
2003
2034
  function resolveManifestFiles(projectRoot, configuredFiles) {
2004
2035
  if (configuredFiles !== void 0) {
2005
- return configuredFiles.map((f) => path10.resolve(projectRoot, f)).filter(existsSync8);
2036
+ return configuredFiles.map((f) => path11.resolve(projectRoot, f)).filter(existsSync9);
2006
2037
  }
2007
- return KNOWN_MANIFESTS.map(({ name }) => path10.join(projectRoot, name)).filter(existsSync8);
2038
+ return KNOWN_MANIFESTS.map(({ name }) => path11.join(projectRoot, name)).filter(existsSync9);
2008
2039
  }
2009
2040
  async function trackDependencies(projectRoot, haiveDir, manifestFiles) {
2010
- const contractsDir = path10.join(haiveDir, "contracts");
2011
- await mkdir5(contractsDir, { recursive: true });
2041
+ const contractsDir = path11.join(haiveDir, "contracts");
2042
+ await mkdir6(contractsDir, { recursive: true });
2012
2043
  const results = [];
2013
2044
  for (const manifestPath of manifestFiles) {
2014
2045
  const parser = getParser(manifestPath);
2015
2046
  if (!parser) continue;
2016
- const content = await readFile8(manifestPath, "utf8");
2047
+ const content = await readFile9(manifestPath, "utf8");
2017
2048
  const currentDeps = parser(content);
2018
- const lockName = `deps-${path10.basename(manifestPath)}.lock`;
2019
- const lockPath = path10.join(contractsDir, lockName);
2020
- if (!existsSync8(lockPath)) {
2049
+ const lockName = `deps-${path11.basename(manifestPath)}.lock`;
2050
+ const lockPath = path11.join(contractsDir, lockName);
2051
+ if (!existsSync9(lockPath)) {
2021
2052
  const snapshot2 = {
2022
- file: path10.relative(projectRoot, manifestPath),
2023
- format: path10.basename(manifestPath),
2053
+ file: path11.relative(projectRoot, manifestPath),
2054
+ format: path11.basename(manifestPath),
2024
2055
  captured_at: (/* @__PURE__ */ new Date()).toISOString(),
2025
2056
  deps: currentDeps
2026
2057
  };
2027
- await writeFile5(lockPath, JSON.stringify(snapshot2, null, 2) + "\n", "utf8");
2058
+ await writeFile6(lockPath, JSON.stringify(snapshot2, null, 2) + "\n", "utf8");
2028
2059
  continue;
2029
2060
  }
2030
- const snapshot = JSON.parse(await readFile8(lockPath, "utf8"));
2061
+ const snapshot = JSON.parse(await readFile9(lockPath, "utf8"));
2031
2062
  const changes = [];
2032
2063
  for (const [name, currentVer] of Object.entries(currentDeps)) {
2033
2064
  const prevVer = snapshot.deps[name];
@@ -2041,22 +2072,22 @@ async function trackDependencies(projectRoot, haiveDir, manifestFiles) {
2041
2072
  }
2042
2073
  }
2043
2074
  if (changes.length > 0) {
2044
- results.push({ file: path10.relative(projectRoot, manifestPath), changes });
2075
+ results.push({ file: path11.relative(projectRoot, manifestPath), changes });
2045
2076
  const updated = {
2046
2077
  ...snapshot,
2047
2078
  captured_at: (/* @__PURE__ */ new Date()).toISOString(),
2048
2079
  deps: currentDeps
2049
2080
  };
2050
- await writeFile5(lockPath, JSON.stringify(updated, null, 2) + "\n", "utf8");
2081
+ await writeFile6(lockPath, JSON.stringify(updated, null, 2) + "\n", "utf8");
2051
2082
  }
2052
2083
  }
2053
2084
  return results;
2054
2085
  }
2055
2086
 
2056
2087
  // src/contract-watcher.ts
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";
2088
+ import { existsSync as existsSync10 } from "fs";
2089
+ import { readFile as readFile10, writeFile as writeFile7, mkdir as mkdir7 } from "fs/promises";
2090
+ import path12 from "path";
2060
2091
  import crypto from "crypto";
2061
2092
  function sha256(content) {
2062
2093
  return crypto.createHash("sha256").update(content).digest("hex");
@@ -2259,14 +2290,14 @@ function diffSnapshots(before, after) {
2259
2290
  return changes;
2260
2291
  }
2261
2292
  function contractLockPath(haiveDir, name) {
2262
- return path11.join(haiveDir, "contracts", `${name}.lock`);
2293
+ return path12.join(haiveDir, "contracts", `${name}.lock`);
2263
2294
  }
2264
2295
  async function snapshotContract(projectRoot, haiveDir, contract) {
2265
- const filePath = path11.resolve(projectRoot, contract.path);
2266
- if (!existsSync9(filePath)) {
2296
+ const filePath = path12.resolve(projectRoot, contract.path);
2297
+ if (!existsSync10(filePath)) {
2267
2298
  throw new Error(`Contract file not found: ${filePath}`);
2268
2299
  }
2269
- const content = await readFile9(filePath, "utf8");
2300
+ const content = await readFile10(filePath, "utf8");
2270
2301
  const parsed = parseByFormat(content, contract.format, filePath);
2271
2302
  const snapshot = {
2272
2303
  name: contract.name,
@@ -2276,23 +2307,23 @@ async function snapshotContract(projectRoot, haiveDir, contract) {
2276
2307
  hash: sha256(content),
2277
2308
  ...parsed
2278
2309
  };
2279
- const contractsDir = path11.join(haiveDir, "contracts");
2280
- await mkdir6(contractsDir, { recursive: true });
2281
- await writeFile6(contractLockPath(haiveDir, contract.name), JSON.stringify(snapshot, null, 2) + "\n", "utf8");
2310
+ const contractsDir = path12.join(haiveDir, "contracts");
2311
+ await mkdir7(contractsDir, { recursive: true });
2312
+ await writeFile7(contractLockPath(haiveDir, contract.name), JSON.stringify(snapshot, null, 2) + "\n", "utf8");
2282
2313
  return snapshot;
2283
2314
  }
2284
2315
  async function diffContract(projectRoot, haiveDir, contract) {
2285
- const filePath = path11.resolve(projectRoot, contract.path);
2286
- if (!existsSync9(filePath)) {
2316
+ const filePath = path12.resolve(projectRoot, contract.path);
2317
+ if (!existsSync10(filePath)) {
2287
2318
  return { contract: contract.name, file: contract.path, changes: [], unchanged: true };
2288
2319
  }
2289
2320
  const lockPath = contractLockPath(haiveDir, contract.name);
2290
- if (!existsSync9(lockPath)) {
2321
+ if (!existsSync10(lockPath)) {
2291
2322
  await snapshotContract(projectRoot, haiveDir, contract);
2292
2323
  return { contract: contract.name, file: contract.path, changes: [], unchanged: true };
2293
2324
  }
2294
- const content = await readFile9(filePath, "utf8");
2295
- const beforeSnapshot = JSON.parse(await readFile9(lockPath, "utf8"));
2325
+ const content = await readFile10(filePath, "utf8");
2326
+ const beforeSnapshot = JSON.parse(await readFile10(lockPath, "utf8"));
2296
2327
  const afterParsed = parseByFormat(content, contract.format, filePath);
2297
2328
  const afterSnapshot = {
2298
2329
  ...beforeSnapshot,
@@ -2302,7 +2333,7 @@ async function diffContract(projectRoot, haiveDir, contract) {
2302
2333
  };
2303
2334
  const changes = diffSnapshots(beforeSnapshot, afterSnapshot);
2304
2335
  if (changes.length > 0) {
2305
- await writeFile6(lockPath, JSON.stringify(afterSnapshot, null, 2) + "\n", "utf8");
2336
+ await writeFile7(lockPath, JSON.stringify(afterSnapshot, null, 2) + "\n", "utf8");
2306
2337
  }
2307
2338
  return {
2308
2339
  contract: contract.name,
@@ -2320,27 +2351,27 @@ async function watchContracts(projectRoot, haiveDir, contractFiles) {
2320
2351
  }
2321
2352
 
2322
2353
  // src/usage-log.ts
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";
2354
+ import { appendFile as appendFile2, mkdir as mkdir8, readFile as readFile11, stat as stat2 } from "fs/promises";
2355
+ import { existsSync as existsSync11 } from "fs";
2356
+ import path13 from "path";
2326
2357
  var USAGE_LOG_FILE = "tool-usage.jsonl";
2327
2358
  var USAGE_LOG_DIR = ".usage";
2328
2359
  function usageLogPath(paths) {
2329
- return path12.join(paths.haiveDir, USAGE_LOG_DIR, USAGE_LOG_FILE);
2360
+ return path13.join(paths.haiveDir, USAGE_LOG_DIR, USAGE_LOG_FILE);
2330
2361
  }
2331
2362
  async function appendUsageEvent(paths, event) {
2332
2363
  try {
2333
2364
  const file = usageLogPath(paths);
2334
- const dir = path12.dirname(file);
2335
- if (!existsSync10(dir)) await mkdir7(dir, { recursive: true });
2365
+ const dir = path13.dirname(file);
2366
+ if (!existsSync11(dir)) await mkdir8(dir, { recursive: true });
2336
2367
  await appendFile2(file, JSON.stringify(event) + "\n", "utf8");
2337
2368
  } catch {
2338
2369
  }
2339
2370
  }
2340
2371
  async function readUsageEvents(paths) {
2341
2372
  const file = usageLogPath(paths);
2342
- if (!existsSync10(file)) return [];
2343
- const raw = await readFile10(file, "utf8");
2373
+ if (!existsSync11(file)) return [];
2374
+ const raw = await readFile11(file, "utf8");
2344
2375
  const out = [];
2345
2376
  for (const line of raw.split("\n")) {
2346
2377
  if (!line) continue;
@@ -2389,9 +2420,9 @@ function parseSince(input) {
2389
2420
  }
2390
2421
  async function usageLogSize(paths) {
2391
2422
  const file = usageLogPath(paths);
2392
- if (!existsSync10(file)) return { exists: false, size_bytes: 0, lines: 0 };
2423
+ if (!existsSync11(file)) return { exists: false, size_bytes: 0, lines: 0 };
2393
2424
  const st = await stat2(file);
2394
- const raw = await readFile10(file, "utf8");
2425
+ const raw = await readFile11(file, "utf8");
2395
2426
  return { exists: true, size_bytes: st.size, lines: raw.split("\n").filter((l) => l).length };
2396
2427
  }
2397
2428
 
@@ -2461,21 +2492,21 @@ function extractActionsBriefBody(markdown, maxChars = MAX_DEFAULT_CHARS) {
2461
2492
  }
2462
2493
 
2463
2494
  // src/resolve-project.ts
2464
- import { existsSync as existsSync11 } from "fs";
2465
- import path13 from "path";
2495
+ import { existsSync as existsSync12 } from "fs";
2496
+ import path14 from "path";
2466
2497
  var ROOT_MARKERS2 = [".ai", ".git", "package.json"];
2467
2498
  function markersAtRoot(root) {
2468
2499
  const found = [];
2469
2500
  for (const m of ROOT_MARKERS2) {
2470
- if (existsSync11(path13.join(root, m))) found.push(m);
2501
+ if (existsSync12(path14.join(root, m))) found.push(m);
2471
2502
  }
2472
2503
  return found;
2473
2504
  }
2474
2505
  function resolveProjectInfo(opts = {}) {
2475
2506
  const env = opts.env ?? process.env;
2476
- const cwd = path13.resolve(opts.cwd ?? process.cwd());
2507
+ const cwd = path14.resolve(opts.cwd ?? process.cwd());
2477
2508
  const raw = env.HAIVE_PROJECT_ROOT;
2478
- const explicit = raw !== void 0 && raw !== "" ? path13.resolve(raw) : null;
2509
+ const explicit = raw !== void 0 && raw !== "" ? path14.resolve(raw) : null;
2479
2510
  const resolvedRoot = explicit ?? findProjectRoot(cwd);
2480
2511
  const paths = resolveHaivePaths(resolvedRoot);
2481
2512
  return {
@@ -2483,8 +2514,8 @@ function resolveProjectInfo(opts = {}) {
2483
2514
  resolved_root: resolvedRoot,
2484
2515
  haive_project_root_env: explicit,
2485
2516
  explicit_root: explicit != null,
2486
- haive_dir_exists: existsSync11(paths.haiveDir),
2487
- memories_dir_exists: existsSync11(paths.memoriesDir),
2517
+ haive_dir_exists: existsSync12(paths.haiveDir),
2518
+ memories_dir_exists: existsSync12(paths.memoriesDir),
2488
2519
  runtime_dir: paths.runtimeDir,
2489
2520
  markers_found: markersAtRoot(resolvedRoot)
2490
2521
  };
@@ -2739,16 +2770,16 @@ function findLexicalConflictPairs(memories, opts) {
2739
2770
  }
2740
2771
 
2741
2772
  // src/runtime-journal.ts
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";
2773
+ import { mkdir as mkdir9, readFile as readFile12, appendFile as appendFile3 } from "fs/promises";
2774
+ import { existsSync as existsSync13 } from "fs";
2775
+ import path15 from "path";
2745
2776
  var RUNTIME_JOURNAL_FILENAME = "session-journal.ndjson";
2746
2777
  function runtimeJournalPath(paths) {
2747
- return path14.join(paths.runtimeDir, RUNTIME_JOURNAL_FILENAME);
2778
+ return path15.join(paths.runtimeDir, RUNTIME_JOURNAL_FILENAME);
2748
2779
  }
2749
2780
  async function appendRuntimeJournalEntry(paths, entry) {
2750
2781
  try {
2751
- await mkdir8(paths.runtimeDir, { recursive: true });
2782
+ await mkdir9(paths.runtimeDir, { recursive: true });
2752
2783
  const line = {
2753
2784
  ts: entry.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
2754
2785
  kind: entry.kind,
@@ -2766,9 +2797,9 @@ async function appendRuntimeJournalEntry(paths, entry) {
2766
2797
  }
2767
2798
  async function readRuntimeJournalTail(paths, limit) {
2768
2799
  const file = runtimeJournalPath(paths);
2769
- if (!existsSync12(file) || limit <= 0) return [];
2800
+ if (!existsSync13(file) || limit <= 0) return [];
2770
2801
  try {
2771
- const raw = await readFile11(file, "utf8");
2802
+ const raw = await readFile12(file, "utf8");
2772
2803
  const lines = raw.trim().split("\n").filter(Boolean);
2773
2804
  const parsed = [];
2774
2805
  for (const line of lines.slice(-limit)) {
@@ -2784,22 +2815,22 @@ async function readRuntimeJournalTail(paths, limit) {
2784
2815
  }
2785
2816
 
2786
2817
  // src/enforcement.ts
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";
2818
+ import { mkdir as mkdir10, readdir as readdir4, readFile as readFile13, writeFile as writeFile8 } from "fs/promises";
2819
+ import { existsSync as existsSync14 } from "fs";
2820
+ import path16 from "path";
2790
2821
  var BRIEFING_MARKER_TTL_MS = 12 * 60 * 60 * 1e3;
2791
2822
  var SESSION_RECAP_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
2792
2823
  function enforcementDir(paths) {
2793
- return path15.join(paths.runtimeDir, "enforcement");
2824
+ return path16.join(paths.runtimeDir, "enforcement");
2794
2825
  }
2795
2826
  function briefingMarkersDir(paths) {
2796
- return path15.join(enforcementDir(paths), "briefings");
2827
+ return path16.join(enforcementDir(paths), "briefings");
2797
2828
  }
2798
2829
  function normalizeSessionId(sessionId) {
2799
2830
  return (sessionId?.trim() || "default").replace(/[^a-zA-Z0-9_.-]+/g, "-").slice(0, 120);
2800
2831
  }
2801
2832
  function briefingMarkerPath(paths, sessionId) {
2802
- return path15.join(briefingMarkersDir(paths), `${normalizeSessionId(sessionId)}.json`);
2833
+ return path16.join(briefingMarkersDir(paths), `${normalizeSessionId(sessionId)}.json`);
2803
2834
  }
2804
2835
  async function writeBriefingMarker(paths, input) {
2805
2836
  const marker = {
@@ -2811,8 +2842,8 @@ async function writeBriefingMarker(paths, input) {
2811
2842
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
2812
2843
  root: paths.root
2813
2844
  };
2814
- await mkdir9(briefingMarkersDir(paths), { recursive: true });
2815
- await writeFile7(
2845
+ await mkdir10(briefingMarkersDir(paths), { recursive: true });
2846
+ await writeFile8(
2816
2847
  briefingMarkerPath(paths, marker.session_id),
2817
2848
  JSON.stringify(marker, null, 2) + "\n",
2818
2849
  "utf8"
@@ -2823,18 +2854,18 @@ async function hasRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKER
2823
2854
  const now = Date.now();
2824
2855
  const candidates = [];
2825
2856
  const exact = briefingMarkerPath(paths, sessionId);
2826
- if (existsSync13(exact)) candidates.push(exact);
2857
+ if (existsSync14(exact)) candidates.push(exact);
2827
2858
  try {
2828
2859
  const dir = briefingMarkersDir(paths);
2829
2860
  const files = await readdir4(dir);
2830
2861
  for (const file of files) {
2831
- if (file.endsWith(".json")) candidates.push(path15.join(dir, file));
2862
+ if (file.endsWith(".json")) candidates.push(path16.join(dir, file));
2832
2863
  }
2833
2864
  } catch {
2834
2865
  }
2835
2866
  for (const file of new Set(candidates)) {
2836
2867
  try {
2837
- const marker = JSON.parse(await readFile12(file, "utf8"));
2868
+ const marker = JSON.parse(await readFile13(file, "utf8"));
2838
2869
  const created = Date.parse(marker.created_at);
2839
2870
  if (Number.isFinite(created) && now - created <= ttlMs) return true;
2840
2871
  } catch {
@@ -2846,12 +2877,12 @@ async function readRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKE
2846
2877
  const now = Date.now();
2847
2878
  const candidates = [];
2848
2879
  const exact = briefingMarkerPath(paths, sessionId);
2849
- if (existsSync13(exact)) candidates.push(exact);
2880
+ if (existsSync14(exact)) candidates.push(exact);
2850
2881
  try {
2851
2882
  const dir = briefingMarkersDir(paths);
2852
2883
  const files = await readdir4(dir);
2853
2884
  for (const file of files) {
2854
- if (file.endsWith(".json")) candidates.push(path15.join(dir, file));
2885
+ if (file.endsWith(".json")) candidates.push(path16.join(dir, file));
2855
2886
  }
2856
2887
  } catch {
2857
2888
  }
@@ -2859,7 +2890,7 @@ async function readRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKE
2859
2890
  let freshestTs = 0;
2860
2891
  for (const file of new Set(candidates)) {
2861
2892
  try {
2862
- const marker = JSON.parse(await readFile12(file, "utf8"));
2893
+ const marker = JSON.parse(await readFile13(file, "utf8"));
2863
2894
  const created = Date.parse(marker.created_at);
2864
2895
  if (!Number.isFinite(created) || now - created > ttlMs) continue;
2865
2896
  if (created > freshestTs) {
@@ -2912,10 +2943,10 @@ function isRetiredMemory(fm, body = "", now = /* @__PURE__ */ new Date()) {
2912
2943
  function normalizeProjectPath(value) {
2913
2944
  return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^[ab]\//, "").replace(/\/+$/g, "");
2914
2945
  }
2915
- function sensorAppliesToPath(sensor, anchorPaths, path16) {
2946
+ function sensorAppliesToPath(sensor, anchorPaths, path18) {
2916
2947
  const scopes = sensor.paths.length > 0 ? sensor.paths : anchorPaths;
2917
2948
  if (scopes.length === 0) return true;
2918
- const target = normalizeProjectPath(path16);
2949
+ const target = normalizeProjectPath(path18);
2919
2950
  return scopes.some((rawScope) => {
2920
2951
  const scope = normalizeProjectPath(rawScope);
2921
2952
  if (!scope) return false;
@@ -2963,6 +2994,28 @@ function runSensors(memories, targets) {
2963
2994
  }
2964
2995
  return hits;
2965
2996
  }
2997
+ function selectCommandSensors(memories, changedPaths) {
2998
+ const specs = [];
2999
+ for (const memory of memories) {
3000
+ const sensor = memory.frontmatter.sensor;
3001
+ if (!sensor) continue;
3002
+ if (sensor.kind !== "shell" && sensor.kind !== "test") continue;
3003
+ const command = sensor.command?.trim();
3004
+ if (!command) continue;
3005
+ const anchorPaths = memory.frontmatter.anchor.paths;
3006
+ const applies = changedPaths.length === 0 ? true : changedPaths.some((p) => sensorAppliesToPath(sensor, anchorPaths, p));
3007
+ if (!applies) continue;
3008
+ specs.push({
3009
+ memory_id: memory.frontmatter.id,
3010
+ command,
3011
+ kind: sensor.kind,
3012
+ severity: sensor.severity,
3013
+ message: sensor.message,
3014
+ paths: sensor.paths.length > 0 ? sensor.paths : anchorPaths
3015
+ });
3016
+ }
3017
+ return specs;
3018
+ }
2966
3019
  function sensorTargetsFromDiff(diff) {
2967
3020
  const targets = [];
2968
3021
  let currentPath = null;
@@ -3189,8 +3242,8 @@ function normalizeFindingSeverity(raw) {
3189
3242
  return "info";
3190
3243
  }
3191
3244
  }
3192
- function findingKey(tool, ruleId, path16) {
3193
- return `${tool}:${ruleId}:${path16}`;
3245
+ function findingKey(tool, ruleId, path18) {
3246
+ return `${tool}:${ruleId}:${path18}`;
3194
3247
  }
3195
3248
  function coerceJson(input) {
3196
3249
  if (typeof input === "string") {
@@ -3224,8 +3277,8 @@ function parseSarif(input) {
3224
3277
  const physical = asRecord(location.physicalLocation);
3225
3278
  const artifact = asRecord(physical.artifactLocation);
3226
3279
  const region = asRecord(physical.region);
3227
- const path16 = typeof artifact.uri === "string" ? normalizeUri(artifact.uri) : "";
3228
- if (!path16) continue;
3280
+ const path18 = typeof artifact.uri === "string" ? normalizeUri(artifact.uri) : "";
3281
+ if (!path18) continue;
3229
3282
  const line = typeof region.startLine === "number" ? region.startLine : void 0;
3230
3283
  const snippet = typeof asRecord(region.snippet).text === "string" ? asRecord(region.snippet).text.trim() : void 0;
3231
3284
  findings.push({
@@ -3233,10 +3286,10 @@ function parseSarif(input) {
3233
3286
  ruleId,
3234
3287
  message: message.trim(),
3235
3288
  severity,
3236
- path: path16,
3289
+ path: path18,
3237
3290
  ...line !== void 0 ? { line } : {},
3238
3291
  ...snippet ? { snippet } : {},
3239
- key: findingKey(tool, ruleId, path16)
3292
+ key: findingKey(tool, ruleId, path18)
3240
3293
  });
3241
3294
  }
3242
3295
  }
@@ -3255,17 +3308,17 @@ function parseSonar(input) {
3255
3308
  (typeof issue.severity === "string" ? issue.severity : void 0) ?? impactSeverity
3256
3309
  );
3257
3310
  const component = typeof issue.component === "string" ? issue.component : "";
3258
- const path16 = componentToPath(component);
3259
- if (!path16) continue;
3311
+ const path18 = componentToPath(component);
3312
+ if (!path18) continue;
3260
3313
  const line = typeof issue.line === "number" ? issue.line : void 0;
3261
3314
  findings.push({
3262
3315
  tool: "sonar",
3263
3316
  ruleId,
3264
3317
  message,
3265
3318
  severity,
3266
- path: path16,
3319
+ path: path18,
3267
3320
  ...line !== void 0 ? { line } : {},
3268
- key: findingKey("sonar", ruleId, path16)
3321
+ key: findingKey("sonar", ruleId, path18)
3269
3322
  });
3270
3323
  }
3271
3324
  return findings;
@@ -3342,6 +3395,52 @@ function filterNewDrafts(drafts, existingTopics) {
3342
3395
  return drafts.filter((d) => !existing.has(d.topic));
3343
3396
  }
3344
3397
 
3398
+ // src/gate-precision.ts
3399
+ function round33(n) {
3400
+ return Math.round(n * 1e3) / 1e3;
3401
+ }
3402
+ function computeGatePrecision(events, usage, currentGate = "anchored") {
3403
+ let sensorCatches = 0;
3404
+ let antiPatternCatches = 0;
3405
+ for (const e of events) {
3406
+ if (e.source === "sensor") sensorCatches += 1;
3407
+ else if (e.source === "anti-pattern") antiPatternCatches += 1;
3408
+ }
3409
+ let applied = 0;
3410
+ let rejections = 0;
3411
+ for (const mem of Object.values(usage.by_id ?? {})) {
3412
+ applied += mem.applied_count ?? 0;
3413
+ rejections += mem.rejected_count ?? 0;
3414
+ }
3415
+ const useful = sensorCatches + antiPatternCatches + applied;
3416
+ const denom = useful + rejections;
3417
+ const precision = denom === 0 ? null : round33(useful / denom);
3418
+ return {
3419
+ sensor_catches: sensorCatches,
3420
+ anti_pattern_catches: antiPatternCatches,
3421
+ useful,
3422
+ rejections,
3423
+ precision,
3424
+ suggestion: suggestGate(precision, rejections, currentGate)
3425
+ };
3426
+ }
3427
+ function suggestGate(precision, rejections, currentGate) {
3428
+ if (precision === null || rejections < 3) return null;
3429
+ if (precision < 0.5 && (currentGate === "anchored" || currentGate === "strict")) {
3430
+ return {
3431
+ recommended: "review",
3432
+ reason: `Gate precision ${precision} with ${rejections} rejection(s) \u2014 the gate is crying wolf. Loosen to "review" (surface, don't hard-block) until the corpus is cleaner.`
3433
+ };
3434
+ }
3435
+ if (precision >= 0.85 && currentGate === "review") {
3436
+ return {
3437
+ recommended: "anchored",
3438
+ reason: `Gate precision ${precision} \u2014 catches are reliably real. Tighten to "anchored" so corroborated anti-patterns hard-block.`
3439
+ };
3440
+ }
3441
+ return null;
3442
+ }
3443
+
3345
3444
  // src/dashboard.ts
3346
3445
  var MS_PER_DAY4 = 24 * 60 * 60 * 1e3;
3347
3446
  function isAnchorless(fm) {
@@ -3483,6 +3582,11 @@ function buildDashboard(memories, usage, options = {}) {
3483
3582
  top: computeRecurrence(options.preventionEvents ?? []).top.slice(0, top)
3484
3583
  }
3485
3584
  },
3585
+ gate_precision: computeGatePrecision(
3586
+ options.preventionEvents ?? [],
3587
+ usage,
3588
+ options.antiPatternGate ?? "anchored"
3589
+ ),
3486
3590
  corpus: {
3487
3591
  memory_files: inventory.total,
3488
3592
  body_chars: bodyChars,
@@ -3490,6 +3594,235 @@ function buildDashboard(memories, usage, options = {}) {
3490
3594
  }
3491
3595
  };
3492
3596
  }
3597
+
3598
+ // src/failure-coverage.ts
3599
+ var MS_PER_HOUR = 36e5;
3600
+ function normalizeSummary(summary) {
3601
+ return summary.toLowerCase().replace(/\s+/g, " ").trim().slice(0, 160);
3602
+ }
3603
+ function findUncapturedFailures(failures, captureTimes, options = {}) {
3604
+ const now = (options.now ?? /* @__PURE__ */ new Date()).getTime();
3605
+ const windowMs = (options.windowHours ?? 24) * MS_PER_HOUR;
3606
+ const dedupe = options.dedupe ?? true;
3607
+ let latestCapture = 0;
3608
+ for (const iso of captureTimes) {
3609
+ const t = Date.parse(iso);
3610
+ if (Number.isFinite(t) && now - t <= windowMs && t > latestCapture) latestCapture = t;
3611
+ }
3612
+ const seen = /* @__PURE__ */ new Set();
3613
+ const out = [];
3614
+ for (const f of failures) {
3615
+ const t = Date.parse(f.ts);
3616
+ if (!Number.isFinite(t)) continue;
3617
+ if (now - t > windowMs) continue;
3618
+ if (t <= latestCapture) continue;
3619
+ if (dedupe) {
3620
+ const key = normalizeSummary(f.summary);
3621
+ if (seen.has(key)) continue;
3622
+ seen.add(key);
3623
+ }
3624
+ out.push({ ts: f.ts, tool: f.tool, summary: f.summary });
3625
+ }
3626
+ out.sort((a, b) => a.ts.localeCompare(b.ts));
3627
+ return out;
3628
+ }
3629
+
3630
+ // src/coverage.ts
3631
+ var DEFAULT_COVERING_TYPES = ["decision", "convention", "gotcha", "architecture"];
3632
+ function normalizePath(value) {
3633
+ return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/g, "");
3634
+ }
3635
+ function buildCoverageIndex(memories, coveringTypes = DEFAULT_COVERING_TYPES) {
3636
+ const types = new Set(coveringTypes);
3637
+ const covered = /* @__PURE__ */ new Set();
3638
+ for (const { memory } of memories) {
3639
+ const fm = memory.frontmatter;
3640
+ if (!types.has(fm.type)) continue;
3641
+ if (fm.status === "stale" || fm.status === "deprecated" || fm.status === "rejected") continue;
3642
+ for (const p of fm.anchor.paths) {
3643
+ const norm = normalizePath(p);
3644
+ if (norm) covered.add(norm);
3645
+ }
3646
+ }
3647
+ return covered;
3648
+ }
3649
+ function isCovered(file, coverage) {
3650
+ const target = normalizePath(file);
3651
+ if (coverage.has(target)) return true;
3652
+ for (const scope of coverage) {
3653
+ if (target === scope || target.startsWith(`${scope}/`)) return true;
3654
+ }
3655
+ return false;
3656
+ }
3657
+ function findCoverageGaps(hotFiles, memories, options = {}) {
3658
+ const minChanges = options.minChanges ?? 3;
3659
+ const limit = options.limit ?? 20;
3660
+ const coverage = buildCoverageIndex(memories, options.coveringTypes);
3661
+ const gaps = [];
3662
+ for (const hot of hotFiles) {
3663
+ if (hot.changes < minChanges) continue;
3664
+ if (isCovered(hot.path, coverage)) continue;
3665
+ gaps.push({ path: normalizePath(hot.path), changes: hot.changes });
3666
+ }
3667
+ gaps.sort((a, b) => b.changes - a.changes);
3668
+ return gaps.slice(0, limit);
3669
+ }
3670
+
3671
+ // src/eval-history.ts
3672
+ import { appendFile as appendFile4, mkdir as mkdir11, readFile as readFile14 } from "fs/promises";
3673
+ import { existsSync as existsSync15 } from "fs";
3674
+ import path17 from "path";
3675
+ function evalHistoryPath(paths) {
3676
+ return path17.join(paths.haiveDir, ".cache", "eval-history.jsonl");
3677
+ }
3678
+ async function appendEvalHistory(paths, entry) {
3679
+ const file = evalHistoryPath(paths);
3680
+ await mkdir11(path17.dirname(file), { recursive: true });
3681
+ await appendFile4(file, JSON.stringify(entry) + "\n", "utf8");
3682
+ }
3683
+ async function loadEvalHistory(paths) {
3684
+ const file = evalHistoryPath(paths);
3685
+ if (!existsSync15(file)) return [];
3686
+ const raw = await readFile14(file, "utf8").catch(() => "");
3687
+ const out = [];
3688
+ for (const line of raw.split("\n")) {
3689
+ const trimmed = line.trim();
3690
+ if (!trimmed) continue;
3691
+ try {
3692
+ const e = JSON.parse(trimmed);
3693
+ if (e && typeof e.at === "string" && typeof e.score === "number") out.push(e);
3694
+ } catch {
3695
+ }
3696
+ }
3697
+ return out;
3698
+ }
3699
+ function computeEvalTrend(entries, recentN = 10) {
3700
+ const sorted = [...entries].sort((a, b) => a.at.localeCompare(b.at));
3701
+ const scores = sorted.map((e) => e.score);
3702
+ const latest = scores.length > 0 ? scores[scores.length - 1] : null;
3703
+ const previous = scores.length > 1 ? scores[scores.length - 2] : null;
3704
+ const delta = latest !== null && previous !== null ? Math.round((latest - previous) * 1e3) / 1e3 : null;
3705
+ const best = scores.length > 0 ? Math.max(...scores) : null;
3706
+ return {
3707
+ latest,
3708
+ previous,
3709
+ delta,
3710
+ best,
3711
+ runs: scores.length,
3712
+ recent: scores.slice(-recentN),
3713
+ regressed: delta !== null && delta < 0
3714
+ };
3715
+ }
3716
+
3717
+ // src/conflict-resolve.ts
3718
+ var STATUS_RANK = {
3719
+ validated: 4,
3720
+ proposed: 3,
3721
+ draft: 2,
3722
+ stale: 1,
3723
+ deprecated: 0,
3724
+ rejected: 0
3725
+ };
3726
+ function statusRank(fm) {
3727
+ return STATUS_RANK[fm.status] ?? 2;
3728
+ }
3729
+ function planConflictResolution(a, b) {
3730
+ const fa = a.memory.frontmatter;
3731
+ const fb = b.memory.frontmatter;
3732
+ const ra = statusRank(fa);
3733
+ const rb = statusRank(fb);
3734
+ let winner;
3735
+ let loser;
3736
+ let reason;
3737
+ if (ra !== rb) {
3738
+ [winner, loser] = ra > rb ? [a, b] : [b, a];
3739
+ reason = `status (${winner.memory.frontmatter.status} beats ${loser.memory.frontmatter.status})`;
3740
+ } else if (fa.revision_count !== fb.revision_count) {
3741
+ [winner, loser] = fa.revision_count > fb.revision_count ? [a, b] : [b, a];
3742
+ reason = `revision_count (${winner.memory.frontmatter.revision_count} > ${loser.memory.frontmatter.revision_count})`;
3743
+ } else {
3744
+ const cmp = fa.created_at.localeCompare(fb.created_at);
3745
+ [winner, loser] = cmp >= 0 ? [a, b] : [b, a];
3746
+ reason = `recency (${winner.memory.frontmatter.created_at} is newer)`;
3747
+ }
3748
+ const keepId = winner.memory.frontmatter.id;
3749
+ const supersedeId = loser.memory.frontmatter.id;
3750
+ return {
3751
+ keep_id: keepId,
3752
+ supersede_id: supersedeId,
3753
+ reason,
3754
+ stale_reason: `Superseded by ${keepId} (conflict resolved on ${reason}).`
3755
+ };
3756
+ }
3757
+
3758
+ // src/seed-git.ts
3759
+ var REVERT_RE = /^Revert\s+"(.+)"\s*$/i;
3760
+ var FIXUP_RE = /^(?:fixup!|hotfix[:!]|fix[:!]\s*revert|revert\s+revert)/i;
3761
+ var URGENT_FIX_RE = /\b(hotfix|urgent fix|emergency fix|critical fix|broke production|broken build)\b/i;
3762
+ function slugify(text) {
3763
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60) || "reverted-change";
3764
+ }
3765
+ function proposeSeedsFromCommits(commits, limit = 20) {
3766
+ const out = [];
3767
+ const seen = /* @__PURE__ */ new Set();
3768
+ for (const commit of commits) {
3769
+ const subject = commit.subject.trim();
3770
+ let what = null;
3771
+ let kind = null;
3772
+ const revert = subject.match(REVERT_RE);
3773
+ if (revert) {
3774
+ what = revert[1].trim();
3775
+ kind = "revert";
3776
+ } else if (FIXUP_RE.test(subject) || URGENT_FIX_RE.test(subject)) {
3777
+ what = subject.replace(FIXUP_RE, "").trim() || subject;
3778
+ kind = "fixup";
3779
+ }
3780
+ if (!what || !kind) continue;
3781
+ const slug = slugify(what);
3782
+ if (seen.has(slug)) continue;
3783
+ seen.add(slug);
3784
+ out.push({
3785
+ slug,
3786
+ what,
3787
+ why_failed: kind === "revert" ? `This change was reverted in commit ${commit.sha} \u2014 it caused a regression and was backed out. Verify the root cause before re-attempting.` : `This area required an urgent fix (commit ${commit.sha}: "${subject}") \u2014 it shipped broken once. Treat changes here with extra care.`,
3788
+ paths: (commit.files ?? []).slice(0, 8),
3789
+ source_sha: commit.sha,
3790
+ kind
3791
+ });
3792
+ if (out.length >= limit) break;
3793
+ }
3794
+ return out;
3795
+ }
3796
+
3797
+ // src/merge-memory.ts
3798
+ function safeParse(raw) {
3799
+ try {
3800
+ return parseMemory(raw);
3801
+ } catch {
3802
+ return null;
3803
+ }
3804
+ }
3805
+ function mergeMemoryVersions(ours, theirs) {
3806
+ if (ours === theirs) {
3807
+ return { content: ours, winner: "ours", reason: "identical" };
3808
+ }
3809
+ const a = safeParse(ours);
3810
+ const b = safeParse(theirs);
3811
+ if (!a || !b) {
3812
+ return { content: ours, winner: "ours", reason: "unparseable side \u2014 kept ours" };
3813
+ }
3814
+ const ra = a.frontmatter.revision_count ?? 0;
3815
+ const rb = b.frontmatter.revision_count ?? 0;
3816
+ if (ra !== rb) {
3817
+ return rb > ra ? { content: theirs, winner: "theirs", reason: `higher revision_count (${rb} > ${ra})` } : { content: ours, winner: "ours", reason: `higher revision_count (${ra} > ${rb})` };
3818
+ }
3819
+ const ca = a.frontmatter.created_at ?? "";
3820
+ const cb = b.frontmatter.created_at ?? "";
3821
+ const cmp = cb.localeCompare(ca);
3822
+ if (cmp > 0) return { content: theirs, winner: "theirs", reason: `newer created_at (${cb})` };
3823
+ if (cmp < 0) return { content: ours, winner: "ours", reason: `newer created_at (${ca})` };
3824
+ return { content: ours, winner: "ours", reason: "tie \u2014 kept ours" };
3825
+ }
3493
3826
  export {
3494
3827
  AUTOPILOT_DEFAULTS,
3495
3828
  ActivationSchema,
@@ -3516,6 +3849,7 @@ export {
3516
3849
  MemoryTypeSchema,
3517
3850
  PREVENTION_DEBOUNCE_MS,
3518
3851
  PROJECT_CONTEXT_FILE,
3852
+ PROJECT_CONTEXT_THROTTLE_MS,
3519
3853
  RUNTIME_JOURNAL_FILENAME,
3520
3854
  SESSION_RECAP_TTL_MS,
3521
3855
  STACK_PACK_TAG,
@@ -3529,12 +3863,14 @@ export {
3529
3863
  aggregateUsage,
3530
3864
  allocateBudget,
3531
3865
  antiPatternGateParams,
3866
+ appendEvalHistory,
3532
3867
  appendPreventionEvent,
3533
3868
  appendRuntimeJournalEntry,
3534
3869
  appendUsageEvent,
3535
3870
  briefingMarkerPath,
3536
3871
  briefingMarkersDir,
3537
3872
  buildCodeMap,
3873
+ buildCoverageIndex,
3538
3874
  buildDashboard,
3539
3875
  buildDocFrequency,
3540
3876
  buildFrontmatter,
@@ -3545,6 +3881,8 @@ export {
3545
3881
  compareEvalReports,
3546
3882
  compareImpact,
3547
3883
  compileRegexSensor,
3884
+ computeEvalTrend,
3885
+ computeGatePrecision,
3548
3886
  computeImpact,
3549
3887
  computePreventionTrend,
3550
3888
  computeRecurrence,
@@ -3559,21 +3897,26 @@ export {
3559
3897
  emptyUsageIndex,
3560
3898
  enforcementDir,
3561
3899
  estimateTokens,
3900
+ evalHistoryPath,
3562
3901
  evaluateSkillActivation,
3563
3902
  extractActionsBriefBody,
3564
3903
  extractSnippet,
3565
3904
  filterNewDrafts,
3905
+ findCoverageGaps,
3566
3906
  findLexicalConflictPairs,
3567
3907
  findProjectRoot,
3568
3908
  findTopicStatusConflictPairs,
3909
+ findUncapturedFailures,
3569
3910
  findingBody,
3570
3911
  findingToDraft,
3571
3912
  firstMemoryOneLine,
3572
3913
  getUsage,
3573
3914
  globToRegExp,
3574
3915
  hasRecentBriefingMarker,
3916
+ hashProjectContext,
3575
3917
  inferModulesFromPaths,
3576
3918
  isAutoPromoteEligible,
3919
+ isCovered,
3577
3920
  isDecaying,
3578
3921
  isDistinctiveToken,
3579
3922
  isFreshIsoDate,
@@ -3589,12 +3932,14 @@ export {
3589
3932
  loadCodeMap,
3590
3933
  loadConfig,
3591
3934
  loadConfigSync,
3935
+ loadEvalHistory,
3592
3936
  loadMemoriesFromDir,
3593
3937
  loadMemory,
3594
3938
  loadPreventionEvents,
3595
3939
  loadUsageIndex,
3596
3940
  memoryFilePath,
3597
3941
  memoryMatchesAnchorPaths,
3942
+ mergeMemoryVersions,
3598
3943
  newMemoryId,
3599
3944
  normalizeFindingSeverity,
3600
3945
  normalizeSessionId,
@@ -3606,7 +3951,10 @@ export {
3606
3951
  parseSonar,
3607
3952
  pathsOverlap,
3608
3953
  pickSnippetNeedle,
3954
+ planConflictResolution,
3609
3955
  preventionLogPath,
3956
+ projectContextRecentlyEmitted,
3957
+ proposeSeedsFromCommits,
3610
3958
  pullCrossRepoSources,
3611
3959
  queryCodeMap,
3612
3960
  rankMemoriesLexical,
@@ -3615,6 +3963,7 @@ export {
3615
3963
  readUsageEvents,
3616
3964
  recordApplied,
3617
3965
  recordPrevention,
3966
+ recordProjectContextEmission,
3618
3967
  recordRejection,
3619
3968
  relPathFrom,
3620
3969
  resolveBriefingBudget,
@@ -3630,12 +3979,14 @@ export {
3630
3979
  saveUsageIndex,
3631
3980
  scoreRetrievalCase,
3632
3981
  scoreSensorCase,
3982
+ selectCommandSensors,
3633
3983
  sensorAppliesToPath,
3634
3984
  sensorTargetsFromDiff,
3635
3985
  serializeMemory,
3636
3986
  snapshotContract,
3637
3987
  specificityScore,
3638
3988
  stripPrivate,
3989
+ suggestGate,
3639
3990
  suggestSensorFromMemory,
3640
3991
  suggestTopicKey,
3641
3992
  summarizeImpact,