@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.d.ts +52 -3
- package/dist/index.js +201 -126
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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()) /
|
|
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
|
|
1230
|
-
import { existsSync as
|
|
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
|
|
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
|
|
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 (!
|
|
1279
|
-
return JSON.parse(await
|
|
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
|
|
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 =
|
|
1356
|
+
const rel = path7.relative(root, abs).replace(/\\/g, "/");
|
|
1292
1357
|
if (rel.startsWith(".ai/")) continue;
|
|
1293
|
-
const content = await
|
|
1294
|
-
const ext =
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
1629
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1565
1630
|
import { readFileSync } from "fs";
|
|
1566
|
-
import { readFile as
|
|
1567
|
-
import
|
|
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
|
|
1704
|
+
return path8.join(paths.haiveDir, CONFIG_FILE);
|
|
1640
1705
|
}
|
|
1641
1706
|
async function loadConfig(paths) {
|
|
1642
1707
|
const file = configPath(paths);
|
|
1643
|
-
if (!
|
|
1708
|
+
if (!existsSync6(file)) return { ...DEFAULT_CONFIG };
|
|
1644
1709
|
try {
|
|
1645
|
-
const raw = await
|
|
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 (!
|
|
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
|
|
1687
|
-
import { mkdir as
|
|
1688
|
-
import
|
|
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 =
|
|
1692
|
-
if (!
|
|
1756
|
+
const mapPath = path9.join(cacheDir, "import-map.json");
|
|
1757
|
+
if (!existsSync7(mapPath)) return {};
|
|
1693
1758
|
try {
|
|
1694
|
-
return JSON.parse(await
|
|
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(
|
|
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 =
|
|
1722
|
-
if (!
|
|
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 (!
|
|
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 =
|
|
1759
|
-
await
|
|
1760
|
-
const cacheDir =
|
|
1761
|
-
await
|
|
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 &&
|
|
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 =
|
|
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 =
|
|
1822
|
-
await
|
|
1823
|
-
if (
|
|
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
|
|
1849
|
-
import { readFile as
|
|
1850
|
-
import
|
|
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 =
|
|
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) =>
|
|
2005
|
+
return configuredFiles.map((f) => path10.resolve(projectRoot, f)).filter(existsSync8);
|
|
1941
2006
|
}
|
|
1942
|
-
return KNOWN_MANIFESTS.map(({ name }) =>
|
|
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 =
|
|
1946
|
-
await
|
|
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
|
|
2016
|
+
const content = await readFile8(manifestPath, "utf8");
|
|
1952
2017
|
const currentDeps = parser(content);
|
|
1953
|
-
const lockName = `deps-${
|
|
1954
|
-
const lockPath =
|
|
1955
|
-
if (!
|
|
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:
|
|
1958
|
-
format:
|
|
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
|
|
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:
|
|
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
|
|
1993
|
-
import { readFile as
|
|
1994
|
-
import
|
|
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
|
|
2262
|
+
return path11.join(haiveDir, "contracts", `${name}.lock`);
|
|
2198
2263
|
}
|
|
2199
2264
|
async function snapshotContract(projectRoot, haiveDir, contract) {
|
|
2200
|
-
const filePath =
|
|
2201
|
-
if (!
|
|
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
|
|
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 =
|
|
2215
|
-
await
|
|
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 =
|
|
2221
|
-
if (!
|
|
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 (!
|
|
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
|
|
2230
|
-
const beforeSnapshot = JSON.parse(await
|
|
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
|
|
2259
|
-
import { existsSync as
|
|
2260
|
-
import
|
|
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
|
|
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 =
|
|
2270
|
-
if (!
|
|
2271
|
-
await
|
|
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 (!
|
|
2278
|
-
const raw = await
|
|
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 (!
|
|
2392
|
+
if (!existsSync10(file)) return { exists: false, size_bytes: 0, lines: 0 };
|
|
2328
2393
|
const st = await stat2(file);
|
|
2329
|
-
const raw = await
|
|
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
|
|
2400
|
-
import
|
|
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 (
|
|
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 =
|
|
2476
|
+
const cwd = path13.resolve(opts.cwd ?? process.cwd());
|
|
2412
2477
|
const raw = env.HAIVE_PROJECT_ROOT;
|
|
2413
|
-
const explicit = raw !== void 0 && raw !== "" ?
|
|
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:
|
|
2422
|
-
memories_dir_exists:
|
|
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
|
|
2678
|
-
import { existsSync as
|
|
2679
|
-
import
|
|
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
|
|
2747
|
+
return path14.join(paths.runtimeDir, RUNTIME_JOURNAL_FILENAME);
|
|
2683
2748
|
}
|
|
2684
2749
|
async function appendRuntimeJournalEntry(paths, entry) {
|
|
2685
2750
|
try {
|
|
2686
|
-
await
|
|
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
|
|
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 (!
|
|
2769
|
+
if (!existsSync12(file) || limit <= 0) return [];
|
|
2705
2770
|
try {
|
|
2706
|
-
const raw = await
|
|
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
|
|
2723
|
-
import { existsSync as
|
|
2724
|
-
import
|
|
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
|
|
2793
|
+
return path15.join(paths.runtimeDir, "enforcement");
|
|
2729
2794
|
}
|
|
2730
2795
|
function briefingMarkersDir(paths) {
|
|
2731
|
-
return
|
|
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
|
|
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
|
|
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 (
|
|
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(
|
|
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
|
|
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 (
|
|
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(
|
|
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
|
|
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,
|
|
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(
|
|
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,
|
|
3128
|
-
return `${tool}:${ruleId}:${
|
|
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
|
|
3163
|
-
if (!
|
|
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:
|
|
3236
|
+
path: path16,
|
|
3172
3237
|
...line !== void 0 ? { line } : {},
|
|
3173
3238
|
...snippet ? { snippet } : {},
|
|
3174
|
-
key: findingKey(tool, ruleId,
|
|
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
|
|
3194
|
-
if (!
|
|
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:
|
|
3266
|
+
path: path16,
|
|
3202
3267
|
...line !== void 0 ? { line } : {},
|
|
3203
|
-
key: findingKey("sonar", ruleId,
|
|
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
|
|
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()) /
|
|
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,
|