@hiveai/core 0.13.8 → 0.14.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.d.ts +68 -3
- package/dist/index.js +245 -135
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -791,6 +791,102 @@ 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
|
+
|
|
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
|
+
|
|
794
890
|
// src/eval.ts
|
|
795
891
|
function round32(n) {
|
|
796
892
|
return Math.round(n * 1e3) / 1e3;
|
|
@@ -926,13 +1022,13 @@ var DEFAULT_CONFIDENCE_THRESHOLDS = {
|
|
|
926
1022
|
decayDays: 180,
|
|
927
1023
|
hardDecayDays: 365
|
|
928
1024
|
};
|
|
929
|
-
var
|
|
1025
|
+
var MS_PER_DAY3 = 24 * 60 * 60 * 1e3;
|
|
930
1026
|
function deriveConfidence(fm, usage, thresholds = DEFAULT_CONFIDENCE_THRESHOLDS, now = /* @__PURE__ */ new Date()) {
|
|
931
1027
|
if (fm.status === "stale" || fm.status === "deprecated" || fm.status === "rejected") return "stale";
|
|
932
1028
|
const baseLevel = baseConfidence(fm, usage, thresholds);
|
|
933
1029
|
if (baseLevel !== "authoritative" && baseLevel !== "trusted") return baseLevel;
|
|
934
1030
|
const anchor = usage.last_read_at ?? fm.created_at;
|
|
935
|
-
const ageDays = (now.getTime() - new Date(anchor).getTime()) /
|
|
1031
|
+
const ageDays = (now.getTime() - new Date(anchor).getTime()) / MS_PER_DAY3;
|
|
936
1032
|
if (Number.isNaN(ageDays) || ageDays <= 0) return baseLevel;
|
|
937
1033
|
if (ageDays >= thresholds.hardDecayDays) {
|
|
938
1034
|
return "low";
|
|
@@ -1226,10 +1322,10 @@ function allocateBudget(parts, maxTokens) {
|
|
|
1226
1322
|
}
|
|
1227
1323
|
|
|
1228
1324
|
// src/code-map.ts
|
|
1229
|
-
import { mkdir as
|
|
1230
|
-
import { existsSync as
|
|
1325
|
+
import { mkdir as mkdir4, readFile as readFile6, readdir as readdir3, writeFile as writeFile3 } from "fs/promises";
|
|
1326
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1231
1327
|
import { spawnSync } from "child_process";
|
|
1232
|
-
import
|
|
1328
|
+
import path8 from "path";
|
|
1233
1329
|
var CODE_MAP_FILE = "code-map.json";
|
|
1234
1330
|
var DEFAULT_INCLUDE = [
|
|
1235
1331
|
".ts",
|
|
@@ -1271,27 +1367,27 @@ var DEFAULT_EXCLUDE = [
|
|
|
1271
1367
|
];
|
|
1272
1368
|
var TEST_FILE_RE = /\.(test|spec)\.[a-z]+$/i;
|
|
1273
1369
|
function codeMapPath(paths) {
|
|
1274
|
-
return
|
|
1370
|
+
return path8.join(paths.haiveDir, CODE_MAP_FILE);
|
|
1275
1371
|
}
|
|
1276
1372
|
async function loadCodeMap(paths) {
|
|
1277
1373
|
const file = codeMapPath(paths);
|
|
1278
|
-
if (!
|
|
1279
|
-
return JSON.parse(await
|
|
1374
|
+
if (!existsSync6(file)) return null;
|
|
1375
|
+
return JSON.parse(await readFile6(file, "utf8"));
|
|
1280
1376
|
}
|
|
1281
1377
|
async function saveCodeMap(paths, map) {
|
|
1282
1378
|
const file = codeMapPath(paths);
|
|
1283
|
-
await
|
|
1284
|
-
await
|
|
1379
|
+
await mkdir4(path8.dirname(file), { recursive: true });
|
|
1380
|
+
await writeFile3(file, JSON.stringify(map, null, 2), "utf8");
|
|
1285
1381
|
}
|
|
1286
1382
|
async function buildCodeMap(root, options = {}) {
|
|
1287
1383
|
const include = new Set(options.includeExtensions ?? DEFAULT_INCLUDE);
|
|
1288
1384
|
const exclude = new Set(options.excludeDirs ?? DEFAULT_EXCLUDE);
|
|
1289
1385
|
const files = {};
|
|
1290
1386
|
for await (const abs of collectSourceFiles(root, include, exclude, options.includeUntracked)) {
|
|
1291
|
-
const rel =
|
|
1387
|
+
const rel = path8.relative(root, abs).replace(/\\/g, "/");
|
|
1292
1388
|
if (rel.startsWith(".ai/")) continue;
|
|
1293
|
-
const content = await
|
|
1294
|
-
const ext =
|
|
1389
|
+
const content = await readFile6(abs, "utf8");
|
|
1390
|
+
const ext = path8.extname(abs).toLowerCase();
|
|
1295
1391
|
const entry = parseFile(content, ext);
|
|
1296
1392
|
if (entry.exports.length > 0) files[rel] = entry;
|
|
1297
1393
|
}
|
|
@@ -1305,7 +1401,7 @@ async function buildCodeMap(root, options = {}) {
|
|
|
1305
1401
|
async function* collectSourceFiles(root, include, exclude, includeUntracked) {
|
|
1306
1402
|
const gitFiles = gitSourceFiles(root, include, exclude, includeUntracked === true);
|
|
1307
1403
|
if (gitFiles) {
|
|
1308
|
-
for (const rel of gitFiles) yield
|
|
1404
|
+
for (const rel of gitFiles) yield path8.join(root, rel);
|
|
1309
1405
|
return;
|
|
1310
1406
|
}
|
|
1311
1407
|
yield* walkSourceFiles(root, include, exclude);
|
|
@@ -1332,11 +1428,11 @@ async function* walkSourceFiles(dir, include, exclude) {
|
|
|
1332
1428
|
if (entry.isDirectory()) continue;
|
|
1333
1429
|
}
|
|
1334
1430
|
if (exclude.has(entry.name)) continue;
|
|
1335
|
-
const full =
|
|
1431
|
+
const full = path8.join(dir, entry.name);
|
|
1336
1432
|
if (entry.isDirectory()) {
|
|
1337
1433
|
yield* walkSourceFiles(full, include, exclude);
|
|
1338
1434
|
} else if (entry.isFile()) {
|
|
1339
|
-
const ext =
|
|
1435
|
+
const ext = path8.extname(entry.name).toLowerCase();
|
|
1340
1436
|
if (include.has(ext) && !TEST_FILE_RE.test(entry.name)) yield full;
|
|
1341
1437
|
}
|
|
1342
1438
|
}
|
|
@@ -1347,7 +1443,7 @@ function isIncludedSourcePath(rel, include, exclude) {
|
|
|
1347
1443
|
const parts = normalized.split("/");
|
|
1348
1444
|
if (parts.some((part) => exclude.has(part))) return false;
|
|
1349
1445
|
const base = parts.at(-1) ?? "";
|
|
1350
|
-
const ext =
|
|
1446
|
+
const ext = path8.extname(base).toLowerCase();
|
|
1351
1447
|
return include.has(ext) && !TEST_FILE_RE.test(base);
|
|
1352
1448
|
}
|
|
1353
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;
|
|
@@ -1561,10 +1657,10 @@ function queryCodeMap(map, options) {
|
|
|
1561
1657
|
}
|
|
1562
1658
|
|
|
1563
1659
|
// src/config.ts
|
|
1564
|
-
import { existsSync as
|
|
1660
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1565
1661
|
import { readFileSync } from "fs";
|
|
1566
|
-
import { readFile as
|
|
1567
|
-
import
|
|
1662
|
+
import { readFile as readFile7, writeFile as writeFile4 } from "fs/promises";
|
|
1663
|
+
import path9 from "path";
|
|
1568
1664
|
var CONFIG_FILE = "haive.config.json";
|
|
1569
1665
|
var DEFAULT_CONFIG = {
|
|
1570
1666
|
autopilot: false,
|
|
@@ -1636,13 +1732,13 @@ function antiPatternGateParams(gate) {
|
|
|
1636
1732
|
}
|
|
1637
1733
|
}
|
|
1638
1734
|
function configPath(paths) {
|
|
1639
|
-
return
|
|
1735
|
+
return path9.join(paths.haiveDir, CONFIG_FILE);
|
|
1640
1736
|
}
|
|
1641
1737
|
async function loadConfig(paths) {
|
|
1642
1738
|
const file = configPath(paths);
|
|
1643
|
-
if (!
|
|
1739
|
+
if (!existsSync7(file)) return { ...DEFAULT_CONFIG };
|
|
1644
1740
|
try {
|
|
1645
|
-
const raw = await
|
|
1741
|
+
const raw = await readFile7(file, "utf8");
|
|
1646
1742
|
const parsed = JSON.parse(raw);
|
|
1647
1743
|
const merged = mergeConfig(DEFAULT_CONFIG, parsed);
|
|
1648
1744
|
if (merged.autopilot) {
|
|
@@ -1655,7 +1751,7 @@ async function loadConfig(paths) {
|
|
|
1655
1751
|
}
|
|
1656
1752
|
function loadConfigSync(paths) {
|
|
1657
1753
|
const file = configPath(paths);
|
|
1658
|
-
if (!
|
|
1754
|
+
if (!existsSync7(file)) return { ...DEFAULT_CONFIG };
|
|
1659
1755
|
try {
|
|
1660
1756
|
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
1661
1757
|
const merged = mergeConfig(DEFAULT_CONFIG, parsed);
|
|
@@ -1665,7 +1761,7 @@ function loadConfigSync(paths) {
|
|
|
1665
1761
|
}
|
|
1666
1762
|
}
|
|
1667
1763
|
async function saveConfig(paths, config) {
|
|
1668
|
-
await
|
|
1764
|
+
await writeFile4(configPath(paths), JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
1669
1765
|
}
|
|
1670
1766
|
function mergeConfig(base, override) {
|
|
1671
1767
|
return {
|
|
@@ -1683,21 +1779,21 @@ function mergeConfig(base, override) {
|
|
|
1683
1779
|
}
|
|
1684
1780
|
|
|
1685
1781
|
// src/cross-repo.ts
|
|
1686
|
-
import { existsSync as
|
|
1687
|
-
import { mkdir as
|
|
1688
|
-
import
|
|
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";
|
|
1689
1785
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1690
1786
|
async function loadImportMap(cacheDir) {
|
|
1691
|
-
const mapPath =
|
|
1692
|
-
if (!
|
|
1787
|
+
const mapPath = path10.join(cacheDir, "import-map.json");
|
|
1788
|
+
if (!existsSync8(mapPath)) return {};
|
|
1693
1789
|
try {
|
|
1694
|
-
return JSON.parse(await
|
|
1790
|
+
return JSON.parse(await readFile8(mapPath, "utf8"));
|
|
1695
1791
|
} catch {
|
|
1696
1792
|
return {};
|
|
1697
1793
|
}
|
|
1698
1794
|
}
|
|
1699
1795
|
async function saveImportMap(cacheDir, map) {
|
|
1700
|
-
await
|
|
1796
|
+
await writeFile5(path10.join(cacheDir, "import-map.json"), JSON.stringify(map, null, 2) + "\n", "utf8");
|
|
1701
1797
|
}
|
|
1702
1798
|
async function pullCrossRepoSources(paths, config, projectRoot) {
|
|
1703
1799
|
const sources = config.crossRepoSources ?? [];
|
|
@@ -1718,8 +1814,8 @@ async function pullFromSource(paths, source, projectRoot) {
|
|
|
1718
1814
|
};
|
|
1719
1815
|
let sourceRoot = null;
|
|
1720
1816
|
if (source.path) {
|
|
1721
|
-
const resolved =
|
|
1722
|
-
if (!
|
|
1817
|
+
const resolved = path10.resolve(projectRoot, source.path);
|
|
1818
|
+
if (!existsSync8(resolved)) {
|
|
1723
1819
|
report.errors.push(`Path not found: ${resolved}`);
|
|
1724
1820
|
return report;
|
|
1725
1821
|
}
|
|
@@ -1732,7 +1828,7 @@ async function pullFromSource(paths, source, projectRoot) {
|
|
|
1732
1828
|
return report;
|
|
1733
1829
|
}
|
|
1734
1830
|
const sourcePaths = resolveHaivePaths(sourceRoot);
|
|
1735
|
-
if (!
|
|
1831
|
+
if (!existsSync8(sourcePaths.memoriesDir)) {
|
|
1736
1832
|
report.errors.push(`No .ai/memories/ found at ${sourceRoot}`);
|
|
1737
1833
|
return report;
|
|
1738
1834
|
}
|
|
@@ -1755,10 +1851,10 @@ async function pullFromSource(paths, source, projectRoot) {
|
|
|
1755
1851
|
report.skipped.push("no shared memories found in source");
|
|
1756
1852
|
return report;
|
|
1757
1853
|
}
|
|
1758
|
-
const destDir =
|
|
1759
|
-
await
|
|
1760
|
-
const cacheDir =
|
|
1761
|
-
await
|
|
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 });
|
|
1762
1858
|
const importMap = await loadImportMap(cacheDir);
|
|
1763
1859
|
const mapDirty = false;
|
|
1764
1860
|
let dirty = mapDirty;
|
|
@@ -1772,7 +1868,7 @@ async function pullFromSource(paths, source, projectRoot) {
|
|
|
1772
1868
|
|
|
1773
1869
|
`;
|
|
1774
1870
|
const existingLocalPath = importMap[sourceId];
|
|
1775
|
-
if (existingLocalPath &&
|
|
1871
|
+
if (existingLocalPath && existsSync8(existingLocalPath)) {
|
|
1776
1872
|
const existingFiles = await loadMemoriesFromDir(destDir);
|
|
1777
1873
|
const existingEntry = existingFiles.find(({ filePath }) => filePath === existingLocalPath);
|
|
1778
1874
|
const sourceBodyStripped = memory.body.trim();
|
|
@@ -1783,7 +1879,7 @@ async function pullFromSource(paths, source, projectRoot) {
|
|
|
1783
1879
|
}
|
|
1784
1880
|
const updatedBody = importedBodyPrefix + memory.body;
|
|
1785
1881
|
if (existingEntry) {
|
|
1786
|
-
await
|
|
1882
|
+
await writeFile5(
|
|
1787
1883
|
existingLocalPath,
|
|
1788
1884
|
serializeMemory({ frontmatter: existingEntry.memory.frontmatter, body: updatedBody }),
|
|
1789
1885
|
"utf8"
|
|
@@ -1807,8 +1903,8 @@ async function pullFromSource(paths, source, projectRoot) {
|
|
|
1807
1903
|
topic: fm.topic ? `${source.name}:${fm.topic}` : void 0
|
|
1808
1904
|
});
|
|
1809
1905
|
const body = importedBodyPrefix + memory.body;
|
|
1810
|
-
const destPath =
|
|
1811
|
-
await
|
|
1906
|
+
const destPath = path10.join(destDir, `${newFm.id}.md`);
|
|
1907
|
+
await writeFile5(destPath, serializeMemory({ frontmatter: newFm, body }), "utf8");
|
|
1812
1908
|
importMap[sourceId] = destPath;
|
|
1813
1909
|
dirty = true;
|
|
1814
1910
|
report.imported.push(sourceId);
|
|
@@ -1818,9 +1914,9 @@ async function pullFromSource(paths, source, projectRoot) {
|
|
|
1818
1914
|
return report;
|
|
1819
1915
|
}
|
|
1820
1916
|
async function cloneOrFetchGitSource(source, paths, report) {
|
|
1821
|
-
const cacheDir =
|
|
1822
|
-
await
|
|
1823
|
-
if (
|
|
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"))) {
|
|
1824
1920
|
const result = spawnSync2("git", ["fetch", "--depth=1", "origin"], {
|
|
1825
1921
|
cwd: cacheDir,
|
|
1826
1922
|
encoding: "utf8"
|
|
@@ -1845,9 +1941,9 @@ async function cloneOrFetchGitSource(source, paths, report) {
|
|
|
1845
1941
|
}
|
|
1846
1942
|
|
|
1847
1943
|
// src/dep-tracker.ts
|
|
1848
|
-
import { existsSync as
|
|
1849
|
-
import { readFile as
|
|
1850
|
-
import
|
|
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";
|
|
1851
1947
|
function parsePackageJson(content) {
|
|
1852
1948
|
try {
|
|
1853
1949
|
const pkg = JSON.parse(content);
|
|
@@ -1919,7 +2015,7 @@ var KNOWN_MANIFESTS = [
|
|
|
1919
2015
|
{ name: "pom.xml", parser: parsePomXml }
|
|
1920
2016
|
];
|
|
1921
2017
|
function getParser(file) {
|
|
1922
|
-
const base =
|
|
2018
|
+
const base = path11.basename(file);
|
|
1923
2019
|
return KNOWN_MANIFESTS.find((m) => m.name === base)?.parser ?? null;
|
|
1924
2020
|
}
|
|
1925
2021
|
function extractMajor(version) {
|
|
@@ -1937,32 +2033,32 @@ function isMajorBump(from, to) {
|
|
|
1937
2033
|
}
|
|
1938
2034
|
function resolveManifestFiles(projectRoot, configuredFiles) {
|
|
1939
2035
|
if (configuredFiles !== void 0) {
|
|
1940
|
-
return configuredFiles.map((f) =>
|
|
2036
|
+
return configuredFiles.map((f) => path11.resolve(projectRoot, f)).filter(existsSync9);
|
|
1941
2037
|
}
|
|
1942
|
-
return KNOWN_MANIFESTS.map(({ name }) =>
|
|
2038
|
+
return KNOWN_MANIFESTS.map(({ name }) => path11.join(projectRoot, name)).filter(existsSync9);
|
|
1943
2039
|
}
|
|
1944
2040
|
async function trackDependencies(projectRoot, haiveDir, manifestFiles) {
|
|
1945
|
-
const contractsDir =
|
|
1946
|
-
await
|
|
2041
|
+
const contractsDir = path11.join(haiveDir, "contracts");
|
|
2042
|
+
await mkdir6(contractsDir, { recursive: true });
|
|
1947
2043
|
const results = [];
|
|
1948
2044
|
for (const manifestPath of manifestFiles) {
|
|
1949
2045
|
const parser = getParser(manifestPath);
|
|
1950
2046
|
if (!parser) continue;
|
|
1951
|
-
const content = await
|
|
2047
|
+
const content = await readFile9(manifestPath, "utf8");
|
|
1952
2048
|
const currentDeps = parser(content);
|
|
1953
|
-
const lockName = `deps-${
|
|
1954
|
-
const lockPath =
|
|
1955
|
-
if (!
|
|
2049
|
+
const lockName = `deps-${path11.basename(manifestPath)}.lock`;
|
|
2050
|
+
const lockPath = path11.join(contractsDir, lockName);
|
|
2051
|
+
if (!existsSync9(lockPath)) {
|
|
1956
2052
|
const snapshot2 = {
|
|
1957
|
-
file:
|
|
1958
|
-
format:
|
|
2053
|
+
file: path11.relative(projectRoot, manifestPath),
|
|
2054
|
+
format: path11.basename(manifestPath),
|
|
1959
2055
|
captured_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1960
2056
|
deps: currentDeps
|
|
1961
2057
|
};
|
|
1962
|
-
await
|
|
2058
|
+
await writeFile6(lockPath, JSON.stringify(snapshot2, null, 2) + "\n", "utf8");
|
|
1963
2059
|
continue;
|
|
1964
2060
|
}
|
|
1965
|
-
const snapshot = JSON.parse(await
|
|
2061
|
+
const snapshot = JSON.parse(await readFile9(lockPath, "utf8"));
|
|
1966
2062
|
const changes = [];
|
|
1967
2063
|
for (const [name, currentVer] of Object.entries(currentDeps)) {
|
|
1968
2064
|
const prevVer = snapshot.deps[name];
|
|
@@ -1976,22 +2072,22 @@ async function trackDependencies(projectRoot, haiveDir, manifestFiles) {
|
|
|
1976
2072
|
}
|
|
1977
2073
|
}
|
|
1978
2074
|
if (changes.length > 0) {
|
|
1979
|
-
results.push({ file:
|
|
2075
|
+
results.push({ file: path11.relative(projectRoot, manifestPath), changes });
|
|
1980
2076
|
const updated = {
|
|
1981
2077
|
...snapshot,
|
|
1982
2078
|
captured_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1983
2079
|
deps: currentDeps
|
|
1984
2080
|
};
|
|
1985
|
-
await
|
|
2081
|
+
await writeFile6(lockPath, JSON.stringify(updated, null, 2) + "\n", "utf8");
|
|
1986
2082
|
}
|
|
1987
2083
|
}
|
|
1988
2084
|
return results;
|
|
1989
2085
|
}
|
|
1990
2086
|
|
|
1991
2087
|
// src/contract-watcher.ts
|
|
1992
|
-
import { existsSync as
|
|
1993
|
-
import { readFile as
|
|
1994
|
-
import
|
|
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";
|
|
1995
2091
|
import crypto from "crypto";
|
|
1996
2092
|
function sha256(content) {
|
|
1997
2093
|
return crypto.createHash("sha256").update(content).digest("hex");
|
|
@@ -2194,14 +2290,14 @@ function diffSnapshots(before, after) {
|
|
|
2194
2290
|
return changes;
|
|
2195
2291
|
}
|
|
2196
2292
|
function contractLockPath(haiveDir, name) {
|
|
2197
|
-
return
|
|
2293
|
+
return path12.join(haiveDir, "contracts", `${name}.lock`);
|
|
2198
2294
|
}
|
|
2199
2295
|
async function snapshotContract(projectRoot, haiveDir, contract) {
|
|
2200
|
-
const filePath =
|
|
2201
|
-
if (!
|
|
2296
|
+
const filePath = path12.resolve(projectRoot, contract.path);
|
|
2297
|
+
if (!existsSync10(filePath)) {
|
|
2202
2298
|
throw new Error(`Contract file not found: ${filePath}`);
|
|
2203
2299
|
}
|
|
2204
|
-
const content = await
|
|
2300
|
+
const content = await readFile10(filePath, "utf8");
|
|
2205
2301
|
const parsed = parseByFormat(content, contract.format, filePath);
|
|
2206
2302
|
const snapshot = {
|
|
2207
2303
|
name: contract.name,
|
|
@@ -2211,23 +2307,23 @@ async function snapshotContract(projectRoot, haiveDir, contract) {
|
|
|
2211
2307
|
hash: sha256(content),
|
|
2212
2308
|
...parsed
|
|
2213
2309
|
};
|
|
2214
|
-
const contractsDir =
|
|
2215
|
-
await
|
|
2216
|
-
await
|
|
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");
|
|
2217
2313
|
return snapshot;
|
|
2218
2314
|
}
|
|
2219
2315
|
async function diffContract(projectRoot, haiveDir, contract) {
|
|
2220
|
-
const filePath =
|
|
2221
|
-
if (!
|
|
2316
|
+
const filePath = path12.resolve(projectRoot, contract.path);
|
|
2317
|
+
if (!existsSync10(filePath)) {
|
|
2222
2318
|
return { contract: contract.name, file: contract.path, changes: [], unchanged: true };
|
|
2223
2319
|
}
|
|
2224
2320
|
const lockPath = contractLockPath(haiveDir, contract.name);
|
|
2225
|
-
if (!
|
|
2321
|
+
if (!existsSync10(lockPath)) {
|
|
2226
2322
|
await snapshotContract(projectRoot, haiveDir, contract);
|
|
2227
2323
|
return { contract: contract.name, file: contract.path, changes: [], unchanged: true };
|
|
2228
2324
|
}
|
|
2229
|
-
const content = await
|
|
2230
|
-
const beforeSnapshot = JSON.parse(await
|
|
2325
|
+
const content = await readFile10(filePath, "utf8");
|
|
2326
|
+
const beforeSnapshot = JSON.parse(await readFile10(lockPath, "utf8"));
|
|
2231
2327
|
const afterParsed = parseByFormat(content, contract.format, filePath);
|
|
2232
2328
|
const afterSnapshot = {
|
|
2233
2329
|
...beforeSnapshot,
|
|
@@ -2237,7 +2333,7 @@ async function diffContract(projectRoot, haiveDir, contract) {
|
|
|
2237
2333
|
};
|
|
2238
2334
|
const changes = diffSnapshots(beforeSnapshot, afterSnapshot);
|
|
2239
2335
|
if (changes.length > 0) {
|
|
2240
|
-
await
|
|
2336
|
+
await writeFile7(lockPath, JSON.stringify(afterSnapshot, null, 2) + "\n", "utf8");
|
|
2241
2337
|
}
|
|
2242
2338
|
return {
|
|
2243
2339
|
contract: contract.name,
|
|
@@ -2255,27 +2351,27 @@ async function watchContracts(projectRoot, haiveDir, contractFiles) {
|
|
|
2255
2351
|
}
|
|
2256
2352
|
|
|
2257
2353
|
// src/usage-log.ts
|
|
2258
|
-
import { appendFile, mkdir as
|
|
2259
|
-
import { existsSync as
|
|
2260
|
-
import
|
|
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";
|
|
2261
2357
|
var USAGE_LOG_FILE = "tool-usage.jsonl";
|
|
2262
2358
|
var USAGE_LOG_DIR = ".usage";
|
|
2263
2359
|
function usageLogPath(paths) {
|
|
2264
|
-
return
|
|
2360
|
+
return path13.join(paths.haiveDir, USAGE_LOG_DIR, USAGE_LOG_FILE);
|
|
2265
2361
|
}
|
|
2266
2362
|
async function appendUsageEvent(paths, event) {
|
|
2267
2363
|
try {
|
|
2268
2364
|
const file = usageLogPath(paths);
|
|
2269
|
-
const dir =
|
|
2270
|
-
if (!
|
|
2271
|
-
await
|
|
2365
|
+
const dir = path13.dirname(file);
|
|
2366
|
+
if (!existsSync11(dir)) await mkdir8(dir, { recursive: true });
|
|
2367
|
+
await appendFile2(file, JSON.stringify(event) + "\n", "utf8");
|
|
2272
2368
|
} catch {
|
|
2273
2369
|
}
|
|
2274
2370
|
}
|
|
2275
2371
|
async function readUsageEvents(paths) {
|
|
2276
2372
|
const file = usageLogPath(paths);
|
|
2277
|
-
if (!
|
|
2278
|
-
const raw = await
|
|
2373
|
+
if (!existsSync11(file)) return [];
|
|
2374
|
+
const raw = await readFile11(file, "utf8");
|
|
2279
2375
|
const out = [];
|
|
2280
2376
|
for (const line of raw.split("\n")) {
|
|
2281
2377
|
if (!line) continue;
|
|
@@ -2324,9 +2420,9 @@ function parseSince(input) {
|
|
|
2324
2420
|
}
|
|
2325
2421
|
async function usageLogSize(paths) {
|
|
2326
2422
|
const file = usageLogPath(paths);
|
|
2327
|
-
if (!
|
|
2423
|
+
if (!existsSync11(file)) return { exists: false, size_bytes: 0, lines: 0 };
|
|
2328
2424
|
const st = await stat2(file);
|
|
2329
|
-
const raw = await
|
|
2425
|
+
const raw = await readFile11(file, "utf8");
|
|
2330
2426
|
return { exists: true, size_bytes: st.size, lines: raw.split("\n").filter((l) => l).length };
|
|
2331
2427
|
}
|
|
2332
2428
|
|
|
@@ -2396,21 +2492,21 @@ function extractActionsBriefBody(markdown, maxChars = MAX_DEFAULT_CHARS) {
|
|
|
2396
2492
|
}
|
|
2397
2493
|
|
|
2398
2494
|
// src/resolve-project.ts
|
|
2399
|
-
import { existsSync as
|
|
2400
|
-
import
|
|
2495
|
+
import { existsSync as existsSync12 } from "fs";
|
|
2496
|
+
import path14 from "path";
|
|
2401
2497
|
var ROOT_MARKERS2 = [".ai", ".git", "package.json"];
|
|
2402
2498
|
function markersAtRoot(root) {
|
|
2403
2499
|
const found = [];
|
|
2404
2500
|
for (const m of ROOT_MARKERS2) {
|
|
2405
|
-
if (
|
|
2501
|
+
if (existsSync12(path14.join(root, m))) found.push(m);
|
|
2406
2502
|
}
|
|
2407
2503
|
return found;
|
|
2408
2504
|
}
|
|
2409
2505
|
function resolveProjectInfo(opts = {}) {
|
|
2410
2506
|
const env = opts.env ?? process.env;
|
|
2411
|
-
const cwd =
|
|
2507
|
+
const cwd = path14.resolve(opts.cwd ?? process.cwd());
|
|
2412
2508
|
const raw = env.HAIVE_PROJECT_ROOT;
|
|
2413
|
-
const explicit = raw !== void 0 && raw !== "" ?
|
|
2509
|
+
const explicit = raw !== void 0 && raw !== "" ? path14.resolve(raw) : null;
|
|
2414
2510
|
const resolvedRoot = explicit ?? findProjectRoot(cwd);
|
|
2415
2511
|
const paths = resolveHaivePaths(resolvedRoot);
|
|
2416
2512
|
return {
|
|
@@ -2418,8 +2514,8 @@ function resolveProjectInfo(opts = {}) {
|
|
|
2418
2514
|
resolved_root: resolvedRoot,
|
|
2419
2515
|
haive_project_root_env: explicit,
|
|
2420
2516
|
explicit_root: explicit != null,
|
|
2421
|
-
haive_dir_exists:
|
|
2422
|
-
memories_dir_exists:
|
|
2517
|
+
haive_dir_exists: existsSync12(paths.haiveDir),
|
|
2518
|
+
memories_dir_exists: existsSync12(paths.memoriesDir),
|
|
2423
2519
|
runtime_dir: paths.runtimeDir,
|
|
2424
2520
|
markers_found: markersAtRoot(resolvedRoot)
|
|
2425
2521
|
};
|
|
@@ -2674,16 +2770,16 @@ function findLexicalConflictPairs(memories, opts) {
|
|
|
2674
2770
|
}
|
|
2675
2771
|
|
|
2676
2772
|
// src/runtime-journal.ts
|
|
2677
|
-
import { mkdir as
|
|
2678
|
-
import { existsSync as
|
|
2679
|
-
import
|
|
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";
|
|
2680
2776
|
var RUNTIME_JOURNAL_FILENAME = "session-journal.ndjson";
|
|
2681
2777
|
function runtimeJournalPath(paths) {
|
|
2682
|
-
return
|
|
2778
|
+
return path15.join(paths.runtimeDir, RUNTIME_JOURNAL_FILENAME);
|
|
2683
2779
|
}
|
|
2684
2780
|
async function appendRuntimeJournalEntry(paths, entry) {
|
|
2685
2781
|
try {
|
|
2686
|
-
await
|
|
2782
|
+
await mkdir9(paths.runtimeDir, { recursive: true });
|
|
2687
2783
|
const line = {
|
|
2688
2784
|
ts: entry.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2689
2785
|
kind: entry.kind,
|
|
@@ -2691,7 +2787,7 @@ async function appendRuntimeJournalEntry(paths, entry) {
|
|
|
2691
2787
|
...entry.tool !== void 0 ? { tool: entry.tool } : {},
|
|
2692
2788
|
...entry.meta !== void 0 ? { meta: entry.meta } : {}
|
|
2693
2789
|
};
|
|
2694
|
-
await
|
|
2790
|
+
await appendFile3(
|
|
2695
2791
|
runtimeJournalPath(paths),
|
|
2696
2792
|
JSON.stringify(line) + "\n",
|
|
2697
2793
|
"utf8"
|
|
@@ -2701,9 +2797,9 @@ async function appendRuntimeJournalEntry(paths, entry) {
|
|
|
2701
2797
|
}
|
|
2702
2798
|
async function readRuntimeJournalTail(paths, limit) {
|
|
2703
2799
|
const file = runtimeJournalPath(paths);
|
|
2704
|
-
if (!
|
|
2800
|
+
if (!existsSync13(file) || limit <= 0) return [];
|
|
2705
2801
|
try {
|
|
2706
|
-
const raw = await
|
|
2802
|
+
const raw = await readFile12(file, "utf8");
|
|
2707
2803
|
const lines = raw.trim().split("\n").filter(Boolean);
|
|
2708
2804
|
const parsed = [];
|
|
2709
2805
|
for (const line of lines.slice(-limit)) {
|
|
@@ -2719,22 +2815,22 @@ async function readRuntimeJournalTail(paths, limit) {
|
|
|
2719
2815
|
}
|
|
2720
2816
|
|
|
2721
2817
|
// src/enforcement.ts
|
|
2722
|
-
import { mkdir as
|
|
2723
|
-
import { existsSync as
|
|
2724
|
-
import
|
|
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";
|
|
2725
2821
|
var BRIEFING_MARKER_TTL_MS = 12 * 60 * 60 * 1e3;
|
|
2726
2822
|
var SESSION_RECAP_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
2727
2823
|
function enforcementDir(paths) {
|
|
2728
|
-
return
|
|
2824
|
+
return path16.join(paths.runtimeDir, "enforcement");
|
|
2729
2825
|
}
|
|
2730
2826
|
function briefingMarkersDir(paths) {
|
|
2731
|
-
return
|
|
2827
|
+
return path16.join(enforcementDir(paths), "briefings");
|
|
2732
2828
|
}
|
|
2733
2829
|
function normalizeSessionId(sessionId) {
|
|
2734
2830
|
return (sessionId?.trim() || "default").replace(/[^a-zA-Z0-9_.-]+/g, "-").slice(0, 120);
|
|
2735
2831
|
}
|
|
2736
2832
|
function briefingMarkerPath(paths, sessionId) {
|
|
2737
|
-
return
|
|
2833
|
+
return path16.join(briefingMarkersDir(paths), `${normalizeSessionId(sessionId)}.json`);
|
|
2738
2834
|
}
|
|
2739
2835
|
async function writeBriefingMarker(paths, input) {
|
|
2740
2836
|
const marker = {
|
|
@@ -2746,8 +2842,8 @@ async function writeBriefingMarker(paths, input) {
|
|
|
2746
2842
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2747
2843
|
root: paths.root
|
|
2748
2844
|
};
|
|
2749
|
-
await
|
|
2750
|
-
await
|
|
2845
|
+
await mkdir10(briefingMarkersDir(paths), { recursive: true });
|
|
2846
|
+
await writeFile8(
|
|
2751
2847
|
briefingMarkerPath(paths, marker.session_id),
|
|
2752
2848
|
JSON.stringify(marker, null, 2) + "\n",
|
|
2753
2849
|
"utf8"
|
|
@@ -2758,18 +2854,18 @@ async function hasRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKER
|
|
|
2758
2854
|
const now = Date.now();
|
|
2759
2855
|
const candidates = [];
|
|
2760
2856
|
const exact = briefingMarkerPath(paths, sessionId);
|
|
2761
|
-
if (
|
|
2857
|
+
if (existsSync14(exact)) candidates.push(exact);
|
|
2762
2858
|
try {
|
|
2763
2859
|
const dir = briefingMarkersDir(paths);
|
|
2764
2860
|
const files = await readdir4(dir);
|
|
2765
2861
|
for (const file of files) {
|
|
2766
|
-
if (file.endsWith(".json")) candidates.push(
|
|
2862
|
+
if (file.endsWith(".json")) candidates.push(path16.join(dir, file));
|
|
2767
2863
|
}
|
|
2768
2864
|
} catch {
|
|
2769
2865
|
}
|
|
2770
2866
|
for (const file of new Set(candidates)) {
|
|
2771
2867
|
try {
|
|
2772
|
-
const marker = JSON.parse(await
|
|
2868
|
+
const marker = JSON.parse(await readFile13(file, "utf8"));
|
|
2773
2869
|
const created = Date.parse(marker.created_at);
|
|
2774
2870
|
if (Number.isFinite(created) && now - created <= ttlMs) return true;
|
|
2775
2871
|
} catch {
|
|
@@ -2781,12 +2877,12 @@ async function readRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKE
|
|
|
2781
2877
|
const now = Date.now();
|
|
2782
2878
|
const candidates = [];
|
|
2783
2879
|
const exact = briefingMarkerPath(paths, sessionId);
|
|
2784
|
-
if (
|
|
2880
|
+
if (existsSync14(exact)) candidates.push(exact);
|
|
2785
2881
|
try {
|
|
2786
2882
|
const dir = briefingMarkersDir(paths);
|
|
2787
2883
|
const files = await readdir4(dir);
|
|
2788
2884
|
for (const file of files) {
|
|
2789
|
-
if (file.endsWith(".json")) candidates.push(
|
|
2885
|
+
if (file.endsWith(".json")) candidates.push(path16.join(dir, file));
|
|
2790
2886
|
}
|
|
2791
2887
|
} catch {
|
|
2792
2888
|
}
|
|
@@ -2794,7 +2890,7 @@ async function readRecentBriefingMarker(paths, sessionId, ttlMs = BRIEFING_MARKE
|
|
|
2794
2890
|
let freshestTs = 0;
|
|
2795
2891
|
for (const file of new Set(candidates)) {
|
|
2796
2892
|
try {
|
|
2797
|
-
const marker = JSON.parse(await
|
|
2893
|
+
const marker = JSON.parse(await readFile13(file, "utf8"));
|
|
2798
2894
|
const created = Date.parse(marker.created_at);
|
|
2799
2895
|
if (!Number.isFinite(created) || now - created > ttlMs) continue;
|
|
2800
2896
|
if (created > freshestTs) {
|
|
@@ -2847,10 +2943,10 @@ function isRetiredMemory(fm, body = "", now = /* @__PURE__ */ new Date()) {
|
|
|
2847
2943
|
function normalizeProjectPath(value) {
|
|
2848
2944
|
return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/^[ab]\//, "").replace(/\/+$/g, "");
|
|
2849
2945
|
}
|
|
2850
|
-
function sensorAppliesToPath(sensor, anchorPaths,
|
|
2946
|
+
function sensorAppliesToPath(sensor, anchorPaths, path17) {
|
|
2851
2947
|
const scopes = sensor.paths.length > 0 ? sensor.paths : anchorPaths;
|
|
2852
2948
|
if (scopes.length === 0) return true;
|
|
2853
|
-
const target = normalizeProjectPath(
|
|
2949
|
+
const target = normalizeProjectPath(path17);
|
|
2854
2950
|
return scopes.some((rawScope) => {
|
|
2855
2951
|
const scope = normalizeProjectPath(rawScope);
|
|
2856
2952
|
if (!scope) return false;
|
|
@@ -3124,8 +3220,8 @@ function normalizeFindingSeverity(raw) {
|
|
|
3124
3220
|
return "info";
|
|
3125
3221
|
}
|
|
3126
3222
|
}
|
|
3127
|
-
function findingKey(tool, ruleId,
|
|
3128
|
-
return `${tool}:${ruleId}:${
|
|
3223
|
+
function findingKey(tool, ruleId, path17) {
|
|
3224
|
+
return `${tool}:${ruleId}:${path17}`;
|
|
3129
3225
|
}
|
|
3130
3226
|
function coerceJson(input) {
|
|
3131
3227
|
if (typeof input === "string") {
|
|
@@ -3159,8 +3255,8 @@ function parseSarif(input) {
|
|
|
3159
3255
|
const physical = asRecord(location.physicalLocation);
|
|
3160
3256
|
const artifact = asRecord(physical.artifactLocation);
|
|
3161
3257
|
const region = asRecord(physical.region);
|
|
3162
|
-
const
|
|
3163
|
-
if (!
|
|
3258
|
+
const path17 = typeof artifact.uri === "string" ? normalizeUri(artifact.uri) : "";
|
|
3259
|
+
if (!path17) continue;
|
|
3164
3260
|
const line = typeof region.startLine === "number" ? region.startLine : void 0;
|
|
3165
3261
|
const snippet = typeof asRecord(region.snippet).text === "string" ? asRecord(region.snippet).text.trim() : void 0;
|
|
3166
3262
|
findings.push({
|
|
@@ -3168,10 +3264,10 @@ function parseSarif(input) {
|
|
|
3168
3264
|
ruleId,
|
|
3169
3265
|
message: message.trim(),
|
|
3170
3266
|
severity,
|
|
3171
|
-
path:
|
|
3267
|
+
path: path17,
|
|
3172
3268
|
...line !== void 0 ? { line } : {},
|
|
3173
3269
|
...snippet ? { snippet } : {},
|
|
3174
|
-
key: findingKey(tool, ruleId,
|
|
3270
|
+
key: findingKey(tool, ruleId, path17)
|
|
3175
3271
|
});
|
|
3176
3272
|
}
|
|
3177
3273
|
}
|
|
@@ -3190,17 +3286,17 @@ function parseSonar(input) {
|
|
|
3190
3286
|
(typeof issue.severity === "string" ? issue.severity : void 0) ?? impactSeverity
|
|
3191
3287
|
);
|
|
3192
3288
|
const component = typeof issue.component === "string" ? issue.component : "";
|
|
3193
|
-
const
|
|
3194
|
-
if (!
|
|
3289
|
+
const path17 = componentToPath(component);
|
|
3290
|
+
if (!path17) continue;
|
|
3195
3291
|
const line = typeof issue.line === "number" ? issue.line : void 0;
|
|
3196
3292
|
findings.push({
|
|
3197
3293
|
tool: "sonar",
|
|
3198
3294
|
ruleId,
|
|
3199
3295
|
message,
|
|
3200
3296
|
severity,
|
|
3201
|
-
path:
|
|
3297
|
+
path: path17,
|
|
3202
3298
|
...line !== void 0 ? { line } : {},
|
|
3203
|
-
key: findingKey("sonar", ruleId,
|
|
3299
|
+
key: findingKey("sonar", ruleId, path17)
|
|
3204
3300
|
});
|
|
3205
3301
|
}
|
|
3206
3302
|
return findings;
|
|
@@ -3278,7 +3374,7 @@ function filterNewDrafts(drafts, existingTopics) {
|
|
|
3278
3374
|
}
|
|
3279
3375
|
|
|
3280
3376
|
// src/dashboard.ts
|
|
3281
|
-
var
|
|
3377
|
+
var MS_PER_DAY4 = 24 * 60 * 60 * 1e3;
|
|
3282
3378
|
function isAnchorless(fm) {
|
|
3283
3379
|
if (!["decision", "gotcha", "architecture"].includes(fm.type)) return false;
|
|
3284
3380
|
if (fm.status !== "validated") return false;
|
|
@@ -3372,7 +3468,7 @@ function buildDashboard(memories, usage, options = {}) {
|
|
|
3372
3468
|
if (isDecaying(memUsage, fm.created_at)) decaying += 1;
|
|
3373
3469
|
if (impact.tier === "dormant") {
|
|
3374
3470
|
const anchor = memUsage.last_read_at ?? fm.created_at;
|
|
3375
|
-
const ageDays = Math.floor((now.getTime() - new Date(anchor).getTime()) /
|
|
3471
|
+
const ageDays = Math.floor((now.getTime() - new Date(anchor).getTime()) / MS_PER_DAY4);
|
|
3376
3472
|
dormantRows.push({ id: fm.id, last_read_at: memUsage.last_read_at, age_days: ageDays });
|
|
3377
3473
|
}
|
|
3378
3474
|
}
|
|
@@ -3411,7 +3507,12 @@ function buildDashboard(memories, usage, options = {}) {
|
|
|
3411
3507
|
prevention: {
|
|
3412
3508
|
total_events: preventionEvents,
|
|
3413
3509
|
memories_with_catches: preventionRows.length,
|
|
3414
|
-
top: preventionRows.sort((a, b) => b.prevented_count - a.prevented_count).slice(0, top)
|
|
3510
|
+
top: preventionRows.sort((a, b) => b.prevented_count - a.prevented_count).slice(0, top),
|
|
3511
|
+
trend: computePreventionTrend(options.preventionEvents ?? [], now),
|
|
3512
|
+
recurrence: {
|
|
3513
|
+
...computeRecurrence(options.preventionEvents ?? []),
|
|
3514
|
+
top: computeRecurrence(options.preventionEvents ?? []).top.slice(0, top)
|
|
3515
|
+
}
|
|
3415
3516
|
},
|
|
3416
3517
|
corpus: {
|
|
3417
3518
|
memory_files: inventory.total,
|
|
@@ -3446,6 +3547,7 @@ export {
|
|
|
3446
3547
|
MemoryTypeSchema,
|
|
3447
3548
|
PREVENTION_DEBOUNCE_MS,
|
|
3448
3549
|
PROJECT_CONTEXT_FILE,
|
|
3550
|
+
PROJECT_CONTEXT_THROTTLE_MS,
|
|
3449
3551
|
RUNTIME_JOURNAL_FILENAME,
|
|
3450
3552
|
SESSION_RECAP_TTL_MS,
|
|
3451
3553
|
STACK_PACK_TAG,
|
|
@@ -3459,6 +3561,7 @@ export {
|
|
|
3459
3561
|
aggregateUsage,
|
|
3460
3562
|
allocateBudget,
|
|
3461
3563
|
antiPatternGateParams,
|
|
3564
|
+
appendPreventionEvent,
|
|
3462
3565
|
appendRuntimeJournalEntry,
|
|
3463
3566
|
appendUsageEvent,
|
|
3464
3567
|
briefingMarkerPath,
|
|
@@ -3475,6 +3578,8 @@ export {
|
|
|
3475
3578
|
compareImpact,
|
|
3476
3579
|
compileRegexSensor,
|
|
3477
3580
|
computeImpact,
|
|
3581
|
+
computePreventionTrend,
|
|
3582
|
+
computeRecurrence,
|
|
3478
3583
|
configPath,
|
|
3479
3584
|
contractLockPath,
|
|
3480
3585
|
deriveConfidence,
|
|
@@ -3499,6 +3604,7 @@ export {
|
|
|
3499
3604
|
getUsage,
|
|
3500
3605
|
globToRegExp,
|
|
3501
3606
|
hasRecentBriefingMarker,
|
|
3607
|
+
hashProjectContext,
|
|
3502
3608
|
inferModulesFromPaths,
|
|
3503
3609
|
isAutoPromoteEligible,
|
|
3504
3610
|
isDecaying,
|
|
@@ -3518,6 +3624,7 @@ export {
|
|
|
3518
3624
|
loadConfigSync,
|
|
3519
3625
|
loadMemoriesFromDir,
|
|
3520
3626
|
loadMemory,
|
|
3627
|
+
loadPreventionEvents,
|
|
3521
3628
|
loadUsageIndex,
|
|
3522
3629
|
memoryFilePath,
|
|
3523
3630
|
memoryMatchesAnchorPaths,
|
|
@@ -3532,6 +3639,8 @@ export {
|
|
|
3532
3639
|
parseSonar,
|
|
3533
3640
|
pathsOverlap,
|
|
3534
3641
|
pickSnippetNeedle,
|
|
3642
|
+
preventionLogPath,
|
|
3643
|
+
projectContextRecentlyEmitted,
|
|
3535
3644
|
pullCrossRepoSources,
|
|
3536
3645
|
queryCodeMap,
|
|
3537
3646
|
rankMemoriesLexical,
|
|
@@ -3540,6 +3649,7 @@ export {
|
|
|
3540
3649
|
readUsageEvents,
|
|
3541
3650
|
recordApplied,
|
|
3542
3651
|
recordPrevention,
|
|
3652
|
+
recordProjectContextEmission,
|
|
3543
3653
|
recordRejection,
|
|
3544
3654
|
relPathFrom,
|
|
3545
3655
|
resolveBriefingBudget,
|