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