@hivelore/cli 0.33.0 → 0.35.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/{chunk-XDB7EKL4.js → chunk-ZR7UPTRR.js} +5 -5
- package/dist/chunk-ZR7UPTRR.js.map +1 -0
- package/dist/index.js +1038 -560
- package/dist/index.js.map +1 -1
- package/dist/{server-LZKYO5O5.js → server-2FUYM6KI.js} +2 -2
- package/package.json +4 -4
- package/dist/chunk-XDB7EKL4.js.map +0 -1
- /package/dist/{server-LZKYO5O5.js.map → server-2FUYM6KI.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
preCommitCheck,
|
|
11
11
|
readPresumedCorrectTargets,
|
|
12
12
|
runHaiveMcpStdio
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-ZR7UPTRR.js";
|
|
14
14
|
import {
|
|
15
15
|
registerMemoryPending
|
|
16
16
|
} from "./chunk-OYJKHD22.js";
|
|
@@ -208,7 +208,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
|
|
|
208
208
|
if (!f) continue;
|
|
209
209
|
counts.set(f, (counts.get(f) ?? 0) + 1);
|
|
210
210
|
}
|
|
211
|
-
let entries = [...counts.entries()].map(([
|
|
211
|
+
let entries = [...counts.entries()].map(([path47, changes]) => ({ path: path47, changes }));
|
|
212
212
|
const lowerPaths = filePaths.map((p) => p.toLowerCase());
|
|
213
213
|
if (lowerPaths.length > 0) {
|
|
214
214
|
entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
|
|
@@ -275,7 +275,7 @@ async function lintMemoriesAsync(root, options = {}) {
|
|
|
275
275
|
const loaded = await loadMemoriesFromDir2(paths.memoriesDir);
|
|
276
276
|
const usage = await loadUsageIndex(paths);
|
|
277
277
|
const codeMap = await loadCodeMap(paths);
|
|
278
|
-
const
|
|
278
|
+
const trackedFiles2 = gitTrackedFiles(root);
|
|
279
279
|
const ANCHOR_TYPES = /* @__PURE__ */ new Set(["decision", "architecture", "gotcha"]);
|
|
280
280
|
const actionableWords = /\b(always|never|prefer|use|run|avoid|because|instead|why|rationale|do not|must|should|require|required|requires|fix|fail|failed|fails|prevent|prevents|allow|allows|lets|ensure|ensures|catch|catches)\b/i;
|
|
281
281
|
for (const { filePath, memory: memory2 } of loaded) {
|
|
@@ -322,9 +322,9 @@ async function lintMemoriesAsync(root, options = {}) {
|
|
|
322
322
|
message: "Reads like generic best practice a capable model already follows. Hivelore's value is UNGUESSABLE team knowledge \u2014 add the concrete, arbitrary specifics (exact names, values, formats, magic numbers) or consider removing it to keep briefings high-signal."
|
|
323
323
|
});
|
|
324
324
|
}
|
|
325
|
-
const
|
|
326
|
-
const
|
|
327
|
-
if (ANCHOR_TYPES.has(fm.type) && fm.anchor.paths.length === 0 && fm.status === "validated" && !
|
|
325
|
+
const isStackSeed = fm.tags.includes("stack-pack");
|
|
326
|
+
const suggestedAnchors = isStackSeed ? { paths: [], symbols: [] } : suggestAnchors(root, { filePath, memory: memory2 }, codeMap, trackedFiles2);
|
|
327
|
+
if (ANCHOR_TYPES.has(fm.type) && fm.anchor.paths.length === 0 && fm.status === "validated" && !isStackSeed) {
|
|
328
328
|
out.push({
|
|
329
329
|
file: filePath,
|
|
330
330
|
id: fm.id,
|
|
@@ -435,14 +435,14 @@ function titleFromId(id) {
|
|
|
435
435
|
const withoutDate = id.replace(/^\d{4}-\d{2}-\d{2}-/, "");
|
|
436
436
|
return withoutDate.split("-").filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
|
|
437
437
|
}
|
|
438
|
-
function suggestAnchors(root, loaded, codeMap,
|
|
438
|
+
function suggestAnchors(root, loaded, codeMap, trackedFiles2) {
|
|
439
439
|
const body = loaded.memory.body;
|
|
440
440
|
const paths = /* @__PURE__ */ new Set();
|
|
441
441
|
const symbols = /* @__PURE__ */ new Set();
|
|
442
442
|
for (const match of body.matchAll(/`([^`\n]+\.[A-Za-z0-9]+)`|(?:^|\s)([A-Za-z0-9_./-]+\.[A-Za-z0-9]+)/gm)) {
|
|
443
443
|
const candidate = (match[1] ?? match[2] ?? "").replace(/^\.?\//, "");
|
|
444
444
|
if (!candidate || candidate.startsWith("http")) continue;
|
|
445
|
-
if (existsSync(path2.join(root, candidate)) && isSafeAnchorPath(candidate,
|
|
445
|
+
if (existsSync(path2.join(root, candidate)) && isSafeAnchorPath(candidate, trackedFiles2)) {
|
|
446
446
|
paths.add(candidate);
|
|
447
447
|
}
|
|
448
448
|
}
|
|
@@ -452,7 +452,7 @@ function suggestAnchors(root, loaded, codeMap, trackedFiles) {
|
|
|
452
452
|
for (const exp of entry.exports) {
|
|
453
453
|
if (!exp.name || exp.name.length < 4) continue;
|
|
454
454
|
if (lowered.includes(exp.name.toLowerCase())) {
|
|
455
|
-
if (isSafeAnchorPath(file,
|
|
455
|
+
if (isSafeAnchorPath(file, trackedFiles2)) {
|
|
456
456
|
paths.add(file);
|
|
457
457
|
symbols.add(exp.name);
|
|
458
458
|
}
|
|
@@ -477,12 +477,12 @@ function gitTrackedFiles(root) {
|
|
|
477
477
|
const files = result.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
478
478
|
return new Set(files);
|
|
479
479
|
}
|
|
480
|
-
function isSafeAnchorPath(file,
|
|
480
|
+
function isSafeAnchorPath(file, trackedFiles2) {
|
|
481
481
|
const normalized = file.replace(/\\/g, "/").replace(/^\.?\//, "");
|
|
482
482
|
if (normalized.startsWith(".ai/.cache/") || normalized.startsWith(".ai/.runtime/")) return false;
|
|
483
483
|
if (normalized.includes("/node_modules/") || normalized.startsWith("node_modules/")) return false;
|
|
484
484
|
if (normalized.includes("/dist/") || normalized.startsWith("dist/")) return false;
|
|
485
|
-
if (
|
|
485
|
+
if (trackedFiles2 && !trackedFiles2.has(normalized)) return false;
|
|
486
486
|
return true;
|
|
487
487
|
}
|
|
488
488
|
function nearDuplicatePairs(loaded) {
|
|
@@ -728,20 +728,7 @@ async function projectContextVersionStatus(root, paths) {
|
|
|
728
728
|
async function refreshCodeMap(root, paths, force) {
|
|
729
729
|
const existing = await loadCodeMap2(paths);
|
|
730
730
|
if (existing && !force) return false;
|
|
731
|
-
const map = await buildCodeMap(root, {
|
|
732
|
-
includeUntracked: true,
|
|
733
|
-
excludeDirs: [
|
|
734
|
-
"node_modules",
|
|
735
|
-
"dist",
|
|
736
|
-
"build",
|
|
737
|
-
"out",
|
|
738
|
-
".git",
|
|
739
|
-
".next",
|
|
740
|
-
".turbo",
|
|
741
|
-
".vitest-cache",
|
|
742
|
-
"coverage"
|
|
743
|
-
]
|
|
744
|
-
});
|
|
731
|
+
const map = await buildCodeMap(root, { includeUntracked: true });
|
|
745
732
|
if (existing && existing.root === map.root && JSON.stringify(existing.files) === JSON.stringify(map.files)) {
|
|
746
733
|
return false;
|
|
747
734
|
}
|
|
@@ -1355,6 +1342,7 @@ import path6 from "path";
|
|
|
1355
1342
|
import "commander";
|
|
1356
1343
|
import {
|
|
1357
1344
|
buildCodeMap as buildCodeMap2,
|
|
1345
|
+
CODE_MAP_DEFAULT_EXCLUDE,
|
|
1358
1346
|
codeMapPath,
|
|
1359
1347
|
findProjectRoot as findProjectRoot4,
|
|
1360
1348
|
loadCodeMap as loadCodeMap4,
|
|
@@ -1383,18 +1371,7 @@ function registerIndexCode(program2) {
|
|
|
1383
1371
|
ui.info(`Indexing source files in ${root}\u2026`);
|
|
1384
1372
|
const map = await buildCodeMap2(root, {
|
|
1385
1373
|
includeUntracked: true,
|
|
1386
|
-
excludeDirs: [
|
|
1387
|
-
"node_modules",
|
|
1388
|
-
"dist",
|
|
1389
|
-
"build",
|
|
1390
|
-
"out",
|
|
1391
|
-
".git",
|
|
1392
|
-
".next",
|
|
1393
|
-
".turbo",
|
|
1394
|
-
".vitest-cache",
|
|
1395
|
-
"coverage",
|
|
1396
|
-
...extraExcludes
|
|
1397
|
-
]
|
|
1374
|
+
excludeDirs: [...CODE_MAP_DEFAULT_EXCLUDE, ...extraExcludes]
|
|
1398
1375
|
});
|
|
1399
1376
|
await saveCodeMap2(paths, map);
|
|
1400
1377
|
const fileCount = Object.keys(map.files).length;
|
|
@@ -1504,14 +1481,14 @@ function isCodeMapStale(root, generatedAt, files) {
|
|
|
1504
1481
|
// src/commands/init.ts
|
|
1505
1482
|
import { execFile as execFile2 } from "child_process";
|
|
1506
1483
|
import { mkdir as mkdir6, readFile as readFile6, readdir as readdir2, writeFile as writeFile7 } from "fs/promises";
|
|
1507
|
-
import { existsSync as
|
|
1508
|
-
import
|
|
1484
|
+
import { existsSync as existsSync12 } from "fs";
|
|
1485
|
+
import path13 from "path";
|
|
1509
1486
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
1510
1487
|
import { promisify as promisify2 } from "util";
|
|
1511
1488
|
import "commander";
|
|
1512
1489
|
import {
|
|
1513
1490
|
AUTOPILOT_DEFAULTS as AUTOPILOT_DEFAULTS2,
|
|
1514
|
-
BRIDGE_TARGETS,
|
|
1491
|
+
BRIDGE_TARGETS as BRIDGE_TARGETS2,
|
|
1515
1492
|
buildCodeMap as buildCodeMap3,
|
|
1516
1493
|
buildFrontmatter as buildFrontmatter2,
|
|
1517
1494
|
detectStacksFromManifests,
|
|
@@ -1523,20 +1500,93 @@ import {
|
|
|
1523
1500
|
serializeMemory as serializeMemory3
|
|
1524
1501
|
} from "@hivelore/core";
|
|
1525
1502
|
|
|
1503
|
+
// src/utils/doc-files.ts
|
|
1504
|
+
import { readdirSync } from "fs";
|
|
1505
|
+
function findDocFile(root, stem) {
|
|
1506
|
+
let entries;
|
|
1507
|
+
try {
|
|
1508
|
+
entries = readdirSync(root);
|
|
1509
|
+
} catch {
|
|
1510
|
+
return void 0;
|
|
1511
|
+
}
|
|
1512
|
+
const re = new RegExp(`^${stem}(\\.(md|markdown|txt|rst))?$`, "i");
|
|
1513
|
+
return entries.find((e) => re.test(e));
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
// src/utils/bridge-detect.ts
|
|
1517
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2 } from "fs";
|
|
1518
|
+
import { homedir } from "os";
|
|
1519
|
+
import path7 from "path";
|
|
1520
|
+
import { BRIDGE_TARGET_PATH, BRIDGE_TARGETS } from "@hivelore/core";
|
|
1521
|
+
var VSCODE_EXTENSION_SIGNALS = {
|
|
1522
|
+
copilot: ["github.copilot"],
|
|
1523
|
+
cline: ["saoudrizwan.claude-dev"],
|
|
1524
|
+
roo: ["rooveterinaryinc.roo"],
|
|
1525
|
+
cody: ["sourcegraph.cody"],
|
|
1526
|
+
continue: ["continue.continue"]
|
|
1527
|
+
};
|
|
1528
|
+
var HOME_SIGNALS = {
|
|
1529
|
+
claude: [".claude", ".claude.json"],
|
|
1530
|
+
cursor: [".cursor"],
|
|
1531
|
+
windsurf: [".codeium/windsurf", ".windsurf"],
|
|
1532
|
+
gemini: [".gemini"],
|
|
1533
|
+
continue: [".continue"],
|
|
1534
|
+
aider: [".aider.conf.yml", ".aider"],
|
|
1535
|
+
zed: [".config/zed", "Library/Application Support/Zed"],
|
|
1536
|
+
copilot: [".config/github-copilot"]
|
|
1537
|
+
};
|
|
1538
|
+
var ENV_SIGNALS = {
|
|
1539
|
+
claude: ["CLAUDECODE", "CLAUDE_CODE_ENTRYPOINT"],
|
|
1540
|
+
cursor: ["CURSOR_AGENT"],
|
|
1541
|
+
gemini: ["GEMINI_CLI"],
|
|
1542
|
+
aider: ["AIDER_MODEL"]
|
|
1543
|
+
};
|
|
1544
|
+
function vscodeExtensionIds(home) {
|
|
1545
|
+
const ids = [];
|
|
1546
|
+
for (const dir of [".vscode/extensions", ".vscode-server/extensions", ".vscode-oss/extensions"]) {
|
|
1547
|
+
try {
|
|
1548
|
+
ids.push(...readdirSync2(path7.join(home, dir)).map((e) => e.toLowerCase()));
|
|
1549
|
+
} catch {
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
return ids;
|
|
1553
|
+
}
|
|
1554
|
+
function detectBridgeTargets(root, env = process.env, home = homedir()) {
|
|
1555
|
+
const reasons = { agents: "universal" };
|
|
1556
|
+
const extensions = vscodeExtensionIds(home);
|
|
1557
|
+
for (const target of BRIDGE_TARGETS) {
|
|
1558
|
+
if (target === "agents") continue;
|
|
1559
|
+
if (existsSync6(path7.join(root, BRIDGE_TARGET_PATH[target]))) {
|
|
1560
|
+
reasons[target] = "repo file";
|
|
1561
|
+
continue;
|
|
1562
|
+
}
|
|
1563
|
+
const homeHit = (HOME_SIGNALS[target] ?? []).some((p) => existsSync6(path7.join(home, p)));
|
|
1564
|
+
const envHit = (ENV_SIGNALS[target] ?? []).some((v) => env[v]);
|
|
1565
|
+
const extHit = (VSCODE_EXTENSION_SIGNALS[target] ?? []).some(
|
|
1566
|
+
(prefix) => extensions.some((id) => id.startsWith(prefix))
|
|
1567
|
+
);
|
|
1568
|
+
if (homeHit || envHit || extHit) reasons[target] = "installed";
|
|
1569
|
+
}
|
|
1570
|
+
return {
|
|
1571
|
+
targets: BRIDGE_TARGETS.filter((t) => t in reasons),
|
|
1572
|
+
reasons
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1526
1576
|
// src/utils/bridge-files.ts
|
|
1527
1577
|
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
1528
|
-
import { existsSync as
|
|
1529
|
-
import
|
|
1578
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1579
|
+
import path8 from "path";
|
|
1530
1580
|
import {
|
|
1531
1581
|
BRIDGE_MARKERS,
|
|
1532
|
-
BRIDGE_TARGET_PATH,
|
|
1582
|
+
BRIDGE_TARGET_PATH as BRIDGE_TARGET_PATH2,
|
|
1533
1583
|
generateBridges,
|
|
1534
1584
|
isRetiredMemory,
|
|
1535
1585
|
loadMemoriesFromDir as loadMemoriesFromDir5
|
|
1536
1586
|
} from "@hivelore/core";
|
|
1537
1587
|
async function writeBridgeFiles(root, paths, opts) {
|
|
1538
1588
|
const result = { created: [], updated: [], unchanged: [], skipped: [], warnings: [] };
|
|
1539
|
-
if (!
|
|
1589
|
+
if (!existsSync7(paths.memoriesDir)) return result;
|
|
1540
1590
|
const allLoaded = await loadMemoriesFromDir5(paths.memoriesDir);
|
|
1541
1591
|
const memories = allLoaded.map((l) => l.memory).filter((m) => !isRetiredMemory(m.frontmatter, m.body));
|
|
1542
1592
|
const sensors = [];
|
|
@@ -1554,8 +1604,8 @@ async function writeBridgeFiles(root, paths, opts) {
|
|
|
1554
1604
|
const maxMemories = Math.max(1, opts.maxMemories ?? 8);
|
|
1555
1605
|
const outputs = generateBridges(memories, sensors, { maxMemories, targets: opts.targets });
|
|
1556
1606
|
for (const output of outputs) {
|
|
1557
|
-
const targetFile =
|
|
1558
|
-
const fileExists =
|
|
1607
|
+
const targetFile = path8.join(root, output.path);
|
|
1608
|
+
const fileExists = existsSync7(targetFile);
|
|
1559
1609
|
if (opts.onlyExisting && !fileExists) continue;
|
|
1560
1610
|
if (opts.dryRun) {
|
|
1561
1611
|
if (!fileExists) {
|
|
@@ -1574,7 +1624,7 @@ async function writeBridgeFiles(root, paths, opts) {
|
|
|
1574
1624
|
}
|
|
1575
1625
|
continue;
|
|
1576
1626
|
}
|
|
1577
|
-
await mkdir2(
|
|
1627
|
+
await mkdir2(path8.dirname(targetFile), { recursive: true });
|
|
1578
1628
|
if (!fileExists) {
|
|
1579
1629
|
await writeFile3(targetFile, output.content, "utf8");
|
|
1580
1630
|
result.created.push(output.path);
|
|
@@ -1597,10 +1647,10 @@ async function writeBridgeFiles(root, paths, opts) {
|
|
|
1597
1647
|
return result;
|
|
1598
1648
|
}
|
|
1599
1649
|
async function getBridgeFileStatuses(root, paths, opts) {
|
|
1600
|
-
if (!
|
|
1650
|
+
if (!existsSync7(paths.memoriesDir)) {
|
|
1601
1651
|
return opts.targets.map((target) => ({
|
|
1602
1652
|
target,
|
|
1603
|
-
path:
|
|
1653
|
+
path: BRIDGE_TARGET_PATH2[target],
|
|
1604
1654
|
exists: false,
|
|
1605
1655
|
state: "missing",
|
|
1606
1656
|
wouldChange: false,
|
|
@@ -1627,8 +1677,8 @@ async function getBridgeFileStatuses(root, paths, opts) {
|
|
|
1627
1677
|
});
|
|
1628
1678
|
const statuses = [];
|
|
1629
1679
|
for (const output of outputs) {
|
|
1630
|
-
const targetFile =
|
|
1631
|
-
const exists =
|
|
1680
|
+
const targetFile = path8.join(root, output.path);
|
|
1681
|
+
const exists = existsSync7(targetFile);
|
|
1632
1682
|
if (!exists) {
|
|
1633
1683
|
statuses.push({
|
|
1634
1684
|
target: output.target,
|
|
@@ -1784,18 +1834,18 @@ function legacyManagedRange(text) {
|
|
|
1784
1834
|
|
|
1785
1835
|
// src/commands/agent.ts
|
|
1786
1836
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1787
|
-
import { existsSync as
|
|
1837
|
+
import { existsSync as existsSync9 } from "fs";
|
|
1788
1838
|
import { mkdir as mkdir4, writeFile as writeFile5 } from "fs/promises";
|
|
1789
1839
|
import os2 from "os";
|
|
1790
|
-
import
|
|
1840
|
+
import path10 from "path";
|
|
1791
1841
|
import { createInterface } from "readline/promises";
|
|
1792
1842
|
import "commander";
|
|
1793
1843
|
import { findProjectRoot as findProjectRoot5, resolveHaivePaths as resolveHaivePaths5 } from "@hivelore/core";
|
|
1794
1844
|
|
|
1795
1845
|
// src/commands/init-mcp-setup.ts
|
|
1796
1846
|
import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir3 } from "fs/promises";
|
|
1797
|
-
import { existsSync as
|
|
1798
|
-
import
|
|
1847
|
+
import { existsSync as existsSync8 } from "fs";
|
|
1848
|
+
import path9 from "path";
|
|
1799
1849
|
import os from "os";
|
|
1800
1850
|
var HOME = os.homedir();
|
|
1801
1851
|
var HAIVE_MCP_ENTRY = {
|
|
@@ -1810,14 +1860,14 @@ function projectMcpEntry(root) {
|
|
|
1810
1860
|
};
|
|
1811
1861
|
}
|
|
1812
1862
|
function cursorMcpPath() {
|
|
1813
|
-
return
|
|
1863
|
+
return path9.join(HOME, ".cursor", "mcp.json");
|
|
1814
1864
|
}
|
|
1815
1865
|
async function configureCursor() {
|
|
1816
1866
|
const mcpPath = cursorMcpPath();
|
|
1817
|
-
const cursorDir =
|
|
1818
|
-
if (!
|
|
1867
|
+
const cursorDir = path9.join(HOME, ".cursor");
|
|
1868
|
+
if (!existsSync8(cursorDir)) return { client: "Cursor", status: "not_installed" };
|
|
1819
1869
|
let config = {};
|
|
1820
|
-
if (
|
|
1870
|
+
if (existsSync8(mcpPath)) {
|
|
1821
1871
|
try {
|
|
1822
1872
|
config = JSON.parse(await readFile4(mcpPath, "utf8"));
|
|
1823
1873
|
} catch {
|
|
@@ -1832,16 +1882,16 @@ async function configureCursor() {
|
|
|
1832
1882
|
}
|
|
1833
1883
|
function vscodeMcpPath() {
|
|
1834
1884
|
const candidates = [
|
|
1835
|
-
|
|
1885
|
+
path9.join(HOME, ".config", "Code", "User", "mcp.json"),
|
|
1836
1886
|
// Linux
|
|
1837
|
-
|
|
1887
|
+
path9.join(HOME, "Library", "Application Support", "Code", "User", "mcp.json"),
|
|
1838
1888
|
// macOS
|
|
1839
|
-
|
|
1889
|
+
path9.join(HOME, "AppData", "Roaming", "Code", "User", "mcp.json"),
|
|
1840
1890
|
// Windows
|
|
1841
|
-
|
|
1891
|
+
path9.join(HOME, ".config", "Code - Insiders", "User", "mcp.json")
|
|
1842
1892
|
];
|
|
1843
1893
|
for (const c of candidates) {
|
|
1844
|
-
if (
|
|
1894
|
+
if (existsSync8(path9.dirname(c))) return c;
|
|
1845
1895
|
}
|
|
1846
1896
|
return null;
|
|
1847
1897
|
}
|
|
@@ -1849,7 +1899,7 @@ async function configureVSCode() {
|
|
|
1849
1899
|
const mcpPath = vscodeMcpPath();
|
|
1850
1900
|
if (!mcpPath) return { client: "VS Code", status: "not_installed" };
|
|
1851
1901
|
let config = {};
|
|
1852
|
-
if (
|
|
1902
|
+
if (existsSync8(mcpPath)) {
|
|
1853
1903
|
try {
|
|
1854
1904
|
config = JSON.parse(await readFile4(mcpPath, "utf8"));
|
|
1855
1905
|
} catch {
|
|
@@ -1858,24 +1908,24 @@ async function configureVSCode() {
|
|
|
1858
1908
|
config.servers ??= {};
|
|
1859
1909
|
if (config.servers["hivelore"] || config.servers["haive"]) return { client: "VS Code", status: "already_configured" };
|
|
1860
1910
|
config.servers["hivelore"] = { ...HAIVE_MCP_ENTRY, type: "stdio" };
|
|
1861
|
-
await mkdir3(
|
|
1911
|
+
await mkdir3(path9.dirname(mcpPath), { recursive: true });
|
|
1862
1912
|
await writeFile4(mcpPath, JSON.stringify(config, null, 2), "utf8");
|
|
1863
1913
|
return { client: "VS Code", status: "configured", path: mcpPath };
|
|
1864
1914
|
}
|
|
1865
1915
|
function claudeConfigPath() {
|
|
1866
|
-
const p =
|
|
1867
|
-
if (
|
|
1868
|
-
const p2 =
|
|
1869
|
-
if (
|
|
1916
|
+
const p = path9.join(HOME, ".claude.json");
|
|
1917
|
+
if (existsSync8(p)) return p;
|
|
1918
|
+
const p2 = path9.join(HOME, ".config", "claude", "claude.json");
|
|
1919
|
+
if (existsSync8(path9.dirname(p2))) return p2;
|
|
1870
1920
|
return null;
|
|
1871
1921
|
}
|
|
1872
1922
|
async function configureClaude() {
|
|
1873
|
-
const cfgPath = claudeConfigPath() ??
|
|
1874
|
-
if (!
|
|
1923
|
+
const cfgPath = claudeConfigPath() ?? path9.join(HOME, ".claude.json");
|
|
1924
|
+
if (!existsSync8(cfgPath) && !existsSync8(path9.join(HOME, ".claude"))) {
|
|
1875
1925
|
return { client: "Claude Code", status: "not_installed" };
|
|
1876
1926
|
}
|
|
1877
1927
|
let config = {};
|
|
1878
|
-
if (
|
|
1928
|
+
if (existsSync8(cfgPath)) {
|
|
1879
1929
|
try {
|
|
1880
1930
|
config = JSON.parse(await readFile4(cfgPath, "utf8"));
|
|
1881
1931
|
} catch {
|
|
@@ -1889,11 +1939,11 @@ async function configureClaude() {
|
|
|
1889
1939
|
}
|
|
1890
1940
|
function windsurfMcpPath() {
|
|
1891
1941
|
const candidates = [
|
|
1892
|
-
|
|
1893
|
-
|
|
1942
|
+
path9.join(HOME, ".codeium", "windsurf", "mcp_config.json"),
|
|
1943
|
+
path9.join(HOME, ".windsurf", "mcp.json")
|
|
1894
1944
|
];
|
|
1895
1945
|
for (const c of candidates) {
|
|
1896
|
-
if (
|
|
1946
|
+
if (existsSync8(path9.dirname(c))) return c;
|
|
1897
1947
|
}
|
|
1898
1948
|
return null;
|
|
1899
1949
|
}
|
|
@@ -1901,7 +1951,7 @@ async function configureWindsurf() {
|
|
|
1901
1951
|
const mcpPath = windsurfMcpPath();
|
|
1902
1952
|
if (!mcpPath) return { client: "Windsurf", status: "not_installed" };
|
|
1903
1953
|
let config = {};
|
|
1904
|
-
if (
|
|
1954
|
+
if (existsSync8(mcpPath)) {
|
|
1905
1955
|
try {
|
|
1906
1956
|
config = JSON.parse(await readFile4(mcpPath, "utf8"));
|
|
1907
1957
|
} catch {
|
|
@@ -1910,7 +1960,7 @@ async function configureWindsurf() {
|
|
|
1910
1960
|
config.mcpServers ??= {};
|
|
1911
1961
|
if (config.mcpServers["hivelore"] || config.mcpServers["haive"]) return { client: "Windsurf", status: "already_configured" };
|
|
1912
1962
|
config.mcpServers["hivelore"] = HAIVE_MCP_ENTRY;
|
|
1913
|
-
await mkdir3(
|
|
1963
|
+
await mkdir3(path9.dirname(mcpPath), { recursive: true });
|
|
1914
1964
|
await writeFile4(mcpPath, JSON.stringify(config, null, 2), "utf8");
|
|
1915
1965
|
return { client: "Windsurf", status: "configured", path: mcpPath };
|
|
1916
1966
|
}
|
|
@@ -1931,9 +1981,9 @@ async function configureProjectMcpClients(root) {
|
|
|
1931
1981
|
const entry = projectMcpEntry(root);
|
|
1932
1982
|
const results = [];
|
|
1933
1983
|
try {
|
|
1934
|
-
const cursorPath =
|
|
1984
|
+
const cursorPath = path9.join(root, ".cursor", "mcp.json");
|
|
1935
1985
|
let config = {};
|
|
1936
|
-
if (
|
|
1986
|
+
if (existsSync8(cursorPath)) {
|
|
1937
1987
|
try {
|
|
1938
1988
|
config = JSON.parse(await readFile4(cursorPath, "utf8"));
|
|
1939
1989
|
} catch {
|
|
@@ -1942,16 +1992,16 @@ async function configureProjectMcpClients(root) {
|
|
|
1942
1992
|
config.mcpServers ??= {};
|
|
1943
1993
|
delete config.mcpServers["haive"];
|
|
1944
1994
|
config.mcpServers["hivelore"] = entry;
|
|
1945
|
-
await mkdir3(
|
|
1995
|
+
await mkdir3(path9.dirname(cursorPath), { recursive: true });
|
|
1946
1996
|
await writeFile4(cursorPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
1947
1997
|
results.push({ client: "Cursor (project)", status: "configured", path: cursorPath });
|
|
1948
1998
|
} catch (err) {
|
|
1949
1999
|
results.push({ client: "Cursor (project)", status: "error", error: String(err) });
|
|
1950
2000
|
}
|
|
1951
2001
|
try {
|
|
1952
|
-
const vscodePath =
|
|
2002
|
+
const vscodePath = path9.join(root, ".vscode", "mcp.json");
|
|
1953
2003
|
let config = {};
|
|
1954
|
-
if (
|
|
2004
|
+
if (existsSync8(vscodePath)) {
|
|
1955
2005
|
try {
|
|
1956
2006
|
config = JSON.parse(await readFile4(vscodePath, "utf8"));
|
|
1957
2007
|
} catch {
|
|
@@ -1960,16 +2010,16 @@ async function configureProjectMcpClients(root) {
|
|
|
1960
2010
|
config.servers ??= {};
|
|
1961
2011
|
delete config.servers["haive"];
|
|
1962
2012
|
config.servers["hivelore"] = { ...entry, type: "stdio" };
|
|
1963
|
-
await mkdir3(
|
|
2013
|
+
await mkdir3(path9.dirname(vscodePath), { recursive: true });
|
|
1964
2014
|
await writeFile4(vscodePath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
1965
2015
|
results.push({ client: "VS Code (workspace)", status: "configured", path: vscodePath });
|
|
1966
2016
|
} catch (err) {
|
|
1967
2017
|
results.push({ client: "VS Code (workspace)", status: "error", error: String(err) });
|
|
1968
2018
|
}
|
|
1969
2019
|
try {
|
|
1970
|
-
const mcpPath =
|
|
2020
|
+
const mcpPath = path9.join(root, ".mcp.json");
|
|
1971
2021
|
let config = {};
|
|
1972
|
-
if (
|
|
2022
|
+
if (existsSync8(mcpPath)) {
|
|
1973
2023
|
try {
|
|
1974
2024
|
config = JSON.parse(await readFile4(mcpPath, "utf8"));
|
|
1975
2025
|
} catch {
|
|
@@ -2044,9 +2094,9 @@ async function detectAgentMode(dir) {
|
|
|
2044
2094
|
const root = findProjectRoot5(dir);
|
|
2045
2095
|
const paths = resolveHaivePaths5(root);
|
|
2046
2096
|
const projectMcp = [
|
|
2047
|
-
{ client: "Claude Code", path:
|
|
2048
|
-
{ client: "Cursor", path:
|
|
2049
|
-
{ client: "VS Code", path:
|
|
2097
|
+
{ client: "Claude Code", path: path10.join(root, ".mcp.json"), present: existsSync9(path10.join(root, ".mcp.json")) },
|
|
2098
|
+
{ client: "Cursor", path: path10.join(root, ".cursor", "mcp.json"), present: existsSync9(path10.join(root, ".cursor", "mcp.json")) },
|
|
2099
|
+
{ client: "VS Code", path: path10.join(root, ".vscode", "mcp.json"), present: existsSync9(path10.join(root, ".vscode", "mcp.json")) }
|
|
2050
2100
|
];
|
|
2051
2101
|
const installedAgents = [
|
|
2052
2102
|
{ agent: "Codex", command: "codex", installed: commandExists("codex"), mcp_configured: codexMcpConfigured() },
|
|
@@ -2061,7 +2111,7 @@ async function detectAgentMode(dir) {
|
|
|
2061
2111
|
const recommendedCommand = recommendedMode === "mcp" ? "Restart your AI client, then call get_briefing before editing." : recommendedMode === "wrapped" && wrapperAgent ? `hivelore run -- ${wrapperAgent.command}` : 'hivelore briefing --task "..." --files "..."';
|
|
2062
2112
|
return {
|
|
2063
2113
|
root,
|
|
2064
|
-
initialized:
|
|
2114
|
+
initialized: existsSync9(paths.haiveDir),
|
|
2065
2115
|
project_mcp: projectMcp,
|
|
2066
2116
|
installed_agents: installedAgents,
|
|
2067
2117
|
recommended_mode: recommendedMode,
|
|
@@ -2069,9 +2119,9 @@ async function detectAgentMode(dir) {
|
|
|
2069
2119
|
};
|
|
2070
2120
|
}
|
|
2071
2121
|
async function writeAgentModeRecord(paths, detection, skippedReason) {
|
|
2072
|
-
const dir =
|
|
2122
|
+
const dir = path10.join(paths.runtimeDir, "enforcement");
|
|
2073
2123
|
await mkdir4(dir, { recursive: true });
|
|
2074
|
-
const file =
|
|
2124
|
+
const file = path10.join(dir, "agent-mode.json");
|
|
2075
2125
|
const record = {
|
|
2076
2126
|
selected_mode: detection.recommended_mode,
|
|
2077
2127
|
recommended_command: detection.recommended_command,
|
|
@@ -2112,7 +2162,7 @@ async function configureCodexIfAvailable(root) {
|
|
|
2112
2162
|
"mcp",
|
|
2113
2163
|
"--stdio"
|
|
2114
2164
|
], { encoding: "utf8" });
|
|
2115
|
-
if (result.status === 0) return { client: "Codex", status: "configured", path:
|
|
2165
|
+
if (result.status === 0) return { client: "Codex", status: "configured", path: path10.join(os2.homedir(), ".codex", "config.toml") };
|
|
2116
2166
|
return { client: "Codex", status: "error", error: result.stderr || result.stdout || "codex mcp add failed" };
|
|
2117
2167
|
}
|
|
2118
2168
|
function commandExists(command) {
|
|
@@ -2139,7 +2189,7 @@ function printDetection(detection, json) {
|
|
|
2139
2189
|
console.log(ui.dim(` root: ${detection.root}`));
|
|
2140
2190
|
console.log(`${detection.initialized ? ui.green("\u2713") : ui.red("\u2717")} project initialized`);
|
|
2141
2191
|
for (const cfg of detection.project_mcp) {
|
|
2142
|
-
console.log(`${cfg.present ? ui.green("\u2713") : ui.yellow("\u2022")} ${cfg.client} project MCP ${ui.dim(
|
|
2192
|
+
console.log(`${cfg.present ? ui.green("\u2713") : ui.yellow("\u2022")} ${cfg.client} project MCP ${ui.dim(path10.relative(detection.root, cfg.path))}`);
|
|
2143
2193
|
}
|
|
2144
2194
|
for (const agent of detection.installed_agents) {
|
|
2145
2195
|
const marker = agent.installed ? ui.green("\u2713") : ui.dim("\u2022");
|
|
@@ -2168,8 +2218,8 @@ function printSetupResult(result) {
|
|
|
2168
2218
|
|
|
2169
2219
|
// src/commands/init-bootstrap.ts
|
|
2170
2220
|
import { readdir, readFile as readFile5 } from "fs/promises";
|
|
2171
|
-
import { existsSync as
|
|
2172
|
-
import
|
|
2221
|
+
import { existsSync as existsSync10, readdirSync as readdirSync3 } from "fs";
|
|
2222
|
+
import path11 from "path";
|
|
2173
2223
|
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
2174
2224
|
"node_modules",
|
|
2175
2225
|
"dist",
|
|
@@ -2255,12 +2305,12 @@ function detectKeyDeps(allDeps) {
|
|
|
2255
2305
|
return KEY_DEPS.filter((d) => allDeps[d] !== void 0);
|
|
2256
2306
|
}
|
|
2257
2307
|
function detectLanguage(root) {
|
|
2258
|
-
if (
|
|
2259
|
-
if (
|
|
2260
|
-
if (
|
|
2261
|
-
if (
|
|
2262
|
-
if (
|
|
2263
|
-
if (
|
|
2308
|
+
if (existsSync10(path11.join(root, "tsconfig.json"))) return "TypeScript";
|
|
2309
|
+
if (existsSync10(path11.join(root, "pyproject.toml")) || existsSync10(path11.join(root, "setup.py"))) return "Python";
|
|
2310
|
+
if (existsSync10(path11.join(root, "go.mod"))) return "Go";
|
|
2311
|
+
if (existsSync10(path11.join(root, "pom.xml")) || existsSync10(path11.join(root, "build.gradle"))) return "Java/Kotlin";
|
|
2312
|
+
if (existsSync10(path11.join(root, "Cargo.toml"))) return "Rust";
|
|
2313
|
+
if (existsSync10(path11.join(root, "package.json"))) {
|
|
2264
2314
|
return hasSourceWithExt(root, [".ts", ".tsx", ".mts", ".cts"]) ? "TypeScript" : "JavaScript";
|
|
2265
2315
|
}
|
|
2266
2316
|
return "Unknown";
|
|
@@ -2270,7 +2320,7 @@ function hasSourceWithExt(root, exts) {
|
|
|
2270
2320
|
const scanDir = (dir, depth) => {
|
|
2271
2321
|
let entries;
|
|
2272
2322
|
try {
|
|
2273
|
-
entries =
|
|
2323
|
+
entries = readdirSync3(dir, { withFileTypes: true });
|
|
2274
2324
|
} catch {
|
|
2275
2325
|
return false;
|
|
2276
2326
|
}
|
|
@@ -2280,7 +2330,7 @@ function hasSourceWithExt(root, exts) {
|
|
|
2280
2330
|
if (depth <= 0) return false;
|
|
2281
2331
|
for (const entry of entries) {
|
|
2282
2332
|
if (entry.isDirectory() && !IGNORE_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
|
|
2283
|
-
if (scanDir(
|
|
2333
|
+
if (scanDir(path11.join(dir, entry.name), depth - 1)) return true;
|
|
2284
2334
|
}
|
|
2285
2335
|
}
|
|
2286
2336
|
return false;
|
|
@@ -2301,7 +2351,7 @@ function detectProjectType(frameworks, scripts, isMonorepo) {
|
|
|
2301
2351
|
if (frameworks.includes("Express") || frameworks.includes("Fastify") || frameworks.includes("Hono")) return "Backend API";
|
|
2302
2352
|
if (frameworks.includes("React") || frameworks.includes("Vue") || frameworks.includes("Svelte")) return "Frontend SPA";
|
|
2303
2353
|
if (scripts["build"] && !scripts["dev"]) return "CLI tool / library";
|
|
2304
|
-
if (
|
|
2354
|
+
if (existsSync10("pom.xml")) return "Java backend";
|
|
2305
2355
|
return "Application";
|
|
2306
2356
|
}
|
|
2307
2357
|
async function scanDirs(root, maxDepth = 2) {
|
|
@@ -2317,9 +2367,9 @@ async function scanDirs(root, maxDepth = 2) {
|
|
|
2317
2367
|
for (const entry of entries) {
|
|
2318
2368
|
if (!entry.isDirectory()) continue;
|
|
2319
2369
|
if (IGNORE_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
2320
|
-
const rel =
|
|
2370
|
+
const rel = path11.relative(root, path11.join(dir, entry.name));
|
|
2321
2371
|
results.push(rel);
|
|
2322
|
-
await walk(
|
|
2372
|
+
await walk(path11.join(dir, entry.name), depth + 1);
|
|
2323
2373
|
}
|
|
2324
2374
|
}
|
|
2325
2375
|
await walk(root, 0);
|
|
@@ -2436,8 +2486,8 @@ function readmeExcerpt(readme) {
|
|
|
2436
2486
|
}
|
|
2437
2487
|
async function generateBootstrapContext(root) {
|
|
2438
2488
|
let pkg = {};
|
|
2439
|
-
const pkgPath =
|
|
2440
|
-
if (
|
|
2489
|
+
const pkgPath = path11.join(root, "package.json");
|
|
2490
|
+
if (existsSync10(pkgPath)) {
|
|
2441
2491
|
try {
|
|
2442
2492
|
pkg = JSON.parse(await readFile5(pkgPath, "utf8"));
|
|
2443
2493
|
} catch {
|
|
@@ -2449,18 +2499,15 @@ async function generateBootstrapContext(root) {
|
|
|
2449
2499
|
const language = detectLanguage(root);
|
|
2450
2500
|
const isMonorepo = pkg.workspaces !== void 0 && (Array.isArray(pkg.workspaces) ? pkg.workspaces.length > 0 : true);
|
|
2451
2501
|
const projectType = detectProjectType(frameworks, pkg.scripts ?? {}, isMonorepo);
|
|
2452
|
-
const projectName = pkg.name ??
|
|
2502
|
+
const projectName = pkg.name ?? path11.basename(root);
|
|
2453
2503
|
const projectDesc = pkg.description ?? "";
|
|
2454
2504
|
let readmeSummary = "";
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
break;
|
|
2462
|
-
} catch {
|
|
2463
|
-
}
|
|
2505
|
+
const readmeName = findDocFile(root, "readme");
|
|
2506
|
+
if (readmeName) {
|
|
2507
|
+
try {
|
|
2508
|
+
const content = await readFile5(path11.join(root, readmeName), "utf8");
|
|
2509
|
+
readmeSummary = readmeExcerpt(content);
|
|
2510
|
+
} catch {
|
|
2464
2511
|
}
|
|
2465
2512
|
}
|
|
2466
2513
|
const dirs = await scanDirs(root, 2);
|
|
@@ -2513,7 +2560,7 @@ async function generateBootstrapContext(root) {
|
|
|
2513
2560
|
"",
|
|
2514
2561
|
`## Gotchas`,
|
|
2515
2562
|
`TODO \u2014 known traps, surprising behavior, things newcomers stub their toes on.`,
|
|
2516
|
-
`(
|
|
2563
|
+
`(Seed these with \`hivelore memory import --from ${findDocFile(root, "changelog") ?? "CHANGELOG.md"} --changelog\` or \`hivelore memory import --from ${findDocFile(root, "readme") ?? "README.md"}\` via your AI client.)`,
|
|
2517
2564
|
""
|
|
2518
2565
|
];
|
|
2519
2566
|
return lines.join("\n");
|
|
@@ -2521,8 +2568,8 @@ async function generateBootstrapContext(root) {
|
|
|
2521
2568
|
|
|
2522
2569
|
// src/commands/init-stack-packs.ts
|
|
2523
2570
|
import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
|
|
2524
|
-
import { existsSync as
|
|
2525
|
-
import
|
|
2571
|
+
import { existsSync as existsSync11 } from "fs";
|
|
2572
|
+
import path12 from "path";
|
|
2526
2573
|
import {
|
|
2527
2574
|
buildFrontmatter,
|
|
2528
2575
|
loadMemoriesFromDir as loadMemoriesFromDir6,
|
|
@@ -3626,7 +3673,7 @@ async function seedStackPack(haivePaths, stack) {
|
|
|
3626
3673
|
const existingTopics = /* @__PURE__ */ new Set();
|
|
3627
3674
|
const existingSignatures = /* @__PURE__ */ new Set();
|
|
3628
3675
|
const handWrittenSlugs = [];
|
|
3629
|
-
if (
|
|
3676
|
+
if (existsSync11(haivePaths.memoriesDir)) {
|
|
3630
3677
|
for (const { memory: memory2 } of await loadMemoriesFromDir6(haivePaths.memoriesDir)) {
|
|
3631
3678
|
if (memory2.frontmatter.topic) existingTopics.add(memory2.frontmatter.topic);
|
|
3632
3679
|
existingSignatures.add(memory2.frontmatter.id.replace(DATE_PREFIX, ""));
|
|
@@ -3671,7 +3718,7 @@ async function seedStackPack(haivePaths, stack) {
|
|
|
3671
3718
|
...sensor ? { sensor } : {}
|
|
3672
3719
|
});
|
|
3673
3720
|
const filePath = memoryFilePath(haivePaths, "team", fm.id);
|
|
3674
|
-
if (
|
|
3721
|
+
if (existsSync11(filePath)) continue;
|
|
3675
3722
|
const ruleSlug = combinedSlug.startsWith(`${stack}-`) ? combinedSlug.slice(stack.length + 1) : combinedSlug;
|
|
3676
3723
|
const titleCase = (s) => s.split("-").filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
3677
3724
|
const heading = `${titleCase(stack)}: ${titleCase(ruleSlug)}`;
|
|
@@ -3681,7 +3728,7 @@ ${mem.body}`;
|
|
|
3681
3728
|
const content = serializeMemory2({ frontmatter: fm, body: `${titledBody}
|
|
3682
3729
|
|
|
3683
3730
|
${SEED_FOOTER(stack)}` });
|
|
3684
|
-
await mkdir5(
|
|
3731
|
+
await mkdir5(path12.dirname(filePath), { recursive: true });
|
|
3685
3732
|
await writeFile6(filePath, content, "utf8");
|
|
3686
3733
|
existingTopics.add(topic);
|
|
3687
3734
|
existingSignatures.add(signature);
|
|
@@ -3708,7 +3755,7 @@ ${SEED_FOOTER(stack)}` });
|
|
|
3708
3755
|
|
|
3709
3756
|
// src/commands/init.ts
|
|
3710
3757
|
var execFileAsync = promisify2(execFile2);
|
|
3711
|
-
var HAIVE_GITHUB_ACTION_REF = `v${"0.
|
|
3758
|
+
var HAIVE_GITHUB_ACTION_REF = `v${"0.35.0"}`;
|
|
3712
3759
|
var PROJECT_CONTEXT_TEMPLATE = `# Project context
|
|
3713
3760
|
|
|
3714
3761
|
> Generated by \`hivelore init\`. Run \`hivelore init --bootstrap\` to auto-fill from your codebase,
|
|
@@ -3901,9 +3948,9 @@ function registerInit(program2) {
|
|
|
3901
3948
|
"Initialize a Hivelore project \u2014 autopilot mode ON by default (zero human intervention).\n Auto-bootstraps project-context.md from local files and seeds detected stack packs.\n Seeds draft memories from git revert/hotfix history (--seed, on by default).\n Add --manual to control memory approval and session recaps yourself.\n Add --no-bootstrap and --stack none to disable the auto-features."
|
|
3902
3949
|
).option("-d, --dir <dir>", "project root", process.cwd()).option("--no-bridges", "do not generate any native agent bridge files").option(
|
|
3903
3950
|
"--bridge-targets <list>",
|
|
3904
|
-
`which agent bridges to generate: '
|
|
3905
|
-
Available: ${
|
|
3906
|
-
"
|
|
3951
|
+
`which agent bridges to generate: 'auto' (default \u2014 clients detected on this machine/repo, plus AGENTS.md) | 'all' | comma-list.
|
|
3952
|
+
Available: ${BRIDGE_TARGETS2.join(", ")}. Each carries top memories + block sensors.`,
|
|
3953
|
+
"auto"
|
|
3907
3954
|
).option("--with-ci", "write a GitHub Actions workflow (.github/workflows/haive-sync.yml) \u2014 included automatically in autopilot mode").option(
|
|
3908
3955
|
"--manual",
|
|
3909
3956
|
"opt out of autopilot: memories require manual approval, no auto-session recap, no auto-context"
|
|
@@ -3936,7 +3983,7 @@ function registerInit(program2) {
|
|
|
3936
3983
|
"approve user-level AI client configuration prompts during agent setup",
|
|
3937
3984
|
false
|
|
3938
3985
|
).option("--json", "emit a machine-readable summary on stdout (human logs go to stderr)", false).action(async (opts) => {
|
|
3939
|
-
const root =
|
|
3986
|
+
const root = path13.resolve(opts.dir);
|
|
3940
3987
|
const paths = resolveHaivePaths6(root);
|
|
3941
3988
|
const autopilot = opts.manual !== true;
|
|
3942
3989
|
const json = opts.json === true;
|
|
@@ -3953,9 +4000,10 @@ function registerInit(program2) {
|
|
|
3953
4000
|
gitCommitsScanned: 0,
|
|
3954
4001
|
gitRevertsFound: 0,
|
|
3955
4002
|
gitRecurring: 0,
|
|
3956
|
-
bridgesWritten: 0
|
|
4003
|
+
bridgesWritten: 0,
|
|
4004
|
+
bridgeTargets: []
|
|
3957
4005
|
};
|
|
3958
|
-
if (
|
|
4006
|
+
if (existsSync12(paths.haiveDir)) {
|
|
3959
4007
|
ui.warn(`.ai/ already exists at ${paths.haiveDir} \u2014 leaving existing files in place.`);
|
|
3960
4008
|
}
|
|
3961
4009
|
await mkdir6(paths.personalDir, { recursive: true });
|
|
@@ -3963,8 +4011,8 @@ function registerInit(program2) {
|
|
|
3963
4011
|
await mkdir6(paths.moduleDir, { recursive: true });
|
|
3964
4012
|
await mkdir6(paths.modulesContextDir, { recursive: true });
|
|
3965
4013
|
await ensureAiRuntimeLayout(paths.runtimeDir);
|
|
3966
|
-
await ensureAiCacheLayout(
|
|
3967
|
-
if (!
|
|
4014
|
+
await ensureAiCacheLayout(path13.join(paths.haiveDir, ".cache"));
|
|
4015
|
+
if (!existsSync12(paths.projectContext)) {
|
|
3968
4016
|
if (wantBootstrap) {
|
|
3969
4017
|
ui.info("Bootstrapping project context from local files\u2026");
|
|
3970
4018
|
try {
|
|
@@ -3977,11 +4025,11 @@ function registerInit(program2) {
|
|
|
3977
4025
|
}
|
|
3978
4026
|
} else {
|
|
3979
4027
|
await writeFile7(paths.projectContext, PROJECT_CONTEXT_TEMPLATE, "utf8");
|
|
3980
|
-
ui.success(`Created ${
|
|
4028
|
+
ui.success(`Created ${path13.relative(root, paths.projectContext)}`);
|
|
3981
4029
|
}
|
|
3982
4030
|
}
|
|
3983
|
-
const configExists =
|
|
3984
|
-
|
|
4031
|
+
const configExists = existsSync12(
|
|
4032
|
+
path13.join(paths.haiveDir, "haive.config.json")
|
|
3985
4033
|
);
|
|
3986
4034
|
if (!configExists) {
|
|
3987
4035
|
await saveConfig2(paths, autopilot ? AUTOPILOT_DEFAULTS2 : { autopilot: false });
|
|
@@ -4031,11 +4079,14 @@ function registerInit(program2) {
|
|
|
4031
4079
|
}
|
|
4032
4080
|
}
|
|
4033
4081
|
if (opts.bridges) {
|
|
4034
|
-
const targets = resolveBridgeTargets(opts.bridgeTargets);
|
|
4082
|
+
const targets = resolveBridgeTargets(opts.bridgeTargets, root);
|
|
4035
4083
|
const res = await writeBridgeFiles(root, paths, { targets });
|
|
4036
|
-
|
|
4084
|
+
if (targets.includes("cursor")) {
|
|
4085
|
+
await writeCursorHaiveRule(root);
|
|
4086
|
+
}
|
|
4037
4087
|
const made = res.created.length + res.updated.length;
|
|
4038
4088
|
report.bridgesWritten = made;
|
|
4089
|
+
report.bridgeTargets = targets;
|
|
4039
4090
|
if (res.created.length > 0) {
|
|
4040
4091
|
ui.success(`Generated ${res.created.length} agent bridge(s): ${res.created.join(", ")}`);
|
|
4041
4092
|
}
|
|
@@ -4048,13 +4099,13 @@ function registerInit(program2) {
|
|
|
4048
4099
|
}
|
|
4049
4100
|
const wantCi = opts.withCi || autopilot;
|
|
4050
4101
|
if (wantCi) {
|
|
4051
|
-
const ciPath =
|
|
4052
|
-
if (
|
|
4102
|
+
const ciPath = path13.join(root, ".github", "workflows", "haive-sync.yml");
|
|
4103
|
+
if (existsSync12(ciPath)) {
|
|
4053
4104
|
ui.info("CI workflow already exists \u2014 skipped");
|
|
4054
4105
|
} else {
|
|
4055
|
-
await mkdir6(
|
|
4106
|
+
await mkdir6(path13.dirname(ciPath), { recursive: true });
|
|
4056
4107
|
await writeFile7(ciPath, CI_WORKFLOW, "utf8");
|
|
4057
|
-
ui.success(`Created ${
|
|
4108
|
+
ui.success(`Created ${path13.relative(root, ciPath)}`);
|
|
4058
4109
|
}
|
|
4059
4110
|
}
|
|
4060
4111
|
if (autopilot) {
|
|
@@ -4094,7 +4145,7 @@ function registerInit(program2) {
|
|
|
4094
4145
|
interactive: process.stdin.isTTY
|
|
4095
4146
|
});
|
|
4096
4147
|
for (const r of agentSetup.project_results) {
|
|
4097
|
-
if (r.status === "configured" && r.path) ui.success(`hivelore MCP project config written (${
|
|
4148
|
+
if (r.status === "configured" && r.path) ui.success(`hivelore MCP project config written (${path13.relative(root, r.path)})`);
|
|
4098
4149
|
else if (r.status === "error") ui.warn(`${r.client}: ${r.error}`);
|
|
4099
4150
|
}
|
|
4100
4151
|
for (const r of agentSetup.global_results) {
|
|
@@ -4160,10 +4211,18 @@ function registerInit(program2) {
|
|
|
4160
4211
|
console.log(ui.dim(" Review .ai/project-context.md and fill in the TODO sections."));
|
|
4161
4212
|
console.log(ui.dim(" Or invoke the MCP prompt `bootstrap_project` for a richer AI-generated version."));
|
|
4162
4213
|
}
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4214
|
+
const changelogFile = findDocFile(root, "changelog");
|
|
4215
|
+
const readmeFile = findDocFile(root, "readme");
|
|
4216
|
+
if (changelogFile || readmeFile) {
|
|
4217
|
+
console.log();
|
|
4218
|
+
console.log(ui.dim(" Seed more memories:"));
|
|
4219
|
+
if (changelogFile) {
|
|
4220
|
+
console.log(ui.dim(` hivelore memory import --from ${changelogFile} --changelog \u2014 extract breaking-change gotchas (no AI needed)`));
|
|
4221
|
+
}
|
|
4222
|
+
if (readmeFile) {
|
|
4223
|
+
console.log(ui.dim(` hivelore memory import --from ${readmeFile} \u2014 prepare an import prompt for your AI client`));
|
|
4224
|
+
}
|
|
4225
|
+
}
|
|
4167
4226
|
} else {
|
|
4168
4227
|
console.log(ui.bold("Next steps:"));
|
|
4169
4228
|
if (!wantBootstrap) {
|
|
@@ -4189,7 +4248,42 @@ async function resolveStacksToSeed(root, stackOpt) {
|
|
|
4189
4248
|
return stackOpt.split(",").map((s) => s.trim().toLowerCase()).filter(isValidStack);
|
|
4190
4249
|
}
|
|
4191
4250
|
async function collectNestedPackageDeps(root) {
|
|
4192
|
-
const SKIP = /* @__PURE__ */ new Set([
|
|
4251
|
+
const SKIP = /* @__PURE__ */ new Set([
|
|
4252
|
+
"node_modules",
|
|
4253
|
+
"dist",
|
|
4254
|
+
"build",
|
|
4255
|
+
"out",
|
|
4256
|
+
".git",
|
|
4257
|
+
".next",
|
|
4258
|
+
"coverage",
|
|
4259
|
+
"vendor",
|
|
4260
|
+
// Fixture/demo trees describe what the project TESTS AGAINST, not what it is built with.
|
|
4261
|
+
// Vite's playground/ pulls react+vue+tailwind+express into detection and seeds four
|
|
4262
|
+
// irrelevant stack packs on a build-tool repo.
|
|
4263
|
+
"playground",
|
|
4264
|
+
"playgrounds",
|
|
4265
|
+
"example",
|
|
4266
|
+
"examples",
|
|
4267
|
+
"fixtures",
|
|
4268
|
+
"__fixtures__",
|
|
4269
|
+
"e2e",
|
|
4270
|
+
"test",
|
|
4271
|
+
"tests",
|
|
4272
|
+
"demo",
|
|
4273
|
+
"demos",
|
|
4274
|
+
"template",
|
|
4275
|
+
"templates",
|
|
4276
|
+
"bench",
|
|
4277
|
+
"benchmarks",
|
|
4278
|
+
"samples",
|
|
4279
|
+
"sandbox",
|
|
4280
|
+
// Doc sites are built with their own framework (VitePress→vue, Docusaurus→react) — that is
|
|
4281
|
+
// the DOCS' stack, not the product's.
|
|
4282
|
+
"docs",
|
|
4283
|
+
"doc",
|
|
4284
|
+
"website"
|
|
4285
|
+
]);
|
|
4286
|
+
const SKIP_PREFIX = /^(template|example|fixture|demo|sample)s?-/;
|
|
4193
4287
|
const merged = {};
|
|
4194
4288
|
let found = false;
|
|
4195
4289
|
async function scan(dir, depth) {
|
|
@@ -4201,10 +4295,10 @@ async function collectNestedPackageDeps(root) {
|
|
|
4201
4295
|
return;
|
|
4202
4296
|
}
|
|
4203
4297
|
for (const entry of entries) {
|
|
4204
|
-
if (!entry.isDirectory() || entry.name.startsWith(".") || SKIP.has(entry.name)) continue;
|
|
4205
|
-
const sub =
|
|
4206
|
-
const pkgPath =
|
|
4207
|
-
if (
|
|
4298
|
+
if (!entry.isDirectory() || entry.name.startsWith(".") || SKIP.has(entry.name) || SKIP_PREFIX.test(entry.name)) continue;
|
|
4299
|
+
const sub = path13.join(dir, entry.name);
|
|
4300
|
+
const pkgPath = path13.join(sub, "package.json");
|
|
4301
|
+
if (existsSync12(pkgPath)) {
|
|
4208
4302
|
try {
|
|
4209
4303
|
const pkg = JSON.parse(await readFile6(pkgPath, "utf8"));
|
|
4210
4304
|
Object.assign(merged, pkg.dependencies ?? {}, pkg.devDependencies ?? {});
|
|
@@ -4223,8 +4317,8 @@ async function autoDetectStacksFromRoot(root) {
|
|
|
4223
4317
|
let requirementsTxt;
|
|
4224
4318
|
let goMod;
|
|
4225
4319
|
let pomXml;
|
|
4226
|
-
const pkgPath =
|
|
4227
|
-
if (
|
|
4320
|
+
const pkgPath = path13.join(root, "package.json");
|
|
4321
|
+
if (existsSync12(pkgPath)) {
|
|
4228
4322
|
try {
|
|
4229
4323
|
const pkg = JSON.parse(await readFile6(pkgPath, "utf8"));
|
|
4230
4324
|
packageJsonDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
@@ -4234,8 +4328,8 @@ async function autoDetectStacksFromRoot(root) {
|
|
|
4234
4328
|
const nestedDeps = await collectNestedPackageDeps(root);
|
|
4235
4329
|
if (nestedDeps) packageJsonDeps = { ...packageJsonDeps ?? {}, ...nestedDeps };
|
|
4236
4330
|
for (const name of ["requirements.txt", "requirements/base.txt", "requirements/prod.txt"]) {
|
|
4237
|
-
const reqPath =
|
|
4238
|
-
if (
|
|
4331
|
+
const reqPath = path13.join(root, name);
|
|
4332
|
+
if (existsSync12(reqPath)) {
|
|
4239
4333
|
try {
|
|
4240
4334
|
requirementsTxt = await readFile6(reqPath, "utf8");
|
|
4241
4335
|
break;
|
|
@@ -4243,39 +4337,39 @@ async function autoDetectStacksFromRoot(root) {
|
|
|
4243
4337
|
}
|
|
4244
4338
|
}
|
|
4245
4339
|
}
|
|
4246
|
-
const goModPath =
|
|
4247
|
-
if (
|
|
4340
|
+
const goModPath = path13.join(root, "go.mod");
|
|
4341
|
+
if (existsSync12(goModPath)) {
|
|
4248
4342
|
try {
|
|
4249
4343
|
goMod = await readFile6(goModPath, "utf8");
|
|
4250
4344
|
} catch {
|
|
4251
4345
|
}
|
|
4252
4346
|
}
|
|
4253
|
-
const pomPath =
|
|
4254
|
-
if (
|
|
4347
|
+
const pomPath = path13.join(root, "pom.xml");
|
|
4348
|
+
if (existsSync12(pomPath)) {
|
|
4255
4349
|
try {
|
|
4256
4350
|
pomXml = await readFile6(pomPath, "utf8");
|
|
4257
4351
|
} catch {
|
|
4258
4352
|
}
|
|
4259
4353
|
}
|
|
4260
4354
|
let composerJson;
|
|
4261
|
-
const composerPath =
|
|
4262
|
-
if (
|
|
4355
|
+
const composerPath = path13.join(root, "composer.json");
|
|
4356
|
+
if (existsSync12(composerPath)) {
|
|
4263
4357
|
try {
|
|
4264
4358
|
composerJson = await readFile6(composerPath, "utf8");
|
|
4265
4359
|
} catch {
|
|
4266
4360
|
}
|
|
4267
4361
|
}
|
|
4268
4362
|
let gemfile;
|
|
4269
|
-
const gemfilePath =
|
|
4270
|
-
if (
|
|
4363
|
+
const gemfilePath = path13.join(root, "Gemfile");
|
|
4364
|
+
if (existsSync12(gemfilePath)) {
|
|
4271
4365
|
try {
|
|
4272
4366
|
gemfile = await readFile6(gemfilePath, "utf8");
|
|
4273
4367
|
} catch {
|
|
4274
4368
|
}
|
|
4275
4369
|
}
|
|
4276
|
-
const hasDockerfile =
|
|
4277
|
-
const hasTurboJson =
|
|
4278
|
-
const hasNxJson =
|
|
4370
|
+
const hasDockerfile = existsSync12(path13.join(root, "Dockerfile"));
|
|
4371
|
+
const hasTurboJson = existsSync12(path13.join(root, "turbo.json"));
|
|
4372
|
+
const hasNxJson = existsSync12(path13.join(root, "nx.json"));
|
|
4279
4373
|
let hasCsproj = false;
|
|
4280
4374
|
try {
|
|
4281
4375
|
const entries = await readdir2(root);
|
|
@@ -4327,8 +4421,8 @@ async function seedFromGitHistory(root, paths, limit) {
|
|
|
4327
4421
|
_Seeded from git ${p.kind} commit ${p.source_sha}. Review and validate (or delete) \u2014 not yet authoritative._
|
|
4328
4422
|
`;
|
|
4329
4423
|
const file = memoryFilePath2(paths, fm.scope, fm.id, fm.module);
|
|
4330
|
-
if (
|
|
4331
|
-
await mkdir6(
|
|
4424
|
+
if (existsSync12(file)) continue;
|
|
4425
|
+
await mkdir6(path13.dirname(file), { recursive: true });
|
|
4332
4426
|
await writeFile7(file, serializeMemory3({ frontmatter: fm, body }), "utf8");
|
|
4333
4427
|
written++;
|
|
4334
4428
|
}
|
|
@@ -4369,7 +4463,7 @@ function printInitReport(r) {
|
|
|
4369
4463
|
lines.push(` Total ready : ${r.totalMemories} lesson(s), ${r.totalSensors} sensor(s) \u2014 0 written by hand`);
|
|
4370
4464
|
}
|
|
4371
4465
|
if (r.bridgesWritten > 0) {
|
|
4372
|
-
lines.push(` Reach : ${r.bridgesWritten} agent bridge(s) generated (
|
|
4466
|
+
lines.push(` Reach : ${r.bridgesWritten} agent bridge(s) generated (${r.bridgeTargets.join(", ")})`);
|
|
4373
4467
|
}
|
|
4374
4468
|
if (lines.length === 0) return;
|
|
4375
4469
|
const width = Math.max(...lines.map((l) => l.length), 44);
|
|
@@ -4390,33 +4484,37 @@ function printInitReport(r) {
|
|
|
4390
4484
|
}
|
|
4391
4485
|
async function writeCursorHaiveRule(root) {
|
|
4392
4486
|
const relPath = ".cursor/rules/haive-mcp-required.mdc";
|
|
4393
|
-
const target =
|
|
4394
|
-
if (
|
|
4487
|
+
const target = path13.join(root, relPath);
|
|
4488
|
+
if (existsSync12(target)) {
|
|
4395
4489
|
ui.info(`Cursor rule ${relPath} already exists \u2014 skipped`);
|
|
4396
4490
|
return;
|
|
4397
4491
|
}
|
|
4398
|
-
await mkdir6(
|
|
4492
|
+
await mkdir6(path13.dirname(target), { recursive: true });
|
|
4399
4493
|
await writeFile7(target, CURSOR_HAIVE_RULE_MDC, "utf8");
|
|
4400
4494
|
ui.success(`Created Cursor rule ${relPath}`);
|
|
4401
4495
|
}
|
|
4402
|
-
function resolveBridgeTargets(opt) {
|
|
4403
|
-
const raw = (opt ?? "
|
|
4404
|
-
if (raw === "
|
|
4496
|
+
function resolveBridgeTargets(opt, root) {
|
|
4497
|
+
const raw = (opt ?? "auto").trim().toLowerCase();
|
|
4498
|
+
if (raw === "all") return [...BRIDGE_TARGETS2];
|
|
4499
|
+
if (raw === "" || raw === "auto") {
|
|
4500
|
+
const detection = detectBridgeTargets(root);
|
|
4501
|
+
const named = detection.targets.map((t) => detection.reasons[t] === "repo file" ? `${t} (already in repo)` : t).join(", ");
|
|
4502
|
+
ui.info(`Bridge targets (detected): ${named} \u2014 pass --bridge-targets all for every supported client`);
|
|
4503
|
+
return detection.targets;
|
|
4504
|
+
}
|
|
4405
4505
|
const requested = raw.split(",").map((t) => t.trim()).filter(Boolean);
|
|
4406
|
-
const valid = requested.filter((t) =>
|
|
4407
|
-
const invalid = requested.filter((t) => !
|
|
4506
|
+
const valid = requested.filter((t) => BRIDGE_TARGETS2.includes(t));
|
|
4507
|
+
const invalid = requested.filter((t) => !BRIDGE_TARGETS2.includes(t));
|
|
4408
4508
|
if (invalid.length > 0) {
|
|
4409
|
-
ui.warn(`Ignoring unknown bridge target(s): ${invalid.join(", ")}. Valid: ${
|
|
4509
|
+
ui.warn(`Ignoring unknown bridge target(s): ${invalid.join(", ")}. Valid: ${BRIDGE_TARGETS2.join(", ")}`);
|
|
4410
4510
|
}
|
|
4411
|
-
return valid.length > 0 ? valid : [...
|
|
4511
|
+
return valid.length > 0 ? valid : [...BRIDGE_TARGETS2];
|
|
4412
4512
|
}
|
|
4413
4513
|
var RUNTIME_README_BODY = `# .ai/.runtime \u2014 disposable local layer
|
|
4414
4514
|
|
|
4415
4515
|
Not team truth. Use for machine-local session notes or tooling scratch files.
|
|
4416
4516
|
Official memories belong in .ai/memories/ (versioned in Git).
|
|
4417
4517
|
Only .gitignore and this README are meant to commit; everything else stays untracked.
|
|
4418
|
-
|
|
4419
|
-
Session continuity (local): agents may append \`session-journal.ndjson\` via MCP \`runtime_journal_append\` or \`hivelore runtime journal append\`.
|
|
4420
4518
|
`;
|
|
4421
4519
|
var RUNTIME_GITIGNORE_BODY = `*
|
|
4422
4520
|
!.gitignore
|
|
@@ -4424,27 +4522,27 @@ var RUNTIME_GITIGNORE_BODY = `*
|
|
|
4424
4522
|
`;
|
|
4425
4523
|
async function ensureAiRuntimeLayout(runtimeDir) {
|
|
4426
4524
|
await mkdir6(runtimeDir, { recursive: true });
|
|
4427
|
-
const gi =
|
|
4428
|
-
if (!
|
|
4525
|
+
const gi = path13.join(runtimeDir, ".gitignore");
|
|
4526
|
+
if (!existsSync12(gi)) {
|
|
4429
4527
|
await writeFile7(gi, RUNTIME_GITIGNORE_BODY, "utf8");
|
|
4430
4528
|
}
|
|
4431
|
-
const readme =
|
|
4432
|
-
if (!
|
|
4529
|
+
const readme = path13.join(runtimeDir, "README.md");
|
|
4530
|
+
if (!existsSync12(readme)) {
|
|
4433
4531
|
await writeFile7(readme, RUNTIME_README_BODY, "utf8");
|
|
4434
4532
|
}
|
|
4435
4533
|
}
|
|
4436
4534
|
async function ensureAiCacheLayout(cacheDir) {
|
|
4437
4535
|
await mkdir6(cacheDir, { recursive: true });
|
|
4438
|
-
const gi =
|
|
4439
|
-
if (!
|
|
4536
|
+
const gi = path13.join(cacheDir, ".gitignore");
|
|
4537
|
+
if (!existsSync12(gi)) {
|
|
4440
4538
|
await writeFile7(gi, "*\n!.gitignore\n", "utf8");
|
|
4441
4539
|
}
|
|
4442
4540
|
}
|
|
4443
4541
|
async function ensureGitignoreEntries(root, patterns) {
|
|
4444
4542
|
try {
|
|
4445
|
-
const gitignorePath =
|
|
4543
|
+
const gitignorePath = path13.join(root, ".gitignore");
|
|
4446
4544
|
let existing = "";
|
|
4447
|
-
if (
|
|
4545
|
+
if (existsSync12(gitignorePath)) {
|
|
4448
4546
|
existing = await readFile6(gitignorePath, "utf8");
|
|
4449
4547
|
}
|
|
4450
4548
|
const lines = existing.split("\n");
|
|
@@ -4458,8 +4556,8 @@ async function ensureGitignoreEntries(root, patterns) {
|
|
|
4458
4556
|
|
|
4459
4557
|
// src/commands/observe.ts
|
|
4460
4558
|
import { appendFile, mkdir as mkdir7 } from "fs/promises";
|
|
4461
|
-
import { existsSync as
|
|
4462
|
-
import
|
|
4559
|
+
import { existsSync as existsSync13 } from "fs";
|
|
4560
|
+
import path14 from "path";
|
|
4463
4561
|
import "commander";
|
|
4464
4562
|
import { findProjectRoot as findProjectRoot7, resolveHaivePaths as resolveHaivePaths7 } from "@hivelore/core";
|
|
4465
4563
|
var MAX_STDIN_BYTES = 256 * 1024;
|
|
@@ -4566,7 +4664,7 @@ function registerObserve(program2) {
|
|
|
4566
4664
|
})();
|
|
4567
4665
|
if (!root) return;
|
|
4568
4666
|
const paths = resolveHaivePaths7(root);
|
|
4569
|
-
if (!
|
|
4667
|
+
if (!existsSync13(paths.haiveDir)) return;
|
|
4570
4668
|
const failureHint = detectFailure(payload);
|
|
4571
4669
|
const observation = {
|
|
4572
4670
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -4577,10 +4675,10 @@ function registerObserve(program2) {
|
|
|
4577
4675
|
files: extractFiles(payload),
|
|
4578
4676
|
...failureHint ? { failure_hint: true } : {}
|
|
4579
4677
|
};
|
|
4580
|
-
const cacheDir =
|
|
4678
|
+
const cacheDir = path14.join(paths.haiveDir, ".cache");
|
|
4581
4679
|
await mkdir7(cacheDir, { recursive: true });
|
|
4582
4680
|
await appendFile(
|
|
4583
|
-
|
|
4681
|
+
path14.join(cacheDir, "observations.jsonl"),
|
|
4584
4682
|
JSON.stringify(observation) + "\n",
|
|
4585
4683
|
"utf8"
|
|
4586
4684
|
);
|
|
@@ -4611,30 +4709,37 @@ function registerMcp(program2) {
|
|
|
4611
4709
|
// src/commands/sync.ts
|
|
4612
4710
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
4613
4711
|
import { readFile as readFile7, writeFile as writeFile8, mkdir as mkdir8 } from "fs/promises";
|
|
4614
|
-
import { existsSync as
|
|
4615
|
-
import
|
|
4712
|
+
import { existsSync as existsSync14 } from "fs";
|
|
4713
|
+
import path15 from "path";
|
|
4616
4714
|
import "commander";
|
|
4617
4715
|
import {
|
|
4618
4716
|
DEFAULT_AUTO_PROMOTE_RULE,
|
|
4717
|
+
assessSensorHealth,
|
|
4619
4718
|
buildFrontmatter as buildFrontmatter3,
|
|
4719
|
+
existingGateMissShas,
|
|
4620
4720
|
findProjectRoot as findProjectRoot9,
|
|
4621
4721
|
getUsage as getUsage2,
|
|
4722
|
+
gatePassedShas,
|
|
4622
4723
|
isAutoPromoteEligible,
|
|
4623
4724
|
isDecaying,
|
|
4624
4725
|
isStackPackSeed,
|
|
4625
4726
|
loadCodeMap as loadCodeMap5,
|
|
4626
4727
|
loadConfig as loadConfig3,
|
|
4627
4728
|
loadMemoriesFromDir as loadMemoriesFromDir7,
|
|
4729
|
+
loadSensorLedger,
|
|
4628
4730
|
loadUsageIndex as loadUsageIndex3,
|
|
4629
4731
|
pullCrossRepoSources,
|
|
4732
|
+
planGitWatch,
|
|
4733
|
+
proposeGateMissDrafts,
|
|
4630
4734
|
resolveHaivePaths as resolveHaivePaths8,
|
|
4631
4735
|
resolveManifestFiles,
|
|
4632
4736
|
serializeMemory as serializeMemory4,
|
|
4737
|
+
withQuarantineNote,
|
|
4633
4738
|
trackDependencies,
|
|
4634
4739
|
verifyAnchor,
|
|
4635
4740
|
watchContracts
|
|
4636
4741
|
} from "@hivelore/core";
|
|
4637
|
-
import { BRIDGE_TARGETS as
|
|
4742
|
+
import { BRIDGE_TARGETS as BRIDGE_TARGETS3 } from "@hivelore/core";
|
|
4638
4743
|
var BRIDGE_START = "<!-- haive:memories-start -->";
|
|
4639
4744
|
var BRIDGE_END = "<!-- haive:memories-end -->";
|
|
4640
4745
|
function registerSync(program2) {
|
|
@@ -4649,7 +4754,7 @@ function registerSync(program2) {
|
|
|
4649
4754
|
).option("--bridge-file <path>", "bridge file to inject into (default: CLAUDE.md)").option("--bridge-max-memories <n>", "max memories to inject into bridge file", "5").option("--embed", "rebuild embeddings index after sync (requires @haive/embeddings)").option("--no-cross-repo", "skip cross-repo memory pull even if crossRepoSources is configured").option("--no-deps", "skip dependency version tracking").option("--no-contracts", "skip contract file diff checking").option("--no-bridges", "skip auto-refresh of existing native agent bridge files (.cursor/rules, .clinerules, \u2026)").option("--dry-run", "report what would change without writing any files").action(async (opts) => {
|
|
4650
4755
|
const root = findProjectRoot9(opts.dir);
|
|
4651
4756
|
const paths = resolveHaivePaths8(root);
|
|
4652
|
-
if (!
|
|
4757
|
+
if (!existsSync14(paths.memoriesDir)) {
|
|
4653
4758
|
if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`hivelore init\` first.`);
|
|
4654
4759
|
process.exitCode = 1;
|
|
4655
4760
|
return;
|
|
@@ -4667,6 +4772,24 @@ function registerSync(program2) {
|
|
|
4667
4772
|
let revalidated = 0;
|
|
4668
4773
|
let promoted = 0;
|
|
4669
4774
|
let autoApproved = 0;
|
|
4775
|
+
let quarantined = 0;
|
|
4776
|
+
const sensorHealth = new Map(
|
|
4777
|
+
assessSensorHealth(await loadSensorLedger(paths)).map((health) => [health.memory_id, health])
|
|
4778
|
+
);
|
|
4779
|
+
const quarantineCandidates = await loadMemoriesFromDir7(paths.memoriesDir);
|
|
4780
|
+
for (const { memory: memory2, filePath } of quarantineCandidates) {
|
|
4781
|
+
const sensor = memory2.frontmatter.sensor;
|
|
4782
|
+
const health = sensorHealth.get(memory2.frontmatter.id);
|
|
4783
|
+
if (!sensor || sensor.kind !== "shell" && sensor.kind !== "test" || sensor.severity !== "block" || !health?.quarantine_pending) continue;
|
|
4784
|
+
if (!dryRun) {
|
|
4785
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
4786
|
+
await writeFile8(filePath, serializeMemory4({
|
|
4787
|
+
frontmatter: { ...memory2.frontmatter, sensor: { ...sensor, severity: "warn" } },
|
|
4788
|
+
body: withQuarantineNote(memory2.body, at, health.flap_count)
|
|
4789
|
+
}), "utf8");
|
|
4790
|
+
}
|
|
4791
|
+
quarantined++;
|
|
4792
|
+
}
|
|
4670
4793
|
if (opts.verify !== false) {
|
|
4671
4794
|
const memories = await loadMemoriesFromDir7(paths.memoriesDir);
|
|
4672
4795
|
for (const { memory: memory2, filePath } of memories) {
|
|
@@ -4787,13 +4910,17 @@ function registerSync(program2) {
|
|
|
4787
4910
|
for (const repair of repairs) log(ui.dim(`autopilot: ${repair.message}`));
|
|
4788
4911
|
}
|
|
4789
4912
|
const sinceReport = opts.since ? collectSinceChanges(root, opts.since) : null;
|
|
4913
|
+
const gateMissIds = await processGateMissWatch(root, paths, dryRun);
|
|
4914
|
+
if (gateMissIds.length > 0) {
|
|
4915
|
+
log(ui.yellow(`gate-miss: proposed ${gateMissIds.length} lesson(s): ${gateMissIds.join(", ")}`));
|
|
4916
|
+
}
|
|
4790
4917
|
const draftMemories = (await loadMemoriesFromDir7(paths.memoriesDir)).filter(
|
|
4791
4918
|
(m) => m.memory.frontmatter.status === "draft"
|
|
4792
4919
|
);
|
|
4793
4920
|
const draftCount = draftMemories.length;
|
|
4794
4921
|
const autoApprovedNote = autoApproved > 0 ? ` \xB7 ${autoApproved} auto-approved` : "";
|
|
4795
4922
|
log(
|
|
4796
|
-
`${ui.dim("sync:")} ${staleMarked} stale \xB7 ${revalidated} revalidated \xB7 ${promoted} promoted${autoApprovedNote}${sinceReport ? ` \xB7 ${sinceReport.added.length}+/${sinceReport.modified.length}~/${sinceReport.removed.length}- since ${opts.since}` : ""}`
|
|
4923
|
+
`${ui.dim("sync:")} ${staleMarked} stale \xB7 ${revalidated} revalidated \xB7 ${promoted} promoted \xB7 ${quarantined} quarantined${autoApprovedNote}${sinceReport ? ` \xB7 ${sinceReport.added.length}+/${sinceReport.modified.length}~/${sinceReport.removed.length}- since ${opts.since}` : ""}`
|
|
4797
4924
|
);
|
|
4798
4925
|
if (!opts.quiet && draftCount > 0) {
|
|
4799
4926
|
log(
|
|
@@ -4805,7 +4932,7 @@ function registerSync(program2) {
|
|
|
4805
4932
|
if (opts.injectBridge) {
|
|
4806
4933
|
const maxInject = Math.max(1, Number(opts.bridgeMaxMemories ?? 5));
|
|
4807
4934
|
if (opts.bridgeFile) {
|
|
4808
|
-
await injectBridge(
|
|
4935
|
+
await injectBridge(path15.resolve(opts.bridgeFile), paths.memoriesDir, maxInject, root, opts.quiet);
|
|
4809
4936
|
} else if (!dryRun) {
|
|
4810
4937
|
const res = await writeBridgeFiles(root, paths, {
|
|
4811
4938
|
targets: ["claude", "agents"],
|
|
@@ -4834,7 +4961,7 @@ function registerSync(program2) {
|
|
|
4834
4961
|
if (opts.noBridges !== true) {
|
|
4835
4962
|
try {
|
|
4836
4963
|
const res = await writeBridgeFiles(root, paths, {
|
|
4837
|
-
targets:
|
|
4964
|
+
targets: BRIDGE_TARGETS3,
|
|
4838
4965
|
onlyExisting: true,
|
|
4839
4966
|
dryRun
|
|
4840
4967
|
});
|
|
@@ -4954,10 +5081,10 @@ Wait for **explicit confirmation** before acting.
|
|
|
4954
5081
|
topic: `dep-bump-${slugParts}`
|
|
4955
5082
|
});
|
|
4956
5083
|
if (!dryRun) {
|
|
4957
|
-
const teamDir =
|
|
5084
|
+
const teamDir = path15.join(paths.memoriesDir, "team");
|
|
4958
5085
|
await mkdir8(teamDir, { recursive: true });
|
|
4959
5086
|
await writeFile8(
|
|
4960
|
-
|
|
5087
|
+
path15.join(teamDir, `${fm.id}.md`),
|
|
4961
5088
|
serializeMemory4({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
4962
5089
|
"utf8"
|
|
4963
5090
|
);
|
|
@@ -5023,10 +5150,10 @@ Wait for **explicit confirmation** before acting.
|
|
|
5023
5150
|
topic: `contract-breaking-${diff.contract}`
|
|
5024
5151
|
});
|
|
5025
5152
|
if (!dryRun) {
|
|
5026
|
-
const teamDir =
|
|
5153
|
+
const teamDir = path15.join(paths.memoriesDir, "team");
|
|
5027
5154
|
await mkdir8(teamDir, { recursive: true });
|
|
5028
5155
|
await writeFile8(
|
|
5029
|
-
|
|
5156
|
+
path15.join(teamDir, `${fm.id}.md`),
|
|
5030
5157
|
serializeMemory4({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
5031
5158
|
"utf8"
|
|
5032
5159
|
);
|
|
@@ -5111,13 +5238,90 @@ Wait for **explicit confirmation** before acting.
|
|
|
5111
5238
|
}
|
|
5112
5239
|
});
|
|
5113
5240
|
}
|
|
5241
|
+
async function processGateMissWatch(root, paths, dryRun) {
|
|
5242
|
+
const headResult = spawnSync4("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" });
|
|
5243
|
+
if (headResult.status !== 0) return [];
|
|
5244
|
+
const head = headResult.stdout.trim();
|
|
5245
|
+
if (!head) return [];
|
|
5246
|
+
const stateFile = path15.join(paths.runtimeDir, "enforcement", "git-watch.json");
|
|
5247
|
+
let state = null;
|
|
5248
|
+
try {
|
|
5249
|
+
state = JSON.parse(await readFile7(stateFile, "utf8"));
|
|
5250
|
+
} catch {
|
|
5251
|
+
}
|
|
5252
|
+
const plan = planGitWatch(state, head);
|
|
5253
|
+
if (plan.action === "initialize") {
|
|
5254
|
+
if (!dryRun) {
|
|
5255
|
+
await mkdir8(path15.dirname(stateFile), { recursive: true });
|
|
5256
|
+
await writeFile8(stateFile, JSON.stringify(plan.next, null, 2) + "\n", "utf8");
|
|
5257
|
+
}
|
|
5258
|
+
return [];
|
|
5259
|
+
}
|
|
5260
|
+
if (plan.action === "idle") return [];
|
|
5261
|
+
const proposals = proposeGateMissDrafts(
|
|
5262
|
+
readGitCommitsRange(root, plan.range),
|
|
5263
|
+
existingGateMissShas(await loadMemoriesFromDir7(paths.memoriesDir)),
|
|
5264
|
+
gatePassedShas(await loadSensorLedger(paths))
|
|
5265
|
+
);
|
|
5266
|
+
const ids = [];
|
|
5267
|
+
for (const proposal of proposals) {
|
|
5268
|
+
const fm = {
|
|
5269
|
+
...buildFrontmatter3({
|
|
5270
|
+
type: "attempt",
|
|
5271
|
+
slug: proposal.slug,
|
|
5272
|
+
scope: "team",
|
|
5273
|
+
status: "proposed",
|
|
5274
|
+
tags: ["gate-miss", proposal.kind],
|
|
5275
|
+
paths: proposal.paths,
|
|
5276
|
+
topic: `gate-miss-${proposal.reverted_sha}`
|
|
5277
|
+
}),
|
|
5278
|
+
status: "proposed"
|
|
5279
|
+
};
|
|
5280
|
+
ids.push(fm.id);
|
|
5281
|
+
if (!dryRun) {
|
|
5282
|
+
await mkdir8(paths.teamDir, { recursive: true });
|
|
5283
|
+
await writeFile8(
|
|
5284
|
+
path15.join(paths.teamDir, `${fm.id}.md`),
|
|
5285
|
+
serializeMemory4({ frontmatter: fm, body: proposal.body }),
|
|
5286
|
+
"utf8"
|
|
5287
|
+
);
|
|
5288
|
+
}
|
|
5289
|
+
}
|
|
5290
|
+
if (!dryRun) {
|
|
5291
|
+
await mkdir8(path15.dirname(stateFile), { recursive: true });
|
|
5292
|
+
await writeFile8(stateFile, JSON.stringify(plan.next, null, 2) + "\n", "utf8");
|
|
5293
|
+
}
|
|
5294
|
+
return ids;
|
|
5295
|
+
}
|
|
5296
|
+
function readGitCommitsRange(root, range) {
|
|
5297
|
+
const result = spawnSync4(
|
|
5298
|
+
"git",
|
|
5299
|
+
["log", "--reverse", "--format=%x1e%H%x1f%s%x1f%b%x1f", "--name-only", range],
|
|
5300
|
+
{ cwd: root, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 }
|
|
5301
|
+
);
|
|
5302
|
+
if (result.status !== 0) return [];
|
|
5303
|
+
const commits = [];
|
|
5304
|
+
for (const block of result.stdout.split("").filter((part) => part.trim())) {
|
|
5305
|
+
const [shaRaw, subjectRaw, bodyRaw, filesRaw = ""] = block.split("");
|
|
5306
|
+
const sha = shaRaw?.trim();
|
|
5307
|
+
const subject = subjectRaw?.trim();
|
|
5308
|
+
if (!sha || !subject) continue;
|
|
5309
|
+
commits.push({
|
|
5310
|
+
sha,
|
|
5311
|
+
subject,
|
|
5312
|
+
body: bodyRaw?.trim() ?? "",
|
|
5313
|
+
files: filesRaw.split("\n").map((file) => file.trim()).filter(Boolean)
|
|
5314
|
+
});
|
|
5315
|
+
}
|
|
5316
|
+
return commits;
|
|
5317
|
+
}
|
|
5114
5318
|
function bridgeSummaryLine(body) {
|
|
5115
5319
|
const firstLine = body.split("\n").map((l) => l.replace(/^#+\s*/, "").trim()).find((l) => l.length > 0) ?? "";
|
|
5116
5320
|
const oneLine = firstLine.replace(/\s+/g, " ");
|
|
5117
5321
|
return oneLine.length > 140 ? oneLine.slice(0, 137) + "\u2026" : oneLine;
|
|
5118
5322
|
}
|
|
5119
5323
|
async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
|
|
5120
|
-
if (!
|
|
5324
|
+
if (!existsSync14(memoriesDir)) return;
|
|
5121
5325
|
const all = await loadMemoriesFromDir7(memoriesDir);
|
|
5122
5326
|
const top = all.filter(({ memory: memory2 }) => {
|
|
5123
5327
|
const s = memory2.frontmatter.status;
|
|
@@ -5143,17 +5347,17 @@ async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
|
|
|
5143
5347
|
` + block + `
|
|
5144
5348
|
|
|
5145
5349
|
${BRIDGE_END}`;
|
|
5146
|
-
const fileExists =
|
|
5350
|
+
const fileExists = existsSync14(bridgeFile);
|
|
5147
5351
|
let existing = fileExists ? await readFile7(bridgeFile, "utf8") : "";
|
|
5148
5352
|
existing = existing.replace(/\r\n/g, "\n");
|
|
5149
5353
|
const startIdx = existing.indexOf(BRIDGE_START);
|
|
5150
5354
|
const endIdx = existing.indexOf(BRIDGE_END);
|
|
5151
5355
|
if (startIdx !== -1 && endIdx === -1) {
|
|
5152
|
-
ui.warn(`${
|
|
5356
|
+
ui.warn(`${path15.relative(root, bridgeFile)}: found ${BRIDGE_START} without ${BRIDGE_END}. Fix the file manually before running --inject-bridge.`);
|
|
5153
5357
|
return;
|
|
5154
5358
|
}
|
|
5155
5359
|
if (startIdx === -1 && endIdx !== -1) {
|
|
5156
|
-
ui.warn(`${
|
|
5360
|
+
ui.warn(`${path15.relative(root, bridgeFile)}: found ${BRIDGE_END} without ${BRIDGE_START}. Fix the file manually before running --inject-bridge.`);
|
|
5157
5361
|
return;
|
|
5158
5362
|
}
|
|
5159
5363
|
let updated;
|
|
@@ -5161,14 +5365,14 @@ ${BRIDGE_END}`;
|
|
|
5161
5365
|
updated = existing.slice(0, startIdx) + injected + existing.slice(endIdx + BRIDGE_END.length);
|
|
5162
5366
|
} else {
|
|
5163
5367
|
if (!fileExists && !quiet) {
|
|
5164
|
-
ui.info(`Creating ${
|
|
5368
|
+
ui.info(`Creating ${path15.relative(root, bridgeFile)} with hivelore memory block.`);
|
|
5165
5369
|
}
|
|
5166
5370
|
updated = existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + injected + "\n";
|
|
5167
5371
|
}
|
|
5168
5372
|
await writeFile8(bridgeFile, updated, "utf8");
|
|
5169
5373
|
if (!quiet) {
|
|
5170
5374
|
console.log(
|
|
5171
|
-
ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${
|
|
5375
|
+
ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path15.relative(root, bridgeFile)}`)
|
|
5172
5376
|
);
|
|
5173
5377
|
}
|
|
5174
5378
|
}
|
|
@@ -5194,8 +5398,8 @@ function collectSinceChanges(root, ref) {
|
|
|
5194
5398
|
// src/commands/memory-add.ts
|
|
5195
5399
|
import { createHash } from "crypto";
|
|
5196
5400
|
import { mkdir as mkdir9, readFile as readFile8, writeFile as writeFile9 } from "fs/promises";
|
|
5197
|
-
import { existsSync as
|
|
5198
|
-
import
|
|
5401
|
+
import { existsSync as existsSync15 } from "fs";
|
|
5402
|
+
import path16 from "path";
|
|
5199
5403
|
import { Option } from "commander";
|
|
5200
5404
|
import {
|
|
5201
5405
|
buildFrontmatter as buildFrontmatter4,
|
|
@@ -5237,7 +5441,7 @@ function registerMemoryAdd(memory2) {
|
|
|
5237
5441
|
if (opts.body === void 0 && opts.content !== void 0) opts.body = opts.content;
|
|
5238
5442
|
const root = findProjectRoot10(opts.dir);
|
|
5239
5443
|
const paths = resolveHaivePaths9(root);
|
|
5240
|
-
if (!
|
|
5444
|
+
if (!existsSync15(paths.haiveDir)) {
|
|
5241
5445
|
ui.error(`No .ai/ found at ${root}. Run \`hivelore init\` first.`);
|
|
5242
5446
|
process.exitCode = 1;
|
|
5243
5447
|
return;
|
|
@@ -5254,7 +5458,7 @@ function registerMemoryAdd(memory2) {
|
|
|
5254
5458
|
const inferredTags = autoTagsEnabled ? inferModulesFromPaths2(anchorPaths) : [];
|
|
5255
5459
|
const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
|
|
5256
5460
|
if (anchorPaths.length > 0) {
|
|
5257
|
-
const missing = anchorPaths.filter((p) => !
|
|
5461
|
+
const missing = anchorPaths.filter((p) => !existsSync15(path16.resolve(root, p)));
|
|
5258
5462
|
if (missing.length > 0) {
|
|
5259
5463
|
ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
|
|
5260
5464
|
for (const p of missing) ui.warn(` \u2717 ${p}`);
|
|
@@ -5267,7 +5471,7 @@ function registerMemoryAdd(memory2) {
|
|
|
5267
5471
|
const slug = slugify(opts.slug ?? opts.title ?? opts.topic ?? opts.body ?? `${opts.type}-memory`);
|
|
5268
5472
|
let body;
|
|
5269
5473
|
if (opts.bodyFile !== void 0) {
|
|
5270
|
-
if (!
|
|
5474
|
+
if (!existsSync15(opts.bodyFile)) {
|
|
5271
5475
|
ui.error(`--body-file not found: ${opts.bodyFile}`);
|
|
5272
5476
|
process.exitCode = 1;
|
|
5273
5477
|
return;
|
|
@@ -5283,7 +5487,7 @@ TODO \u2014 write the memory body.
|
|
|
5283
5487
|
`;
|
|
5284
5488
|
}
|
|
5285
5489
|
const scope = opts.scope ?? config.defaultScope ?? "personal";
|
|
5286
|
-
if (
|
|
5490
|
+
if (existsSync15(paths.memoriesDir)) {
|
|
5287
5491
|
const incomingHash = createHash("sha256").update(body.trim()).digest("hex").slice(0, 12);
|
|
5288
5492
|
const allForHash = await loadMemoriesFromDir8(paths.memoriesDir);
|
|
5289
5493
|
const hashDup = allForHash.find(
|
|
@@ -5296,7 +5500,7 @@ TODO \u2014 write the memory body.
|
|
|
5296
5500
|
return;
|
|
5297
5501
|
}
|
|
5298
5502
|
}
|
|
5299
|
-
if (opts.topic &&
|
|
5503
|
+
if (opts.topic && existsSync15(paths.memoriesDir)) {
|
|
5300
5504
|
const existing = await loadMemoriesFromDir8(paths.memoriesDir);
|
|
5301
5505
|
const topicMatch = existing.find(
|
|
5302
5506
|
({ memory: memory3 }) => memory3.frontmatter.topic === opts.topic && memory3.frontmatter.scope === scope && (!opts.module || memory3.frontmatter.module === opts.module)
|
|
@@ -5316,7 +5520,7 @@ TODO \u2014 write the memory body.
|
|
|
5316
5520
|
}
|
|
5317
5521
|
};
|
|
5318
5522
|
await writeFile9(topicMatch.filePath, serializeMemory5({ frontmatter: newFrontmatter, body }), "utf8");
|
|
5319
|
-
ui.success(`Updated (topic upsert) ${
|
|
5523
|
+
ui.success(`Updated (topic upsert) ${path16.relative(root, topicMatch.filePath)}`);
|
|
5320
5524
|
ui.info(`id=${fm.id} revision=${revisionCount}`);
|
|
5321
5525
|
printSensorLoopHint(opts.type, body, newFrontmatter.anchor.paths, Boolean(newFrontmatter.sensor));
|
|
5322
5526
|
await runPostMemoryAutopilot(root, paths, config);
|
|
@@ -5340,13 +5544,13 @@ TODO \u2014 write the memory body.
|
|
|
5340
5544
|
});
|
|
5341
5545
|
if (frontmatter.status === "validated") frontmatter.validated_by = "auto";
|
|
5342
5546
|
const file = memoryFilePath3(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
5343
|
-
await mkdir9(
|
|
5344
|
-
if (
|
|
5547
|
+
await mkdir9(path16.dirname(file), { recursive: true });
|
|
5548
|
+
if (existsSync15(file)) {
|
|
5345
5549
|
ui.error(`Memory already exists at ${file}`);
|
|
5346
5550
|
process.exitCode = 1;
|
|
5347
5551
|
return;
|
|
5348
5552
|
}
|
|
5349
|
-
if (
|
|
5553
|
+
if (existsSync15(paths.memoriesDir)) {
|
|
5350
5554
|
const existing = await loadMemoriesFromDir8(paths.memoriesDir);
|
|
5351
5555
|
const slugTokens = slug.toLowerCase().split(/[-_\s]+/).filter(Boolean);
|
|
5352
5556
|
const similar = existing.filter(({ memory: memory3 }) => {
|
|
@@ -5359,7 +5563,7 @@ TODO \u2014 write the memory body.
|
|
|
5359
5563
|
}
|
|
5360
5564
|
}
|
|
5361
5565
|
await writeFile9(file, serializeMemory5({ frontmatter, body }), "utf8");
|
|
5362
|
-
ui.success(`Created ${
|
|
5566
|
+
ui.success(`Created ${path16.relative(root, file)}`);
|
|
5363
5567
|
ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
|
|
5364
5568
|
printSensorLoopHint(opts.type, body, anchorPaths, Boolean(frontmatter.sensor));
|
|
5365
5569
|
await runPostMemoryAutopilot(root, paths, config);
|
|
@@ -5453,8 +5657,8 @@ function slugify(value) {
|
|
|
5453
5657
|
}
|
|
5454
5658
|
|
|
5455
5659
|
// src/commands/memory-list.ts
|
|
5456
|
-
import { existsSync as
|
|
5457
|
-
import
|
|
5660
|
+
import { existsSync as existsSync16 } from "fs";
|
|
5661
|
+
import path17 from "path";
|
|
5458
5662
|
import "commander";
|
|
5459
5663
|
import { findProjectRoot as findProjectRoot11, resolveHaivePaths as resolveHaivePaths10 } from "@hivelore/core";
|
|
5460
5664
|
function registerMemoryList(memory2) {
|
|
@@ -5466,7 +5670,7 @@ function registerMemoryList(memory2) {
|
|
|
5466
5670
|
}
|
|
5467
5671
|
const root = findProjectRoot11(opts.dir);
|
|
5468
5672
|
const paths = resolveHaivePaths10(root);
|
|
5469
|
-
if (!
|
|
5673
|
+
if (!existsSync16(paths.memoriesDir)) {
|
|
5470
5674
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`hivelore init\` first.`);
|
|
5471
5675
|
process.exitCode = 1;
|
|
5472
5676
|
return;
|
|
@@ -5505,7 +5709,7 @@ function registerMemoryList(memory2) {
|
|
|
5505
5709
|
);
|
|
5506
5710
|
const title = mem.body.match(/^#\s+(.+)$/m)?.[1]?.trim();
|
|
5507
5711
|
if (title && title !== fm.id) console.log(` ${title}`);
|
|
5508
|
-
console.log(` ${ui.dim(
|
|
5712
|
+
console.log(` ${ui.dim(path17.relative(root, filePath))}`);
|
|
5509
5713
|
}
|
|
5510
5714
|
const totalLabel = clipped > 0 ? `
|
|
5511
5715
|
${displayed.length} of ${filtered.length} memories shown (use --limit to adjust)` : `
|
|
@@ -5543,8 +5747,8 @@ function matchesFilters(loaded, opts) {
|
|
|
5543
5747
|
|
|
5544
5748
|
// src/commands/memory-promote.ts
|
|
5545
5749
|
import { mkdir as mkdir10, unlink, writeFile as writeFile10 } from "fs/promises";
|
|
5546
|
-
import { existsSync as
|
|
5547
|
-
import
|
|
5750
|
+
import { existsSync as existsSync17 } from "fs";
|
|
5751
|
+
import path18 from "path";
|
|
5548
5752
|
import "commander";
|
|
5549
5753
|
import {
|
|
5550
5754
|
findProjectRoot as findProjectRoot12,
|
|
@@ -5556,7 +5760,7 @@ function registerMemoryPromote(memory2) {
|
|
|
5556
5760
|
memory2.command("promote <id>").description("Promote a personal memory to team scope (status -> proposed)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
5557
5761
|
const root = findProjectRoot12(opts.dir);
|
|
5558
5762
|
const paths = resolveHaivePaths11(root);
|
|
5559
|
-
if (!
|
|
5763
|
+
if (!existsSync17(paths.memoriesDir)) {
|
|
5560
5764
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`hivelore init\` first.`);
|
|
5561
5765
|
process.exitCode = 1;
|
|
5562
5766
|
return;
|
|
@@ -5591,19 +5795,19 @@ function registerMemoryPromote(memory2) {
|
|
|
5591
5795
|
body: found.memory.body
|
|
5592
5796
|
};
|
|
5593
5797
|
const newPath = memoryFilePath4(paths, "team", updated.frontmatter.id);
|
|
5594
|
-
await mkdir10(
|
|
5798
|
+
await mkdir10(path18.dirname(newPath), { recursive: true });
|
|
5595
5799
|
await writeFile10(newPath, serializeMemory6(updated), "utf8");
|
|
5596
5800
|
await unlink(found.filePath);
|
|
5597
5801
|
ui.success(`Promoted ${id} to team scope (status=proposed)`);
|
|
5598
|
-
ui.info(`Now at ${
|
|
5802
|
+
ui.info(`Now at ${path18.relative(root, newPath)}`);
|
|
5599
5803
|
console.log(ui.dim(`\u2192 next: hivelore memory approve ${id} (validate for team use)`));
|
|
5600
5804
|
});
|
|
5601
5805
|
}
|
|
5602
5806
|
|
|
5603
5807
|
// src/commands/memory-approve.ts
|
|
5604
|
-
import { existsSync as
|
|
5808
|
+
import { existsSync as existsSync18 } from "fs";
|
|
5605
5809
|
import { writeFile as writeFile11 } from "fs/promises";
|
|
5606
|
-
import
|
|
5810
|
+
import path19 from "path";
|
|
5607
5811
|
import "commander";
|
|
5608
5812
|
import {
|
|
5609
5813
|
findProjectRoot as findProjectRoot13,
|
|
@@ -5614,7 +5818,7 @@ function registerMemoryApprove(memory2) {
|
|
|
5614
5818
|
memory2.command("approve [id]").description("Mark a memory as 'validated'. Use --all to bulk-approve all proposed/draft memories.").option("--all", "approve all proposed and draft memories at once").option("--pending", "approve all memories with status 'proposed'").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
5615
5819
|
const root = findProjectRoot13(opts.dir);
|
|
5616
5820
|
const paths = resolveHaivePaths12(root);
|
|
5617
|
-
if (!
|
|
5821
|
+
if (!existsSync18(paths.memoriesDir)) {
|
|
5618
5822
|
ui.error(`No .ai/memories at ${root}.`);
|
|
5619
5823
|
process.exitCode = 1;
|
|
5620
5824
|
return;
|
|
@@ -5668,15 +5872,15 @@ function registerMemoryApprove(memory2) {
|
|
|
5668
5872
|
};
|
|
5669
5873
|
await writeFile11(found.filePath, serializeMemory7(next), "utf8");
|
|
5670
5874
|
ui.success(`Approved ${id} (status=validated, by=human)`);
|
|
5671
|
-
ui.info(
|
|
5875
|
+
ui.info(path19.relative(root, found.filePath));
|
|
5672
5876
|
});
|
|
5673
5877
|
}
|
|
5674
5878
|
|
|
5675
5879
|
// src/commands/memory-update.ts
|
|
5676
5880
|
import { spawn } from "child_process";
|
|
5677
5881
|
import { readFile as readFile9, writeFile as writeFile12 } from "fs/promises";
|
|
5678
|
-
import { existsSync as
|
|
5679
|
-
import
|
|
5882
|
+
import { existsSync as existsSync19 } from "fs";
|
|
5883
|
+
import path20 from "path";
|
|
5680
5884
|
import "commander";
|
|
5681
5885
|
import {
|
|
5682
5886
|
findProjectRoot as findProjectRoot14,
|
|
@@ -5689,7 +5893,7 @@ function registerMemoryUpdate(memory2) {
|
|
|
5689
5893
|
const root = findProjectRoot14(opts.dir);
|
|
5690
5894
|
const paths = resolveHaivePaths13(root);
|
|
5691
5895
|
if (opts.edit) {
|
|
5692
|
-
const all =
|
|
5896
|
+
const all = existsSync19(paths.memoriesDir) ? await loadMemoriesFromDir(paths.memoriesDir) : [];
|
|
5693
5897
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
5694
5898
|
if (!found) {
|
|
5695
5899
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -5697,7 +5901,7 @@ function registerMemoryUpdate(memory2) {
|
|
|
5697
5901
|
return;
|
|
5698
5902
|
}
|
|
5699
5903
|
const editor = opts.editor ?? process.env.EDITOR ?? process.env.VISUAL ?? "vi";
|
|
5700
|
-
ui.info(`Opening ${
|
|
5904
|
+
ui.info(`Opening ${path20.relative(root, found.filePath)} with ${editor}\u2026`);
|
|
5701
5905
|
const code = await new Promise((resolve) => {
|
|
5702
5906
|
const child = spawn(editor, [found.filePath], { stdio: "inherit" });
|
|
5703
5907
|
child.on("exit", (c) => resolve(c ?? 0));
|
|
@@ -5714,7 +5918,7 @@ function registerMemoryUpdate(memory2) {
|
|
|
5714
5918
|
}
|
|
5715
5919
|
return;
|
|
5716
5920
|
}
|
|
5717
|
-
if (!
|
|
5921
|
+
if (!existsSync19(paths.memoriesDir)) {
|
|
5718
5922
|
ui.error(`No .ai/memories at ${root}. Run \`hivelore init\` first.`);
|
|
5719
5923
|
process.exitCode = 1;
|
|
5720
5924
|
return;
|
|
@@ -5756,7 +5960,7 @@ function registerMemoryUpdate(memory2) {
|
|
|
5756
5960
|
if (opts.author !== void 0) updated.push("author");
|
|
5757
5961
|
let newBody;
|
|
5758
5962
|
if (opts.bodyFile !== void 0) {
|
|
5759
|
-
if (!
|
|
5963
|
+
if (!existsSync19(opts.bodyFile)) {
|
|
5760
5964
|
ui.error(`--body-file not found: ${opts.bodyFile}`);
|
|
5761
5965
|
process.exitCode = 1;
|
|
5762
5966
|
return;
|
|
@@ -5782,7 +5986,7 @@ function registerMemoryUpdate(memory2) {
|
|
|
5782
5986
|
serializeMemory8({ frontmatter: newFrontmatter, body: newBody }),
|
|
5783
5987
|
"utf8"
|
|
5784
5988
|
);
|
|
5785
|
-
ui.success(`Updated ${
|
|
5989
|
+
ui.success(`Updated ${path20.relative(root, loaded.filePath)}`);
|
|
5786
5990
|
ui.info(`fields: ${updated.join(", ")}`);
|
|
5787
5991
|
});
|
|
5788
5992
|
}
|
|
@@ -5801,8 +6005,8 @@ function parseCsv3(value) {
|
|
|
5801
6005
|
}
|
|
5802
6006
|
|
|
5803
6007
|
// src/commands/memory-tried.ts
|
|
5804
|
-
import { existsSync as
|
|
5805
|
-
import
|
|
6008
|
+
import { existsSync as existsSync20 } from "fs";
|
|
6009
|
+
import path21 from "path";
|
|
5806
6010
|
import "commander";
|
|
5807
6011
|
import {
|
|
5808
6012
|
findProjectRoot as findProjectRoot15,
|
|
@@ -5817,7 +6021,7 @@ function registerMemoryTried(memory2) {
|
|
|
5817
6021
|
).requiredOption("--what <text>", "what approach was tried (short, descriptive title)").requiredOption("--why-failed <text>", "why it failed or should NOT be used (include the exact error if possible)").option("--instead <text>", "the correct approach to use instead").option("--scope <scope>", "personal | team | module", "personal").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags").option("--paths <csv>", "anchor paths, comma-separated").option("--files <csv>", "alias for --paths (matches the MCP `files` parameter)").option("--author <author>", "author email or handle").option("--sensor-pattern <regex>", "one-shot: regex matching the FAULTY usage \u2014 validates + attaches a sensor in this call").option("--sensor-command <cmd>", "one-shot BEHAVIOUR sensor: a command (test/script) the gate runs when the diff touches --paths; non-zero exit = lesson fires").option("--sensor-kind <kind>", "with --sensor-command: shell | test (default test)").option("--sensor-timeout <ms>", "with --sensor-command: max runtime in ms (default 120000)").option("--sensor-absent <regex>", "one-shot: regex marking CORRECT usage nearby (excludes it from firing)").option("--sensor-severity <level>", "one-shot sensor severity: warn | block", "block").option("--sensor-message <text>", "one-shot: self-correction message shown when the sensor fires").option("--bad-example <code>", "one-shot: code snippet the sensor must fire on (validation)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
5818
6022
|
const root = findProjectRoot15(opts.dir);
|
|
5819
6023
|
const paths = resolveHaivePaths14(root);
|
|
5820
|
-
if (!
|
|
6024
|
+
if (!existsSync20(paths.haiveDir)) {
|
|
5821
6025
|
ui.error(`No .ai/ found at ${root}. Run \`hivelore init\` first.`);
|
|
5822
6026
|
process.exitCode = 1;
|
|
5823
6027
|
return;
|
|
@@ -5863,7 +6067,7 @@ function registerMemoryTried(memory2) {
|
|
|
5863
6067
|
process.exitCode = 1;
|
|
5864
6068
|
return;
|
|
5865
6069
|
}
|
|
5866
|
-
ui.success(`Recorded: ${
|
|
6070
|
+
ui.success(`Recorded: ${path21.relative(root, result.file_path)}`);
|
|
5867
6071
|
ui.info(`id=${result.id} type=attempt status=validated (auto-approved)`);
|
|
5868
6072
|
if (result.sensor_result) {
|
|
5869
6073
|
if (result.sensor_result.accepted) {
|
|
@@ -5887,8 +6091,8 @@ function registerMemoryTried(memory2) {
|
|
|
5887
6091
|
|
|
5888
6092
|
// src/commands/memory-seed.ts
|
|
5889
6093
|
import { readFile as readFile10 } from "fs/promises";
|
|
5890
|
-
import { existsSync as
|
|
5891
|
-
import
|
|
6094
|
+
import { existsSync as existsSync21 } from "fs";
|
|
6095
|
+
import path22 from "path";
|
|
5892
6096
|
import "commander";
|
|
5893
6097
|
import {
|
|
5894
6098
|
findProjectRoot as findProjectRoot16,
|
|
@@ -5897,7 +6101,7 @@ import {
|
|
|
5897
6101
|
} from "@hivelore/core";
|
|
5898
6102
|
async function readDependencyMap(root) {
|
|
5899
6103
|
try {
|
|
5900
|
-
const raw = await readFile10(
|
|
6104
|
+
const raw = await readFile10(path22.join(root, "package.json"), "utf8");
|
|
5901
6105
|
const pkg = JSON.parse(raw);
|
|
5902
6106
|
return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
5903
6107
|
} catch {
|
|
@@ -5932,7 +6136,7 @@ function registerMemorySeed(memory2) {
|
|
|
5932
6136
|
}
|
|
5933
6137
|
return;
|
|
5934
6138
|
}
|
|
5935
|
-
if (!
|
|
6139
|
+
if (!existsSync21(paths.haiveDir)) {
|
|
5936
6140
|
ui.error(`No .ai/ found at ${root}. Run \`hivelore init\` first.`);
|
|
5937
6141
|
process.exitCode = 1;
|
|
5938
6142
|
return;
|
|
@@ -5988,8 +6192,8 @@ function registerMemorySeed(memory2) {
|
|
|
5988
6192
|
}
|
|
5989
6193
|
|
|
5990
6194
|
// src/commands/memory-query.ts
|
|
5991
|
-
import { existsSync as
|
|
5992
|
-
import
|
|
6195
|
+
import { existsSync as existsSync22 } from "fs";
|
|
6196
|
+
import path23 from "path";
|
|
5993
6197
|
import "commander";
|
|
5994
6198
|
import {
|
|
5995
6199
|
extractSnippet,
|
|
@@ -6005,7 +6209,7 @@ function registerMemoryQuery(memory2) {
|
|
|
6005
6209
|
memory2.command("search <text>").alias("query").description("Search memories by id, tag, or substring (AND, OR fallback). Mirrors MCP mem_search. Alias: query").option("-d, --dir <dir>", "project root").option("--limit <n>", "max results", "20").option("--scope <scope>", "personal | team | module").option("--status <csv>", "filter by status (draft,proposed,validated,stale,rejected)").option("--show-rejected", "include rejected memories (hidden by default)").action(async (text, opts) => {
|
|
6006
6210
|
const root = findProjectRoot17(opts.dir);
|
|
6007
6211
|
const paths = resolveHaivePaths16(root);
|
|
6008
|
-
if (!
|
|
6212
|
+
if (!existsSync22(paths.memoriesDir)) {
|
|
6009
6213
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`hivelore init\` first.`);
|
|
6010
6214
|
process.exitCode = 1;
|
|
6011
6215
|
return;
|
|
@@ -6046,7 +6250,7 @@ function registerMemoryQuery(memory2) {
|
|
|
6046
6250
|
const fm = mem.frontmatter;
|
|
6047
6251
|
const statusBadge = ui.statusBadge(fm.status);
|
|
6048
6252
|
console.log(`${ui.bold(fm.id)} ${ui.dim(fm.scope)} ${statusBadge}`);
|
|
6049
|
-
console.log(` ${ui.dim(
|
|
6253
|
+
console.log(` ${ui.dim(path23.relative(root, filePath))}`);
|
|
6050
6254
|
const snippet = extractSnippet(mem.body, snippetNeedle);
|
|
6051
6255
|
if (snippet) console.log(` ${snippet}`);
|
|
6052
6256
|
}
|
|
@@ -6064,7 +6268,7 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
|
|
|
6064
6268
|
|
|
6065
6269
|
// src/commands/memory-reject.ts
|
|
6066
6270
|
import { writeFile as writeFile13 } from "fs/promises";
|
|
6067
|
-
import { existsSync as
|
|
6271
|
+
import { existsSync as existsSync23 } from "fs";
|
|
6068
6272
|
import "commander";
|
|
6069
6273
|
import {
|
|
6070
6274
|
findProjectRoot as findProjectRoot18,
|
|
@@ -6078,7 +6282,7 @@ function registerMemoryReject(memory2) {
|
|
|
6078
6282
|
memory2.command("reject <id>").description("Record a rejection (blocks auto-promotion and lowers confidence)").option("-r, --reason <reason>", "why this memory is being rejected").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
6079
6283
|
const root = findProjectRoot18(opts.dir);
|
|
6080
6284
|
const paths = resolveHaivePaths17(root);
|
|
6081
|
-
if (!
|
|
6285
|
+
if (!existsSync23(paths.memoriesDir)) {
|
|
6082
6286
|
ui.error(`No .ai/memories at ${root}.`);
|
|
6083
6287
|
process.exitCode = 1;
|
|
6084
6288
|
return;
|
|
@@ -6114,9 +6318,9 @@ function registerMemoryReject(memory2) {
|
|
|
6114
6318
|
}
|
|
6115
6319
|
|
|
6116
6320
|
// src/commands/memory-rm.ts
|
|
6117
|
-
import { existsSync as
|
|
6321
|
+
import { existsSync as existsSync24 } from "fs";
|
|
6118
6322
|
import { unlink as unlink2 } from "fs/promises";
|
|
6119
|
-
import
|
|
6323
|
+
import path24 from "path";
|
|
6120
6324
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
6121
6325
|
import "commander";
|
|
6122
6326
|
import {
|
|
@@ -6129,7 +6333,7 @@ function registerMemoryRm(memory2) {
|
|
|
6129
6333
|
memory2.command("delete <id>").alias("rm").description("Delete a memory file (and its usage entry by default). Mirrors MCP mem_delete. Alias: rm").option("-y, --yes", "skip the confirmation prompt").option("--keep-usage", "do not remove the usage.json entry").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
6130
6334
|
const root = findProjectRoot19(opts.dir);
|
|
6131
6335
|
const paths = resolveHaivePaths18(root);
|
|
6132
|
-
if (!
|
|
6336
|
+
if (!existsSync24(paths.memoriesDir)) {
|
|
6133
6337
|
ui.error(`No .ai/memories at ${root}.`);
|
|
6134
6338
|
process.exitCode = 1;
|
|
6135
6339
|
return;
|
|
@@ -6141,7 +6345,7 @@ function registerMemoryRm(memory2) {
|
|
|
6141
6345
|
process.exitCode = 1;
|
|
6142
6346
|
return;
|
|
6143
6347
|
}
|
|
6144
|
-
const rel =
|
|
6348
|
+
const rel = path24.relative(root, found.filePath);
|
|
6145
6349
|
if (!opts.yes) {
|
|
6146
6350
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
6147
6351
|
const answer = (await rl.question(`Delete ${rel}? [y/N] `)).trim().toLowerCase();
|
|
@@ -6165,9 +6369,9 @@ function registerMemoryRm(memory2) {
|
|
|
6165
6369
|
}
|
|
6166
6370
|
|
|
6167
6371
|
// src/commands/memory-show.ts
|
|
6168
|
-
import { existsSync as
|
|
6372
|
+
import { existsSync as existsSync25 } from "fs";
|
|
6169
6373
|
import { readFile as readFile11 } from "fs/promises";
|
|
6170
|
-
import
|
|
6374
|
+
import path25 from "path";
|
|
6171
6375
|
import "commander";
|
|
6172
6376
|
import {
|
|
6173
6377
|
deriveConfidence,
|
|
@@ -6180,7 +6384,7 @@ function registerMemoryShow(memory2) {
|
|
|
6180
6384
|
memory2.command("get <id>").alias("show").description("Print a memory's frontmatter, body, and confidence/usage. Mirrors MCP mem_get. Alias: show").option("--raw", "print the raw file contents instead of a summary").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
6181
6385
|
const root = findProjectRoot20(opts.dir);
|
|
6182
6386
|
const paths = resolveHaivePaths19(root);
|
|
6183
|
-
if (!
|
|
6387
|
+
if (!existsSync25(paths.memoriesDir)) {
|
|
6184
6388
|
ui.error(`No .ai/memories at ${root}.`);
|
|
6185
6389
|
process.exitCode = 1;
|
|
6186
6390
|
return;
|
|
@@ -6209,7 +6413,7 @@ function registerMemoryShow(memory2) {
|
|
|
6209
6413
|
if (fm.verified_at) console.log(`${ui.dim("verified:")} ${fm.verified_at}`);
|
|
6210
6414
|
if (fm.stale_reason) console.log(`${ui.dim("stale:")} ${fm.stale_reason}`);
|
|
6211
6415
|
console.log(`${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`);
|
|
6212
|
-
console.log(`${ui.dim("file:")} ${
|
|
6416
|
+
console.log(`${ui.dim("file:")} ${path25.relative(root, found.filePath)}`);
|
|
6213
6417
|
if (fm.anchor.paths.length || fm.anchor.symbols.length) {
|
|
6214
6418
|
console.log(ui.dim("anchor:"));
|
|
6215
6419
|
if (fm.anchor.commit) console.log(` ${ui.dim("commit:")} ${fm.anchor.commit}`);
|
|
@@ -6224,8 +6428,8 @@ function registerMemoryShow(memory2) {
|
|
|
6224
6428
|
}
|
|
6225
6429
|
|
|
6226
6430
|
// src/commands/memory-stats.ts
|
|
6227
|
-
import { existsSync as
|
|
6228
|
-
import
|
|
6431
|
+
import { existsSync as existsSync26 } from "fs";
|
|
6432
|
+
import path26 from "path";
|
|
6229
6433
|
import "commander";
|
|
6230
6434
|
import {
|
|
6231
6435
|
deriveConfidence as deriveConfidence2,
|
|
@@ -6238,7 +6442,7 @@ function registerMemoryStats(memory2) {
|
|
|
6238
6442
|
memory2.command("stats").description("Show usage stats and confidence levels per memory").option("--id <id>", "show stats for a single memory id").option("--hot", "only unvalidated (draft/proposed) memories read often \u2014 promotion candidates (absorbed `memory hot`)").option("--threshold <n>", "with --hot: minimum read_count to qualify", "3").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
6239
6443
|
const root = findProjectRoot21(opts.dir);
|
|
6240
6444
|
const paths = resolveHaivePaths20(root);
|
|
6241
|
-
if (!
|
|
6445
|
+
if (!existsSync26(paths.memoriesDir)) {
|
|
6242
6446
|
ui.error(`No .ai/memories at ${root}. Run \`hivelore init\` first.`);
|
|
6243
6447
|
process.exitCode = 1;
|
|
6244
6448
|
return;
|
|
@@ -6269,13 +6473,13 @@ function registerMemoryStats(memory2) {
|
|
|
6269
6473
|
console.log(
|
|
6270
6474
|
` ${ui.dim("status:")} ${fm.status} ${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`
|
|
6271
6475
|
);
|
|
6272
|
-
console.log(` ${ui.dim(
|
|
6476
|
+
console.log(` ${ui.dim(path26.relative(root, filePath))}`);
|
|
6273
6477
|
}
|
|
6274
6478
|
});
|
|
6275
6479
|
}
|
|
6276
6480
|
|
|
6277
6481
|
// src/commands/memory-impact.ts
|
|
6278
|
-
import { existsSync as
|
|
6482
|
+
import { existsSync as existsSync27 } from "fs";
|
|
6279
6483
|
import "commander";
|
|
6280
6484
|
import {
|
|
6281
6485
|
compareImpact,
|
|
@@ -6292,7 +6496,7 @@ function registerMemoryImpact(memory2) {
|
|
|
6292
6496
|
).option("--id <id>", "show impact for a single memory id").option("--prune", "list only prune candidates (dead weight worth reviewing)", false).option("--tier <tier>", "filter to a tier: high | medium | low | dormant").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
6293
6497
|
const root = findProjectRoot22(opts.dir);
|
|
6294
6498
|
const paths = resolveHaivePaths21(root);
|
|
6295
|
-
if (!
|
|
6499
|
+
if (!existsSync27(paths.memoriesDir)) {
|
|
6296
6500
|
ui.error(`No .ai/memories at ${root}. Run \`hivelore init\` first.`);
|
|
6297
6501
|
process.exitCode = 1;
|
|
6298
6502
|
return;
|
|
@@ -6363,7 +6567,7 @@ function pad(value, width) {
|
|
|
6363
6567
|
}
|
|
6364
6568
|
|
|
6365
6569
|
// src/commands/memory-feedback.ts
|
|
6366
|
-
import { existsSync as
|
|
6570
|
+
import { existsSync as existsSync28 } from "fs";
|
|
6367
6571
|
import { writeFile as writeFile14 } from "fs/promises";
|
|
6368
6572
|
import "commander";
|
|
6369
6573
|
import {
|
|
@@ -6390,7 +6594,7 @@ function registerMemoryFeedback(memory2) {
|
|
|
6390
6594
|
}
|
|
6391
6595
|
const root = findProjectRoot23(opts.dir);
|
|
6392
6596
|
const paths = resolveHaivePaths22(root);
|
|
6393
|
-
if (!
|
|
6597
|
+
if (!existsSync28(paths.memoriesDir)) {
|
|
6394
6598
|
ui.error(`No .ai/memories at ${root}. Run \`hivelore init\` first.`);
|
|
6395
6599
|
process.exitCode = 1;
|
|
6396
6600
|
return;
|
|
@@ -6431,8 +6635,8 @@ function registerMemoryFeedback(memory2) {
|
|
|
6431
6635
|
|
|
6432
6636
|
// src/commands/memory-verify.ts
|
|
6433
6637
|
import { writeFile as writeFile15 } from "fs/promises";
|
|
6434
|
-
import { existsSync as
|
|
6435
|
-
import
|
|
6638
|
+
import { existsSync as existsSync29 } from "fs";
|
|
6639
|
+
import path27 from "path";
|
|
6436
6640
|
import "commander";
|
|
6437
6641
|
import {
|
|
6438
6642
|
findProjectRoot as findProjectRoot24,
|
|
@@ -6446,7 +6650,7 @@ function registerMemoryVerify(memory2) {
|
|
|
6446
6650
|
).option("--id <id>", "verify a single memory by id").option("--all", "verify every memory (default if --id is omitted)").option("--update", "write status=stale or status=validated back to disk").option("--json", "emit machine-readable JSON (for CI / agents)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
6447
6651
|
const root = findProjectRoot24(opts.dir);
|
|
6448
6652
|
const paths = resolveHaivePaths23(root);
|
|
6449
|
-
if (!
|
|
6653
|
+
if (!existsSync29(paths.memoriesDir)) {
|
|
6450
6654
|
if (opts.json) {
|
|
6451
6655
|
console.log(JSON.stringify({ error: "not-initialized", root }, null, 2));
|
|
6452
6656
|
} else {
|
|
@@ -6474,7 +6678,7 @@ function registerMemoryVerify(memory2) {
|
|
|
6474
6678
|
for (const { memory: mem, filePath } of targets) {
|
|
6475
6679
|
const result = await verifyAnchor2(mem, { projectRoot: root });
|
|
6476
6680
|
const isAnchored = mem.frontmatter.anchor.paths.length > 0 || mem.frontmatter.anchor.symbols.length > 0;
|
|
6477
|
-
const rel =
|
|
6681
|
+
const rel = path27.relative(root, filePath);
|
|
6478
6682
|
if (!isAnchored) {
|
|
6479
6683
|
anchorlessIds.push(mem.frontmatter.id);
|
|
6480
6684
|
entries.push({ id: mem.frontmatter.id, status: "anchorless", path: rel });
|
|
@@ -6567,7 +6771,7 @@ function applyVerification(mem, result) {
|
|
|
6567
6771
|
|
|
6568
6772
|
// src/commands/memory-import.ts
|
|
6569
6773
|
import { readFile as readFile12 } from "fs/promises";
|
|
6570
|
-
import { existsSync as
|
|
6774
|
+
import { existsSync as existsSync30 } from "fs";
|
|
6571
6775
|
import "commander";
|
|
6572
6776
|
import {
|
|
6573
6777
|
findProjectRoot as findProjectRoot25,
|
|
@@ -6584,12 +6788,12 @@ function registerMemoryImport(memory2) {
|
|
|
6584
6788
|
}
|
|
6585
6789
|
const root = findProjectRoot25(opts.dir);
|
|
6586
6790
|
const paths = resolveHaivePaths24(root);
|
|
6587
|
-
if (!
|
|
6791
|
+
if (!existsSync30(paths.haiveDir)) {
|
|
6588
6792
|
ui.error(`No .ai/ found at ${root}. Run \`hivelore init\` first.`);
|
|
6589
6793
|
process.exitCode = 1;
|
|
6590
6794
|
return;
|
|
6591
6795
|
}
|
|
6592
|
-
if (!
|
|
6796
|
+
if (!existsSync30(opts.from)) {
|
|
6593
6797
|
ui.error(`File not found: ${opts.from}`);
|
|
6594
6798
|
process.exitCode = 1;
|
|
6595
6799
|
return;
|
|
@@ -6622,9 +6826,9 @@ function registerMemoryImport(memory2) {
|
|
|
6622
6826
|
}
|
|
6623
6827
|
|
|
6624
6828
|
// src/commands/memory-digest.ts
|
|
6625
|
-
import { existsSync as
|
|
6829
|
+
import { existsSync as existsSync31 } from "fs";
|
|
6626
6830
|
import { writeFile as writeFile16 } from "fs/promises";
|
|
6627
|
-
import
|
|
6831
|
+
import path28 from "path";
|
|
6628
6832
|
import "commander";
|
|
6629
6833
|
import {
|
|
6630
6834
|
deriveConfidence as deriveConfidence3,
|
|
@@ -6647,7 +6851,7 @@ function registerMemoryDigest(program2) {
|
|
|
6647
6851
|
).option("--days <n>", "look-back window in days (default: 7)", "7").option("--scope <scope>", "personal | team | module | all (default: team)", "team").option("--out <file>", "write digest to a file instead of stdout").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
6648
6852
|
const root = findProjectRoot26(opts.dir);
|
|
6649
6853
|
const paths = resolveHaivePaths25(root);
|
|
6650
|
-
if (!
|
|
6854
|
+
if (!existsSync31(paths.memoriesDir)) {
|
|
6651
6855
|
ui.error("No .ai/memories found. Run `hivelore init` first.");
|
|
6652
6856
|
process.exitCode = 1;
|
|
6653
6857
|
return;
|
|
@@ -6719,7 +6923,7 @@ function registerMemoryDigest(program2) {
|
|
|
6719
6923
|
);
|
|
6720
6924
|
const digest = lines.join("\n");
|
|
6721
6925
|
if (opts.out) {
|
|
6722
|
-
const outPath =
|
|
6926
|
+
const outPath = path28.resolve(process.cwd(), opts.out);
|
|
6723
6927
|
await writeFile16(outPath, digest, "utf8");
|
|
6724
6928
|
ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
|
|
6725
6929
|
} else {
|
|
@@ -6730,9 +6934,9 @@ function registerMemoryDigest(program2) {
|
|
|
6730
6934
|
|
|
6731
6935
|
// src/commands/session-end.ts
|
|
6732
6936
|
import { writeFile as writeFile17, mkdir as mkdir11, readFile as readFile13, rm } from "fs/promises";
|
|
6733
|
-
import { existsSync as
|
|
6937
|
+
import { existsSync as existsSync32 } from "fs";
|
|
6734
6938
|
import { spawn as spawn2 } from "child_process";
|
|
6735
|
-
import
|
|
6939
|
+
import path29 from "path";
|
|
6736
6940
|
import { Option as Option2 } from "commander";
|
|
6737
6941
|
import {
|
|
6738
6942
|
buildFrontmatter as buildFrontmatter5,
|
|
@@ -6749,8 +6953,8 @@ import {
|
|
|
6749
6953
|
writeSessionHandoff
|
|
6750
6954
|
} from "@hivelore/core";
|
|
6751
6955
|
async function buildAutoRecap(paths) {
|
|
6752
|
-
const obsFile =
|
|
6753
|
-
if (!
|
|
6956
|
+
const obsFile = path29.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
6957
|
+
if (!existsSync32(obsFile)) return await buildGitAutoRecap(paths);
|
|
6754
6958
|
const raw = await readFile13(obsFile, "utf8").catch(() => "");
|
|
6755
6959
|
if (!raw.trim()) return await buildGitAutoRecap(paths);
|
|
6756
6960
|
const lines = raw.split("\n").filter(Boolean);
|
|
@@ -6918,8 +7122,8 @@ function runGit(cwd, args) {
|
|
|
6918
7122
|
});
|
|
6919
7123
|
}
|
|
6920
7124
|
async function observationStart(paths) {
|
|
6921
|
-
const obsFile =
|
|
6922
|
-
if (!
|
|
7125
|
+
const obsFile = path29.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
7126
|
+
if (!existsSync32(obsFile)) return null;
|
|
6923
7127
|
const raw = await readFile13(obsFile, "utf8").catch(() => "");
|
|
6924
7128
|
let first = null;
|
|
6925
7129
|
for (const line of raw.split("\n")) {
|
|
@@ -6935,7 +7139,7 @@ async function observationStart(paths) {
|
|
|
6935
7139
|
}
|
|
6936
7140
|
async function printCaughtForYou(paths, since, quiet) {
|
|
6937
7141
|
if (quiet) return;
|
|
6938
|
-
const memories =
|
|
7142
|
+
const memories = existsSync32(paths.memoriesDir) ? await loadMemoriesFromDir10(paths.memoriesDir) : [];
|
|
6939
7143
|
const usage = await loadUsageIndex11(paths);
|
|
6940
7144
|
const events = await loadPreventionEvents(paths);
|
|
6941
7145
|
const summary = summarizeCaughtForYou(events, memories, usage, {
|
|
@@ -6973,7 +7177,7 @@ function registerSessionEnd(session2) {
|
|
|
6973
7177
|
).option("--goal <text>", "what you were trying to accomplish (1\u20132 sentences)").option("--accomplished <text>", "what was actually done (bullet list recommended)").addOption(new Option2("--summary <text>", "alias for --accomplished").hideHelp()).option("--discoveries <text>", "bugs, surprises, or inconsistencies found during this session").option("--files <csv>", "key files touched, comma-separated (used as anchor for staleness detection)").option("--next <text>", "what should happen next (for the next session or a teammate)").option("--scope <scope>", "personal | team | module (default: personal)", "personal").option("--module <name>", "module name (required when scope=module)").option("--auto", "synthesize the recap from .ai/.cache/observations.jsonl (used by Claude Code SessionEnd hook)").option("--quiet", "suppress non-error output (for hook use)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
6974
7178
|
const root = findProjectRoot27(opts.dir);
|
|
6975
7179
|
const paths = resolveHaivePaths26(root);
|
|
6976
|
-
if (!
|
|
7180
|
+
if (!existsSync32(paths.haiveDir)) {
|
|
6977
7181
|
if (opts.auto || opts.quiet) return;
|
|
6978
7182
|
ui.error(`No .ai/ found at ${root}. Run \`hivelore init\` first.`);
|
|
6979
7183
|
process.exitCode = 1;
|
|
@@ -7007,15 +7211,15 @@ function registerSessionEnd(session2) {
|
|
|
7007
7211
|
});
|
|
7008
7212
|
const topic = recapTopic(scope, opts.module);
|
|
7009
7213
|
const filesTouched = parseCsv5(resolvedFiles).map((p) => normalizeAnchorPath(root, p));
|
|
7010
|
-
const missingPaths = filesTouched.filter((p) => !
|
|
7214
|
+
const missingPaths = filesTouched.filter((p) => !existsSync32(path29.resolve(root, p)));
|
|
7011
7215
|
if (missingPaths.length > 0 && !opts.quiet) {
|
|
7012
7216
|
ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
|
|
7013
7217
|
for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
|
|
7014
7218
|
}
|
|
7015
7219
|
const cleanupObservations = async () => {
|
|
7016
7220
|
if (!opts.auto) return;
|
|
7017
|
-
const obsFile =
|
|
7018
|
-
if (
|
|
7221
|
+
const obsFile = path29.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
7222
|
+
if (existsSync32(obsFile)) await rm(obsFile).catch(() => {
|
|
7019
7223
|
});
|
|
7020
7224
|
};
|
|
7021
7225
|
const config = await loadConfig6(paths);
|
|
@@ -7039,7 +7243,7 @@ function registerSessionEnd(session2) {
|
|
|
7039
7243
|
}
|
|
7040
7244
|
return;
|
|
7041
7245
|
}
|
|
7042
|
-
if (
|
|
7246
|
+
if (existsSync32(paths.memoriesDir)) {
|
|
7043
7247
|
const existing = await loadMemoriesFromDir10(paths.memoriesDir);
|
|
7044
7248
|
const topicMatch = existing.find(
|
|
7045
7249
|
({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
|
|
@@ -7060,7 +7264,7 @@ function registerSessionEnd(session2) {
|
|
|
7060
7264
|
await cleanupObservations();
|
|
7061
7265
|
if (!opts.quiet) {
|
|
7062
7266
|
ui.success(`Session recap updated (revision #${revisionCount})`);
|
|
7063
|
-
ui.info(`id=${fm.id} file=${
|
|
7267
|
+
ui.info(`id=${fm.id} file=${path29.relative(root, topicMatch.filePath)}`);
|
|
7064
7268
|
await printCaughtForYou(paths, caughtSince, opts.quiet);
|
|
7065
7269
|
ui.info("Tip: `hivelore stats --export-report` generates a usage JSON suitable for dashboards.");
|
|
7066
7270
|
}
|
|
@@ -7078,12 +7282,12 @@ function registerSessionEnd(session2) {
|
|
|
7078
7282
|
status: "validated"
|
|
7079
7283
|
});
|
|
7080
7284
|
const file = memoryFilePath5(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
7081
|
-
await mkdir11(
|
|
7285
|
+
await mkdir11(path29.dirname(file), { recursive: true });
|
|
7082
7286
|
await writeFile17(file, serializeMemory12({ frontmatter, body }), "utf8");
|
|
7083
7287
|
await cleanupObservations();
|
|
7084
7288
|
if (!opts.quiet) {
|
|
7085
7289
|
ui.success(`Session recap created`);
|
|
7086
|
-
ui.info(`id=${frontmatter.id} scope=${scope} file=${
|
|
7290
|
+
ui.info(`id=${frontmatter.id} scope=${scope} file=${path29.relative(root, file)}`);
|
|
7087
7291
|
await printCaughtForYou(paths, caughtSince, opts.quiet);
|
|
7088
7292
|
ui.info("Next session: call `get_briefing` \u2014 the recap will be surfaced automatically.");
|
|
7089
7293
|
ui.info("Tip: export a local MCP usage rollup with `hivelore stats --export-report .ai/tool-usage-roi-report.json`.");
|
|
@@ -7096,29 +7300,47 @@ function parseCsv5(value) {
|
|
|
7096
7300
|
}
|
|
7097
7301
|
function normalizeAnchorPath(root, filePath) {
|
|
7098
7302
|
if (!filePath) return filePath;
|
|
7099
|
-
if (!
|
|
7100
|
-
const rel =
|
|
7303
|
+
if (!path29.isAbsolute(filePath)) return filePath;
|
|
7304
|
+
const rel = path29.relative(root, filePath);
|
|
7101
7305
|
if (rel.startsWith("..")) return filePath;
|
|
7102
7306
|
return rel;
|
|
7103
7307
|
}
|
|
7104
7308
|
|
|
7105
7309
|
// src/commands/stats.ts
|
|
7106
7310
|
import "commander";
|
|
7107
|
-
import { existsSync as
|
|
7311
|
+
import { existsSync as existsSync33 } from "fs";
|
|
7108
7312
|
import { mkdir as mkdir12, writeFile as writeFile18 } from "fs/promises";
|
|
7109
|
-
import
|
|
7313
|
+
import path30 from "path";
|
|
7110
7314
|
import {
|
|
7111
7315
|
aggregateUsage,
|
|
7316
|
+
buildPreventionReceipt,
|
|
7317
|
+
renderPreventionReceipt,
|
|
7112
7318
|
findProjectRoot as findProjectRoot28,
|
|
7113
7319
|
loadMemoriesFromDir as loadMemoriesFromDir11,
|
|
7114
7320
|
loadUsageIndex as loadUsageIndex12,
|
|
7321
|
+
loadPreventionEvents as loadPreventionEvents2,
|
|
7115
7322
|
parseSince,
|
|
7116
7323
|
readUsageEvents,
|
|
7117
7324
|
resolveHaivePaths as resolveHaivePaths27,
|
|
7118
7325
|
usageLogSize
|
|
7119
7326
|
} from "@hivelore/core";
|
|
7120
7327
|
function registerStats(program2) {
|
|
7121
|
-
program2.command("stats").description("Show MCP tool-usage stats
|
|
7328
|
+
const stats = program2.command("stats").description("Show MCP tool-usage stats and prevention receipts.");
|
|
7329
|
+
stats.command("receipt").description("Show documented mistakes refused by the gate over a time window").addHelpText("after", "\nParent options also apply: --since <window> (default 7d here), --json, --dir <dir>.").action(async () => {
|
|
7330
|
+
const opts = stats.opts();
|
|
7331
|
+
const root = findProjectRoot28(opts.dir);
|
|
7332
|
+
const paths = resolveHaivePaths27(root);
|
|
7333
|
+
const sinceRaw = stats.getOptionValueSource("since") === "default" ? "7d" : opts.since ?? "7d";
|
|
7334
|
+
const since = parseSince(sinceRaw) ?? new Date(Date.now() - 7 * 864e5);
|
|
7335
|
+
const [events, usage, memories] = await Promise.all([
|
|
7336
|
+
loadPreventionEvents2(paths),
|
|
7337
|
+
loadUsageIndex12(paths),
|
|
7338
|
+
existsSync33(paths.memoriesDir) ? loadMemoriesFromDir11(paths.memoriesDir) : Promise.resolve([])
|
|
7339
|
+
]);
|
|
7340
|
+
const receipt = buildPreventionReceipt(events, memories, usage, { since });
|
|
7341
|
+
console.log(opts.json ? JSON.stringify(receipt, null, 2) : renderPreventionReceipt(receipt));
|
|
7342
|
+
});
|
|
7343
|
+
stats.option("--since <window>", "ISO date or relative (e.g. '7d', '24h', '30m')", "30d").option("--json", "emit JSON instead of human-readable output", false).option("--memory-hits", "show top-read memories (which mems are actually being used)", false).option(
|
|
7122
7344
|
"--export-report <path>",
|
|
7123
7345
|
"write a JSON rollup (tools + briefing counts + heuristic ROI hints). Parent dirs are created if needed.",
|
|
7124
7346
|
void 0
|
|
@@ -7178,11 +7400,11 @@ function registerStats(program2) {
|
|
|
7178
7400
|
});
|
|
7179
7401
|
}
|
|
7180
7402
|
async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
7181
|
-
const outAbs =
|
|
7403
|
+
const outAbs = path30.isAbsolute(outRelative) ? path30.resolve(outRelative) : path30.resolve(root, outRelative);
|
|
7182
7404
|
const size = await usageLogSize(paths);
|
|
7183
7405
|
let events = await readUsageEvents(paths);
|
|
7184
7406
|
let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
|
|
7185
|
-
if (
|
|
7407
|
+
if (existsSync33(paths.memoriesDir)) {
|
|
7186
7408
|
const mems = await loadMemoriesFromDir11(paths.memoriesDir);
|
|
7187
7409
|
for (const { memory: memory2 } of mems) {
|
|
7188
7410
|
const fm = memory2.frontmatter;
|
|
@@ -7212,7 +7434,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
7212
7434
|
ui.warn("Usage log missing or empty \u2014 report still written with partial data.");
|
|
7213
7435
|
events = [];
|
|
7214
7436
|
}
|
|
7215
|
-
await mkdir12(
|
|
7437
|
+
await mkdir12(path30.dirname(outAbs), { recursive: true });
|
|
7216
7438
|
const payload = {
|
|
7217
7439
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7218
7440
|
project_root: root,
|
|
@@ -7401,9 +7623,9 @@ function summarize(name, t0, payload, notes) {
|
|
|
7401
7623
|
}
|
|
7402
7624
|
|
|
7403
7625
|
// src/commands/benchmark.ts
|
|
7404
|
-
import { existsSync as
|
|
7626
|
+
import { existsSync as existsSync34 } from "fs";
|
|
7405
7627
|
import { readdir as readdir3, readFile as readFile14, writeFile as writeFile19 } from "fs/promises";
|
|
7406
|
-
import
|
|
7628
|
+
import path31 from "path";
|
|
7407
7629
|
import "commander";
|
|
7408
7630
|
import { estimateTokens as estimateTokens2, findProjectRoot as findProjectRoot30 } from "@hivelore/core";
|
|
7409
7631
|
function registerBenchmark(program2) {
|
|
@@ -7418,9 +7640,9 @@ function registerBenchmark(program2) {
|
|
|
7418
7640
|
}
|
|
7419
7641
|
const markdown = renderMarkdown(root, summary, rows);
|
|
7420
7642
|
if (opts.out) {
|
|
7421
|
-
const outFile =
|
|
7643
|
+
const outFile = path31.isAbsolute(opts.out) ? opts.out : path31.join(root, opts.out);
|
|
7422
7644
|
await writeFile19(outFile, markdown, "utf8");
|
|
7423
|
-
ui.success(`wrote ${
|
|
7645
|
+
ui.success(`wrote ${path31.relative(process.cwd(), outFile)}`);
|
|
7424
7646
|
return;
|
|
7425
7647
|
}
|
|
7426
7648
|
console.log(markdown);
|
|
@@ -7444,19 +7666,19 @@ function registerBenchmark(program2) {
|
|
|
7444
7666
|
}
|
|
7445
7667
|
function resolveBenchmarkRoot(dir) {
|
|
7446
7668
|
const candidate = dir ?? "benchmarks/agent-benchmark";
|
|
7447
|
-
if (
|
|
7669
|
+
if (path31.isAbsolute(candidate)) return candidate;
|
|
7448
7670
|
const projectRoot = findProjectRoot30(process.cwd());
|
|
7449
|
-
return
|
|
7671
|
+
return path31.join(projectRoot, candidate);
|
|
7450
7672
|
}
|
|
7451
7673
|
async function collectRows(root) {
|
|
7452
|
-
if (!
|
|
7674
|
+
if (!existsSync34(root)) throw new Error(`Benchmark directory not found: ${root}`);
|
|
7453
7675
|
const entries = await readdir3(root, { withFileTypes: true });
|
|
7454
7676
|
const rows = [];
|
|
7455
7677
|
for (const entry of entries) {
|
|
7456
7678
|
if (!entry.isDirectory()) continue;
|
|
7457
|
-
const fixtureDir =
|
|
7458
|
-
const reportFile =
|
|
7459
|
-
if (!
|
|
7679
|
+
const fixtureDir = path31.join(root, entry.name);
|
|
7680
|
+
const reportFile = path31.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
|
|
7681
|
+
if (!existsSync34(reportFile)) continue;
|
|
7460
7682
|
const report = await readFile14(reportFile, "utf8");
|
|
7461
7683
|
rows.push(parseAgentReport(entry.name, report));
|
|
7462
7684
|
}
|
|
@@ -7549,8 +7771,8 @@ function escapeRegExp(value) {
|
|
|
7549
7771
|
|
|
7550
7772
|
// src/commands/eval.ts
|
|
7551
7773
|
import { mkdir as mkdir13, readFile as readFile15, writeFile as writeFile20 } from "fs/promises";
|
|
7552
|
-
import { existsSync as
|
|
7553
|
-
import
|
|
7774
|
+
import { existsSync as existsSync35 } from "fs";
|
|
7775
|
+
import path32 from "path";
|
|
7554
7776
|
import "commander";
|
|
7555
7777
|
import {
|
|
7556
7778
|
aggregateRetrieval,
|
|
@@ -7564,7 +7786,7 @@ import {
|
|
|
7564
7786
|
findProjectRoot as findProjectRoot31,
|
|
7565
7787
|
loadConfig as loadConfig7,
|
|
7566
7788
|
loadEvalHistory,
|
|
7567
|
-
loadPreventionEvents as
|
|
7789
|
+
loadPreventionEvents as loadPreventionEvents3,
|
|
7568
7790
|
loadUsageIndex as loadUsageIndex13,
|
|
7569
7791
|
overallScore,
|
|
7570
7792
|
resolveHaivePaths as resolveHaivePaths29,
|
|
@@ -7578,7 +7800,7 @@ function registerEval(program2) {
|
|
|
7578
7800
|
).option("--spec <file>", "JSON eval spec ({ retrieval: [...], sensors: [...] })").option("--semantic-only", "self-eval probes by title alone (no anchor files) \u2014 harder retrieval", false).option("-k, --top <n>", "briefing top-k considered a hit", "8").option("--json", "emit JSON", false).option("--out <file>", "write a Markdown report").option("--fail-under <score>", "exit non-zero if the overall score is below this (0\u2013100) \u2014 for CI gates").option("--fail-under-catch-rate <pct>", "exit non-zero if sensor catch-rate is below this percentage").option("--fail-under-gate-precision <pct>", "exit non-zero if gate precision is below this percentage").option("--baseline", "save this run as the baseline (.ai/eval/baseline.json) for future --compare", false).option("--compare", "diff this run against the saved baseline and print the delta", false).option("--baseline-file <path>", "baseline file to read/write (default: .ai/eval/baseline.json)").option("--fail-on-regression", "with --compare, exit non-zero if the score dropped vs the baseline", false).option("--regression-gate", "CI-safe gate: compare against the baseline IF one exists (fail on regression), else no-op", false).option("--record", "append this run's score to .ai/.cache/eval-history.jsonl (trend the harness over time)", false).option("--trend", "print the recorded score trend (sparkline + latest/best/delta) and exit", false).option("--ref <ref>", "version/commit label stored with a --record run").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
7579
7801
|
const root = findProjectRoot31(opts.dir);
|
|
7580
7802
|
const paths = resolveHaivePaths29(root);
|
|
7581
|
-
if (!
|
|
7803
|
+
if (!existsSync35(paths.memoriesDir)) {
|
|
7582
7804
|
ui.error(`No .ai/memories at ${root}. Run \`hivelore init\` first.`);
|
|
7583
7805
|
process.exitCode = 1;
|
|
7584
7806
|
return;
|
|
@@ -7631,7 +7853,7 @@ function registerEval(program2) {
|
|
|
7631
7853
|
const authoredScore = resolvedSpec.authored > 0 && resolvedSpec.synthesized > 0 && (authoredRetrievalAgg || sensorAgg) ? overallScore(authoredRetrievalAgg, sensorAgg) : null;
|
|
7632
7854
|
const [usage, preventionEvents, config] = await Promise.all([
|
|
7633
7855
|
loadUsageIndex13(paths),
|
|
7634
|
-
|
|
7856
|
+
loadPreventionEvents3(paths),
|
|
7635
7857
|
loadConfig7(paths)
|
|
7636
7858
|
]);
|
|
7637
7859
|
const gatePrecision = computeGatePrecision(
|
|
@@ -7650,7 +7872,7 @@ function registerEval(program2) {
|
|
|
7650
7872
|
});
|
|
7651
7873
|
if (!opts.json) ui.success(`Recorded eval score ${report.score}/100 to history.`);
|
|
7652
7874
|
}
|
|
7653
|
-
const baselineFile = opts.baselineFile ?
|
|
7875
|
+
const baselineFile = opts.baselineFile ? path32.isAbsolute(opts.baselineFile) ? opts.baselineFile : path32.join(root, opts.baselineFile) : path32.join(root, ".ai", "eval", "baseline.json");
|
|
7654
7876
|
if (opts.baseline) {
|
|
7655
7877
|
const snapshot = {
|
|
7656
7878
|
saved_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -7659,18 +7881,18 @@ function registerEval(program2) {
|
|
|
7659
7881
|
report,
|
|
7660
7882
|
gate_precision: gatePrecision
|
|
7661
7883
|
};
|
|
7662
|
-
await mkdir13(
|
|
7884
|
+
await mkdir13(path32.dirname(baselineFile), { recursive: true });
|
|
7663
7885
|
await writeFile20(baselineFile, JSON.stringify(snapshot, null, 2), "utf8");
|
|
7664
|
-
if (!opts.json) ui.success(`Saved baseline (score ${report.score}/100) \u2192 ${
|
|
7886
|
+
if (!opts.json) ui.success(`Saved baseline (score ${report.score}/100) \u2192 ${path32.relative(root, baselineFile)}`);
|
|
7665
7887
|
}
|
|
7666
7888
|
let delta = null;
|
|
7667
7889
|
let gateDelta = null;
|
|
7668
7890
|
if (opts.compare || opts.regressionGate) {
|
|
7669
|
-
if (!
|
|
7891
|
+
if (!existsSync35(baselineFile)) {
|
|
7670
7892
|
if (opts.regressionGate) {
|
|
7671
|
-
if (!opts.json) ui.info(`No baseline at ${
|
|
7893
|
+
if (!opts.json) ui.info(`No baseline at ${path32.relative(root, baselineFile)} \u2014 regression gate skipped. Run \`hivelore eval --baseline\` to enable it.`);
|
|
7672
7894
|
} else {
|
|
7673
|
-
ui.error(`No baseline at ${
|
|
7895
|
+
ui.error(`No baseline at ${path32.relative(root, baselineFile)}. Run \`hivelore eval --baseline\` first.`);
|
|
7674
7896
|
process.exitCode = 1;
|
|
7675
7897
|
return;
|
|
7676
7898
|
}
|
|
@@ -7713,9 +7935,9 @@ function registerEval(program2) {
|
|
|
7713
7935
|
}
|
|
7714
7936
|
const md = renderMarkdown2(root, k, resolvedSpec, report, gatePrecision, authoredScore);
|
|
7715
7937
|
if (opts.out) {
|
|
7716
|
-
const outFile =
|
|
7938
|
+
const outFile = path32.isAbsolute(opts.out) ? opts.out : path32.join(root, opts.out);
|
|
7717
7939
|
await writeFile20(outFile, md, "utf8");
|
|
7718
|
-
ui.success(`wrote ${
|
|
7940
|
+
ui.success(`wrote ${path32.relative(process.cwd(), outFile)}`);
|
|
7719
7941
|
} else {
|
|
7720
7942
|
console.log(md);
|
|
7721
7943
|
}
|
|
@@ -7799,13 +8021,13 @@ function countCases(spec) {
|
|
|
7799
8021
|
}
|
|
7800
8022
|
async function resolveSpec(opts, root, memoriesDir) {
|
|
7801
8023
|
if (opts.spec) {
|
|
7802
|
-
const file =
|
|
8024
|
+
const file = path32.resolve(opts.spec);
|
|
7803
8025
|
const raw = await readFile15(file, "utf8");
|
|
7804
8026
|
const spec = JSON.parse(raw);
|
|
7805
8027
|
return { spec, source: file, synthesized: 0, authored: countCases(spec) };
|
|
7806
8028
|
}
|
|
7807
|
-
const defaultSpec =
|
|
7808
|
-
if (
|
|
8029
|
+
const defaultSpec = path32.join(root, ".ai", "eval", "spec.json");
|
|
8030
|
+
if (existsSync35(defaultSpec)) {
|
|
7809
8031
|
const raw = await readFile15(defaultSpec, "utf8");
|
|
7810
8032
|
const explicit = JSON.parse(raw);
|
|
7811
8033
|
const memories2 = await loadMemoriesFromDir(memoriesDir);
|
|
@@ -7932,8 +8154,8 @@ function renderMarkdown2(root, k, resolved, report, gatePrecision, authoredScore
|
|
|
7932
8154
|
|
|
7933
8155
|
// src/commands/memory-suggest.ts
|
|
7934
8156
|
import { mkdir as mkdir14, writeFile as writeFile21 } from "fs/promises";
|
|
7935
|
-
import { existsSync as
|
|
7936
|
-
import
|
|
8157
|
+
import { existsSync as existsSync36 } from "fs";
|
|
8158
|
+
import path33 from "path";
|
|
7937
8159
|
import "commander";
|
|
7938
8160
|
import {
|
|
7939
8161
|
MemoryTypeSchema,
|
|
@@ -8023,7 +8245,7 @@ function registerMemorySuggest(memory2) {
|
|
|
8023
8245
|
}
|
|
8024
8246
|
const created = [];
|
|
8025
8247
|
const skipped = [];
|
|
8026
|
-
const existing =
|
|
8248
|
+
const existing = existsSync36(paths.memoriesDir) ? await loadMemoriesFromDir12(paths.memoriesDir) : [];
|
|
8027
8249
|
for (const s of top) {
|
|
8028
8250
|
const slug = slugify2(s.query);
|
|
8029
8251
|
if (!slug) {
|
|
@@ -8046,13 +8268,13 @@ function registerMemorySuggest(memory2) {
|
|
|
8046
8268
|
});
|
|
8047
8269
|
const body = renderTemplate(s, fm.id, status);
|
|
8048
8270
|
const file = memoryFilePath6(paths, fm.scope, fm.id, fm.module);
|
|
8049
|
-
await mkdir14(
|
|
8050
|
-
if (
|
|
8051
|
-
skipped.push({ query: s.query, reason: `file already exists at ${
|
|
8271
|
+
await mkdir14(path33.dirname(file), { recursive: true });
|
|
8272
|
+
if (existsSync36(file)) {
|
|
8273
|
+
skipped.push({ query: s.query, reason: `file already exists at ${path33.relative(root, file)}` });
|
|
8052
8274
|
continue;
|
|
8053
8275
|
}
|
|
8054
8276
|
await writeFile21(file, serializeMemory13({ frontmatter: fm, body }), "utf8");
|
|
8055
|
-
created.push({ id: fm.id, file:
|
|
8277
|
+
created.push({ id: fm.id, file: path33.relative(root, file), query: s.query });
|
|
8056
8278
|
}
|
|
8057
8279
|
if (opts.json) {
|
|
8058
8280
|
console.log(JSON.stringify({ created, skipped }, null, 2));
|
|
@@ -8150,8 +8372,8 @@ function truncate2(text, max) {
|
|
|
8150
8372
|
}
|
|
8151
8373
|
|
|
8152
8374
|
// src/commands/memory-conflict-candidates.ts
|
|
8153
|
-
import { existsSync as
|
|
8154
|
-
import
|
|
8375
|
+
import { existsSync as existsSync37 } from "fs";
|
|
8376
|
+
import path34 from "path";
|
|
8155
8377
|
import "commander";
|
|
8156
8378
|
import {
|
|
8157
8379
|
findLexicalConflictPairs,
|
|
@@ -8187,9 +8409,9 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
8187
8409
|
});
|
|
8188
8410
|
}
|
|
8189
8411
|
async function runConflictCandidates(opts) {
|
|
8190
|
-
const root =
|
|
8412
|
+
const root = path34.resolve(opts.dir ?? process.cwd());
|
|
8191
8413
|
const paths = resolveHaivePaths31(findProjectRoot33(root));
|
|
8192
|
-
if (!
|
|
8414
|
+
if (!existsSync37(paths.memoriesDir)) {
|
|
8193
8415
|
ui.error("No memories \u2014 run `hivelore init`.");
|
|
8194
8416
|
process.exitCode = 1;
|
|
8195
8417
|
return;
|
|
@@ -8223,9 +8445,9 @@ async function runConflictCandidates(opts) {
|
|
|
8223
8445
|
}
|
|
8224
8446
|
|
|
8225
8447
|
// src/commands/memory-archive.ts
|
|
8226
|
-
import { existsSync as
|
|
8448
|
+
import { existsSync as existsSync38 } from "fs";
|
|
8227
8449
|
import { writeFile as writeFile22 } from "fs/promises";
|
|
8228
|
-
import
|
|
8450
|
+
import path35 from "path";
|
|
8229
8451
|
import "commander";
|
|
8230
8452
|
import {
|
|
8231
8453
|
findProjectRoot as findProjectRoot34,
|
|
@@ -8244,7 +8466,7 @@ function registerMemoryArchive(memory2) {
|
|
|
8244
8466
|
).option("--since <window>", "minimum age since last read (e.g. '180d', '6m'). Default: enforcement.decayAfterDays or 180d").option("--type <type>", "limit to a memory type (default 'attempt'). Pass 'all' to scan all types.", "attempt").option("--unread", "decay by unread-age ALONE (ignore anchor status) \u2014 more aggressive corpus hygiene", false).option("--apply", "actually rewrite files (default: dry run)", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
8245
8467
|
const root = findProjectRoot34(opts.dir);
|
|
8246
8468
|
const paths = resolveHaivePaths32(root);
|
|
8247
|
-
if (!
|
|
8469
|
+
if (!existsSync38(paths.memoriesDir)) {
|
|
8248
8470
|
ui.error(`No .ai/memories at ${root}. Run \`hivelore init\` first.`);
|
|
8249
8471
|
process.exitCode = 1;
|
|
8250
8472
|
return;
|
|
@@ -8269,7 +8491,7 @@ function registerMemoryArchive(memory2) {
|
|
|
8269
8491
|
if (fm.status === "deprecated" || fm.status === "rejected") continue;
|
|
8270
8492
|
const retired = retirementSignal2(fm, mem.body);
|
|
8271
8493
|
const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
|
|
8272
|
-
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !
|
|
8494
|
+
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync38(path35.join(paths.root, p)));
|
|
8273
8495
|
const isAnchorless = !hasAnyAnchor;
|
|
8274
8496
|
if (!retired.retired && !opts.unread && !isAnchorless && !allPathsGone) continue;
|
|
8275
8497
|
const u = getUsage8(usage, fm.id);
|
|
@@ -8344,13 +8566,14 @@ function parseDays(input) {
|
|
|
8344
8566
|
}
|
|
8345
8567
|
|
|
8346
8568
|
// src/commands/doctor.ts
|
|
8347
|
-
import { existsSync as
|
|
8569
|
+
import { existsSync as existsSync39, statSync as statSync2 } from "fs";
|
|
8348
8570
|
import { readFile as readFile16, stat, writeFile as writeFile23 } from "fs/promises";
|
|
8349
|
-
import
|
|
8571
|
+
import path36 from "path";
|
|
8350
8572
|
import { execFileSync, execSync } from "child_process";
|
|
8351
8573
|
import "commander";
|
|
8352
8574
|
import {
|
|
8353
8575
|
codeMapPath as codeMapPath2,
|
|
8576
|
+
assessSensorHealth as assessSensorHealth2,
|
|
8354
8577
|
countSourceFilesOnDisk,
|
|
8355
8578
|
extractReferencedPaths,
|
|
8356
8579
|
sensorPatternBrittleness,
|
|
@@ -8360,7 +8583,8 @@ import {
|
|
|
8360
8583
|
isStackPackSeed as isStackPackSeed2,
|
|
8361
8584
|
loadCodeMap as loadCodeMap6,
|
|
8362
8585
|
loadConfig as loadConfig10,
|
|
8363
|
-
|
|
8586
|
+
loadMemoriesFromDirDetailed,
|
|
8587
|
+
loadSensorLedger as loadSensorLedger2,
|
|
8364
8588
|
loadUsageIndex as loadUsageIndex15,
|
|
8365
8589
|
readUsageEvents as readUsageEvents3,
|
|
8366
8590
|
resolveHaivePaths as resolveHaivePaths33
|
|
@@ -8375,7 +8599,7 @@ function registerDoctor(program2) {
|
|
|
8375
8599
|
const findings = [];
|
|
8376
8600
|
const repairs = [];
|
|
8377
8601
|
const config = await loadConfig10(paths);
|
|
8378
|
-
if (!
|
|
8602
|
+
if (!existsSync39(paths.haiveDir)) {
|
|
8379
8603
|
if (opts.json) {
|
|
8380
8604
|
console.log(JSON.stringify({
|
|
8381
8605
|
initialized: false,
|
|
@@ -8400,7 +8624,7 @@ function registerDoctor(program2) {
|
|
|
8400
8624
|
})
|
|
8401
8625
|
);
|
|
8402
8626
|
}
|
|
8403
|
-
if (!
|
|
8627
|
+
if (!existsSync39(paths.projectContext)) {
|
|
8404
8628
|
findings.push({
|
|
8405
8629
|
severity: "warn",
|
|
8406
8630
|
code: "no-project-context",
|
|
@@ -8420,7 +8644,7 @@ function registerDoctor(program2) {
|
|
|
8420
8644
|
});
|
|
8421
8645
|
} else {
|
|
8422
8646
|
const referenced = extractReferencedPaths(content);
|
|
8423
|
-
const missing = referenced.filter((p) => !
|
|
8647
|
+
const missing = referenced.filter((p) => !existsSync39(path36.resolve(root, p)));
|
|
8424
8648
|
const grounded = referenced.length - missing.length;
|
|
8425
8649
|
if (referenced.length >= 3 && grounded / referenced.length < 0.5) {
|
|
8426
8650
|
findings.push({
|
|
@@ -8442,13 +8666,23 @@ function registerDoctor(program2) {
|
|
|
8442
8666
|
});
|
|
8443
8667
|
}
|
|
8444
8668
|
}
|
|
8445
|
-
const
|
|
8669
|
+
const memoriesDetailed = existsSync39(paths.memoriesDir) ? await loadMemoriesFromDirDetailed(paths.memoriesDir) : { loaded: [], invalid: [] };
|
|
8670
|
+
const memories = memoriesDetailed.loaded;
|
|
8446
8671
|
const now = Date.now();
|
|
8672
|
+
if (memoriesDetailed.invalid.length > 0) {
|
|
8673
|
+
const listed = memoriesDetailed.invalid.slice(0, 5).map((f) => `${path36.relative(root, f.filePath)} (${f.error})`).join("; ");
|
|
8674
|
+
findings.push({
|
|
8675
|
+
severity: "warn",
|
|
8676
|
+
code: "invalid-memory-files",
|
|
8677
|
+
message: `${memoriesDetailed.invalid.length} memory file(s) failed to parse and are INVISIBLE to briefings and the gate: ${listed}`,
|
|
8678
|
+
fix: "Fix the frontmatter (or delete the file) \u2014 a corrupt memory is a silently lost team lesson."
|
|
8679
|
+
});
|
|
8680
|
+
}
|
|
8447
8681
|
if (memories.length === 0) {
|
|
8448
8682
|
findings.push({
|
|
8449
8683
|
severity: "info",
|
|
8450
8684
|
code: "no-memories",
|
|
8451
|
-
message: "No memories yet. Capture knowledge as agents work via mem_save /
|
|
8685
|
+
message: "No memories yet. Capture knowledge as agents work via mem_save / mem_tried."
|
|
8452
8686
|
});
|
|
8453
8687
|
} else {
|
|
8454
8688
|
const usage = await loadUsageIndex15(paths);
|
|
@@ -8458,7 +8692,7 @@ function registerDoctor(program2) {
|
|
|
8458
8692
|
severity: "warn",
|
|
8459
8693
|
code: "stale-memories",
|
|
8460
8694
|
message: `${stale.length} memor${stale.length === 1 ? "y" : "ies"} marked stale (anchored code drifted).`,
|
|
8461
|
-
fix: "hivelore memory verify --update # re-check anchors\
|
|
8695
|
+
fix: "hivelore memory verify --update # re-check anchors\nhivelore memory update <id> # manually refresh body"
|
|
8462
8696
|
});
|
|
8463
8697
|
}
|
|
8464
8698
|
const proposed = memories.filter((m) => m.memory.frontmatter.status === "proposed");
|
|
@@ -8546,6 +8780,18 @@ function registerDoctor(program2) {
|
|
|
8546
8780
|
});
|
|
8547
8781
|
}
|
|
8548
8782
|
const sensorMemories = memories.filter((m) => m.memory.frontmatter.sensor);
|
|
8783
|
+
const gateMissDrafts = memories.filter(
|
|
8784
|
+
(m) => m.memory.frontmatter.status === "proposed" && m.memory.frontmatter.tags.includes("gate-miss")
|
|
8785
|
+
);
|
|
8786
|
+
if (gateMissDrafts.length > 0) {
|
|
8787
|
+
findings.push({
|
|
8788
|
+
severity: "info",
|
|
8789
|
+
code: "gate-miss-drafts",
|
|
8790
|
+
section: "Corpus health",
|
|
8791
|
+
message: `${gateMissDrafts.length} proposed gate-miss lesson(s) await review: ${gateMissDrafts.slice(0, 5).map((m) => m.memory.frontmatter.id).join(", ")}.`,
|
|
8792
|
+
fix: "Review with `hivelore memory list --status proposed`."
|
|
8793
|
+
});
|
|
8794
|
+
}
|
|
8549
8795
|
const blockSensors = sensorMemories.filter(
|
|
8550
8796
|
(m) => m.memory.frontmatter.sensor?.severity === "block"
|
|
8551
8797
|
).length;
|
|
@@ -8565,8 +8811,8 @@ function registerDoctor(program2) {
|
|
|
8565
8811
|
const anchorPaths = s.paths.length > 0 ? s.paths : m.memory.frontmatter.anchor.paths;
|
|
8566
8812
|
const targets = [];
|
|
8567
8813
|
for (const rel of anchorPaths) {
|
|
8568
|
-
const abs =
|
|
8569
|
-
if (!
|
|
8814
|
+
const abs = path36.resolve(root, rel);
|
|
8815
|
+
if (!existsSync39(abs)) continue;
|
|
8570
8816
|
try {
|
|
8571
8817
|
targets.push({ path: rel, content: await readFile16(abs, "utf8") });
|
|
8572
8818
|
} catch {
|
|
@@ -8586,6 +8832,26 @@ function registerDoctor(program2) {
|
|
|
8586
8832
|
fix: "Make the pattern discriminating (add an 'absent' companion), or demote: `hivelore sensors promote <id> --severity warn`"
|
|
8587
8833
|
});
|
|
8588
8834
|
}
|
|
8835
|
+
const sensorHealth = assessSensorHealth2(await loadSensorLedger2(paths));
|
|
8836
|
+
for (const health of sensorHealth.filter((h) => h.quarantine_pending)) {
|
|
8837
|
+
const last = health.flaps.at(-1);
|
|
8838
|
+
findings.push({
|
|
8839
|
+
severity: "warn",
|
|
8840
|
+
code: "sensor-flaky",
|
|
8841
|
+
section: "Protection",
|
|
8842
|
+
message: `${health.memory_id} flapped ${health.flap_count}\xD7 on identical inputs. Last contradiction: ${last.previous.at} ${last.previous.outcome} \u2192 ${last.current.at} ${last.current.outcome}.`,
|
|
8843
|
+
fix: "Run `hivelore sync` to demote it, fix the oracle, then `hivelore sensors promote <id> --yes`."
|
|
8844
|
+
});
|
|
8845
|
+
}
|
|
8846
|
+
for (const health of sensorHealth.filter((h) => h.never_fired)) {
|
|
8847
|
+
findings.push({
|
|
8848
|
+
severity: "info",
|
|
8849
|
+
code: "sensor-never-fired",
|
|
8850
|
+
section: "Protection",
|
|
8851
|
+
message: `${health.memory_id} was evaluated ${health.evaluation_count} times across 30+ days and never fired \u2014 retirement candidate.`,
|
|
8852
|
+
fix: "Review the sensor manually; no automatic delete/retire command is provided."
|
|
8853
|
+
});
|
|
8854
|
+
}
|
|
8589
8855
|
const codeMap = await loadCodeMap6(paths);
|
|
8590
8856
|
if (!codeMap) {
|
|
8591
8857
|
findings.push({
|
|
@@ -8655,9 +8921,9 @@ function registerDoctor(program2) {
|
|
|
8655
8921
|
}
|
|
8656
8922
|
}
|
|
8657
8923
|
if (config.enforcement?.requireBriefingFirst) {
|
|
8658
|
-
const claudeSettings =
|
|
8924
|
+
const claudeSettings = path36.join(root, ".claude", "settings.local.json");
|
|
8659
8925
|
let hasClaudeEnforcement = false;
|
|
8660
|
-
if (
|
|
8926
|
+
if (existsSync39(claudeSettings)) {
|
|
8661
8927
|
try {
|
|
8662
8928
|
const { readFile: readFile24 } = await import("fs/promises");
|
|
8663
8929
|
const raw = await readFile24(claudeSettings, "utf8");
|
|
@@ -8683,7 +8949,7 @@ function registerDoctor(program2) {
|
|
|
8683
8949
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `hivelore init` without --manual)."
|
|
8684
8950
|
});
|
|
8685
8951
|
}
|
|
8686
|
-
findings.push(...await collectInstallFindings(root, "0.
|
|
8952
|
+
findings.push(...await collectInstallFindings(root, "0.35.0"));
|
|
8687
8953
|
findings.push(...await collectToolchainFindings(root));
|
|
8688
8954
|
try {
|
|
8689
8955
|
const legacyRaw = execSync("haive-mcp --version", {
|
|
@@ -8691,7 +8957,7 @@ function registerDoctor(program2) {
|
|
|
8691
8957
|
timeout: 3e3,
|
|
8692
8958
|
stdio: ["ignore", "pipe", "ignore"]
|
|
8693
8959
|
}).trim();
|
|
8694
|
-
const cliVersion = "0.
|
|
8960
|
+
const cliVersion = "0.35.0";
|
|
8695
8961
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
8696
8962
|
findings.push({
|
|
8697
8963
|
severity: "warn",
|
|
@@ -8707,17 +8973,17 @@ npm uninstall -g @hivelore/mcp`
|
|
|
8707
8973
|
}
|
|
8708
8974
|
{
|
|
8709
8975
|
const configPaths = [
|
|
8710
|
-
|
|
8711
|
-
|
|
8712
|
-
|
|
8976
|
+
path36.join(root, ".mcp.json"),
|
|
8977
|
+
path36.join(root, ".cursor", "mcp.json"),
|
|
8978
|
+
path36.join(root, ".vscode", "mcp.json")
|
|
8713
8979
|
];
|
|
8714
8980
|
const staleConfigs = [];
|
|
8715
8981
|
for (const cfgPath of configPaths) {
|
|
8716
|
-
if (!
|
|
8982
|
+
if (!existsSync39(cfgPath)) continue;
|
|
8717
8983
|
try {
|
|
8718
8984
|
const raw = await readFile16(cfgPath, "utf8");
|
|
8719
8985
|
if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
|
|
8720
|
-
staleConfigs.push(
|
|
8986
|
+
staleConfigs.push(path36.relative(root, cfgPath));
|
|
8721
8987
|
if (opts.fix && !opts.dryRun) {
|
|
8722
8988
|
const updated = raw.replace(/"command"\s*:\s*"haive-mcp"/g, '"command": "hivelore"').replace(/"args"\s*:\s*\[\]/g, '"args": ["mcp", "--stdio"]');
|
|
8723
8989
|
await writeFile23(cfgPath, updated, "utf8");
|
|
@@ -9010,8 +9276,8 @@ which -a hivelore haive`
|
|
|
9010
9276
|
const missingBins = /* @__PURE__ */ new Map();
|
|
9011
9277
|
const staleBins = /* @__PURE__ */ new Map();
|
|
9012
9278
|
for (const rel of integrationFiles) {
|
|
9013
|
-
const file =
|
|
9014
|
-
if (!
|
|
9279
|
+
const file = path36.join(root, rel);
|
|
9280
|
+
if (!existsSync39(file)) continue;
|
|
9015
9281
|
const text = await readFile16(file, "utf8").catch(() => "");
|
|
9016
9282
|
for (const bin of extractAbsoluteHaiveBins(text)) {
|
|
9017
9283
|
const version = versionForBinary(bin);
|
|
@@ -9044,7 +9310,7 @@ which -a hivelore haive`
|
|
|
9044
9310
|
async function collectToolchainFindings(root) {
|
|
9045
9311
|
const findings = [];
|
|
9046
9312
|
const pkg = await readJson(
|
|
9047
|
-
|
|
9313
|
+
path36.join(root, "package.json")
|
|
9048
9314
|
);
|
|
9049
9315
|
const wantsPnpm = pkg?.packageManager?.startsWith("pnpm@") || Object.values(pkg?.scripts ?? {}).some((script) => /\bpnpm\b/.test(script));
|
|
9050
9316
|
if (!wantsPnpm) return findings;
|
|
@@ -9063,10 +9329,10 @@ async function collectToolchainFindings(root) {
|
|
|
9063
9329
|
}
|
|
9064
9330
|
async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
9065
9331
|
const findings = [];
|
|
9066
|
-
const isHaiveWorkspace = ["hivelore-monorepo", "haive-monorepo"].includes((await readJson(
|
|
9332
|
+
const isHaiveWorkspace = ["hivelore-monorepo", "haive-monorepo"].includes((await readJson(path36.join(root, "package.json")))?.name ?? "");
|
|
9067
9333
|
if (!isHaiveWorkspace) return findings;
|
|
9068
|
-
const cliDist =
|
|
9069
|
-
if (!
|
|
9334
|
+
const cliDist = path36.join(root, "packages/cli/dist/index.js");
|
|
9335
|
+
if (!existsSync39(cliDist)) {
|
|
9070
9336
|
findings.push({
|
|
9071
9337
|
severity: "warn",
|
|
9072
9338
|
code: "workspace-dist-missing",
|
|
@@ -9090,7 +9356,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
|
9090
9356
|
"packages/core/src/index.ts",
|
|
9091
9357
|
"packages/mcp/src/server.ts",
|
|
9092
9358
|
"packages/cli/src/index.ts"
|
|
9093
|
-
].map((rel) =>
|
|
9359
|
+
].map((rel) => path36.join(root, rel)).filter(existsSync39);
|
|
9094
9360
|
if (sourceFiles.length > 0) {
|
|
9095
9361
|
const distMtime = statSync2(cliDist).mtimeMs;
|
|
9096
9362
|
const newestSource = Math.max(...sourceFiles.map((file) => statSync2(file).mtimeMs));
|
|
@@ -9108,7 +9374,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
|
9108
9374
|
}
|
|
9109
9375
|
async function collectWorkspaceVersionFindings(root, expectedVersion) {
|
|
9110
9376
|
const findings = [];
|
|
9111
|
-
const rootPkg = await readJson(
|
|
9377
|
+
const rootPkg = await readJson(path36.join(root, "package.json"));
|
|
9112
9378
|
const workspacePackages = [
|
|
9113
9379
|
"packages/core/package.json",
|
|
9114
9380
|
"packages/embeddings/package.json",
|
|
@@ -9117,7 +9383,7 @@ async function collectWorkspaceVersionFindings(root, expectedVersion) {
|
|
|
9117
9383
|
];
|
|
9118
9384
|
const existing = (await Promise.all(workspacePackages.map(async (rel) => ({
|
|
9119
9385
|
rel,
|
|
9120
|
-
pkg: await readJson(
|
|
9386
|
+
pkg: await readJson(path36.join(root, rel))
|
|
9121
9387
|
})))).filter((item) => item.pkg);
|
|
9122
9388
|
const isHaiveWorkspace = rootPkg?.name === "hivelore-monorepo" || rootPkg?.name === "haive-monorepo" || existing.some((item) => item.pkg?.name?.startsWith("@hivelore/"));
|
|
9123
9389
|
if (!isHaiveWorkspace) return findings;
|
|
@@ -9179,7 +9445,7 @@ function collectGlobalHivemoduleFindings(expectedVersion) {
|
|
|
9179
9445
|
}
|
|
9180
9446
|
}
|
|
9181
9447
|
async function readJson(file) {
|
|
9182
|
-
if (!
|
|
9448
|
+
if (!existsSync39(file)) return null;
|
|
9183
9449
|
try {
|
|
9184
9450
|
return JSON.parse(await readFile16(file, "utf8"));
|
|
9185
9451
|
} catch {
|
|
@@ -9408,27 +9674,29 @@ function runCommand(cmd, args, cwd) {
|
|
|
9408
9674
|
}
|
|
9409
9675
|
|
|
9410
9676
|
// src/commands/resolve-project.ts
|
|
9411
|
-
import
|
|
9677
|
+
import path37 from "path";
|
|
9412
9678
|
import "commander";
|
|
9413
9679
|
import { resolveProjectInfo } from "@hivelore/core";
|
|
9414
9680
|
function registerResolveProject(program2) {
|
|
9415
9681
|
program2.command("resolve-project").description(
|
|
9416
9682
|
"Print JSON for Hivelore project root resolution (HAIVE_PROJECT_ROOT, markers, .ai layout)."
|
|
9417
9683
|
).option("-d, --dir <dir>", "working directory", process.cwd()).action((opts) => {
|
|
9418
|
-
const info = resolveProjectInfo({ cwd:
|
|
9684
|
+
const info = resolveProjectInfo({ cwd: path37.resolve(opts.dir) });
|
|
9419
9685
|
console.log(JSON.stringify({ ok: true, info }, null, 2));
|
|
9420
9686
|
});
|
|
9421
9687
|
}
|
|
9422
9688
|
|
|
9423
9689
|
// src/commands/enforce.ts
|
|
9424
|
-
import { execFile as
|
|
9425
|
-
import { existsSync as
|
|
9690
|
+
import { execFile as execFile5, execFileSync as execFileSync2, spawn as spawn4 } from "child_process";
|
|
9691
|
+
import { existsSync as existsSync41, statSync as statSync3 } from "fs";
|
|
9426
9692
|
import { chmod, mkdir as mkdir16, readFile as readFile18, readdir as readdir4, rm as rm2, writeFile as writeFile25 } from "fs/promises";
|
|
9427
|
-
import
|
|
9428
|
-
import { promisify as
|
|
9693
|
+
import path39 from "path";
|
|
9694
|
+
import { promisify as promisify5 } from "util";
|
|
9429
9695
|
import "commander";
|
|
9430
9696
|
import {
|
|
9431
9697
|
antiPatternGateParams as antiPatternGateParams2,
|
|
9698
|
+
appendSensorEvaluations,
|
|
9699
|
+
assessSensorHealth as assessSensorHealth3,
|
|
9432
9700
|
assessBootstrapState,
|
|
9433
9701
|
isSensorScannablePath,
|
|
9434
9702
|
findProjectRoot as findProjectRoot37,
|
|
@@ -9441,7 +9709,8 @@ import {
|
|
|
9441
9709
|
isRetiredMemory as isRetiredMemory2,
|
|
9442
9710
|
loadConfig as loadConfig12,
|
|
9443
9711
|
detectAgentContext,
|
|
9444
|
-
loadMemoriesFromDir as
|
|
9712
|
+
loadMemoriesFromDir as loadMemoriesFromDir14,
|
|
9713
|
+
loadSensorLedger as loadSensorLedger3,
|
|
9445
9714
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
|
|
9446
9715
|
readRecentBriefingMarker,
|
|
9447
9716
|
recordPreventionHits,
|
|
@@ -9451,15 +9720,16 @@ import {
|
|
|
9451
9720
|
saveConfig as saveConfig3,
|
|
9452
9721
|
selectCommandSensors,
|
|
9453
9722
|
sensorTargetsFromDiff,
|
|
9723
|
+
sensorAppliesToPath as sensorAppliesToPath2,
|
|
9454
9724
|
SESSION_RECAP_TTL_MS,
|
|
9455
9725
|
verifyAnchor as verifyAnchor3,
|
|
9456
9726
|
writeBriefingMarker as writeBriefingMarker2
|
|
9457
9727
|
} from "@hivelore/core";
|
|
9458
9728
|
|
|
9459
9729
|
// src/utils/claude-hooks.ts
|
|
9460
|
-
import { existsSync as
|
|
9730
|
+
import { existsSync as existsSync40 } from "fs";
|
|
9461
9731
|
import { mkdir as mkdir15, readFile as readFile17, writeFile as writeFile24 } from "fs/promises";
|
|
9462
|
-
import
|
|
9732
|
+
import path38 from "path";
|
|
9463
9733
|
var HAIVE_HOOK_TAG = "haive-enforcement";
|
|
9464
9734
|
var POST_TOOL_USE_GROUP = {
|
|
9465
9735
|
matcher: "Edit|Write|Bash",
|
|
@@ -9545,7 +9815,7 @@ function unpatchClaudeSettings(input) {
|
|
|
9545
9815
|
async function installClaudeHooksAtPath(settingsPath) {
|
|
9546
9816
|
let raw = null;
|
|
9547
9817
|
let created = false;
|
|
9548
|
-
if (
|
|
9818
|
+
if (existsSync40(settingsPath)) {
|
|
9549
9819
|
try {
|
|
9550
9820
|
raw = JSON.parse(await readFile17(settingsPath, "utf8"));
|
|
9551
9821
|
} catch {
|
|
@@ -9555,12 +9825,12 @@ async function installClaudeHooksAtPath(settingsPath) {
|
|
|
9555
9825
|
created = true;
|
|
9556
9826
|
}
|
|
9557
9827
|
const patched = patchClaudeSettings(raw);
|
|
9558
|
-
await mkdir15(
|
|
9828
|
+
await mkdir15(path38.dirname(settingsPath), { recursive: true });
|
|
9559
9829
|
await writeFile24(settingsPath, JSON.stringify(patched, null, 2) + "\n", "utf8");
|
|
9560
9830
|
return { settingsPath, created };
|
|
9561
9831
|
}
|
|
9562
9832
|
async function uninstallClaudeHooksAtPath(settingsPath) {
|
|
9563
|
-
if (!
|
|
9833
|
+
if (!existsSync40(settingsPath)) {
|
|
9564
9834
|
return { settingsPath, created: false };
|
|
9565
9835
|
}
|
|
9566
9836
|
const raw = JSON.parse(await readFile17(settingsPath, "utf8"));
|
|
@@ -9571,9 +9841,9 @@ async function uninstallClaudeHooksAtPath(settingsPath) {
|
|
|
9571
9841
|
function defaultClaudeSettingsPath(scope, projectRoot) {
|
|
9572
9842
|
if (scope === "user") {
|
|
9573
9843
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
9574
|
-
return
|
|
9844
|
+
return path38.join(home, ".claude", "settings.json");
|
|
9575
9845
|
}
|
|
9576
|
-
return
|
|
9846
|
+
return path38.join(projectRoot, ".claude", "settings.local.json");
|
|
9577
9847
|
}
|
|
9578
9848
|
|
|
9579
9849
|
// src/utils/command-sensors.ts
|
|
@@ -9653,8 +9923,52 @@ async function executeCommandSensors(specs, root) {
|
|
|
9653
9923
|
return runs;
|
|
9654
9924
|
}
|
|
9655
9925
|
|
|
9926
|
+
// src/utils/sensor-evaluations.ts
|
|
9927
|
+
import { execFile as execFile4 } from "child_process";
|
|
9928
|
+
import { promisify as promisify4 } from "util";
|
|
9929
|
+
import {
|
|
9930
|
+
computeScopeHash,
|
|
9931
|
+
sensorAppliesToPath
|
|
9932
|
+
} from "@hivelore/core";
|
|
9933
|
+
var exec3 = promisify4(execFile4);
|
|
9934
|
+
async function gitHeadSha(root) {
|
|
9935
|
+
try {
|
|
9936
|
+
const { stdout } = await exec3("git", ["rev-parse", "HEAD"], { cwd: root });
|
|
9937
|
+
return stdout.trim();
|
|
9938
|
+
} catch {
|
|
9939
|
+
return "";
|
|
9940
|
+
}
|
|
9941
|
+
}
|
|
9942
|
+
async function trackedFiles(root) {
|
|
9943
|
+
try {
|
|
9944
|
+
const { stdout } = await exec3("git", ["ls-files", "-co", "--exclude-standard"], { cwd: root });
|
|
9945
|
+
return stdout.split("\n").map((f) => f.trim()).filter(Boolean);
|
|
9946
|
+
} catch {
|
|
9947
|
+
return [];
|
|
9948
|
+
}
|
|
9949
|
+
}
|
|
9950
|
+
async function commandScopeHash(root, spec) {
|
|
9951
|
+
if (spec.paths.length === 0) return "";
|
|
9952
|
+
const sensor = { kind: spec.kind, paths: spec.paths };
|
|
9953
|
+
const files = (await trackedFiles(root)).filter((file) => sensorAppliesToPath(sensor, [], file));
|
|
9954
|
+
return computeScopeHash(root, files);
|
|
9955
|
+
}
|
|
9956
|
+
function evaluation(base, command) {
|
|
9957
|
+
return {
|
|
9958
|
+
at: base.at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
9959
|
+
memory_id: base.memory_id,
|
|
9960
|
+
kind: base.kind,
|
|
9961
|
+
stage: base.stage,
|
|
9962
|
+
head_sha: base.head_sha,
|
|
9963
|
+
scope_hash: base.scope_hash,
|
|
9964
|
+
outcome: base.outcome,
|
|
9965
|
+
...command?.exit_code !== null ? { exit_code: command?.exit_code } : {},
|
|
9966
|
+
...command ? { duration_ms: command.duration_ms } : {}
|
|
9967
|
+
};
|
|
9968
|
+
}
|
|
9969
|
+
|
|
9656
9970
|
// src/commands/enforce.ts
|
|
9657
|
-
var execFileAsync2 =
|
|
9971
|
+
var execFileAsync2 = promisify5(execFile5);
|
|
9658
9972
|
var MAX_STDIN_BYTES2 = 256 * 1024;
|
|
9659
9973
|
var ENFORCE_HOOK_MARKER = "# Hivelore enforcement hook";
|
|
9660
9974
|
function registerEnforce(program2) {
|
|
@@ -9715,7 +10029,7 @@ function registerEnforce(program2) {
|
|
|
9715
10029
|
ui.success(`Removed Hivelore hooks from ${result.settingsPath}`);
|
|
9716
10030
|
} else {
|
|
9717
10031
|
const result = await installClaudeHooksAtPath(settingsPath);
|
|
9718
|
-
ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${
|
|
10032
|
+
ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path39.relative(root, result.settingsPath) || result.settingsPath})`);
|
|
9719
10033
|
}
|
|
9720
10034
|
} catch (err) {
|
|
9721
10035
|
ui.warn(`Claude Code hooks not ${opts.removeClaude ? "removed" : "installed"}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -9737,19 +10051,19 @@ function registerEnforce(program2) {
|
|
|
9737
10051
|
enforce.command("cleanup").description("Remove generated Hivelore runtime/cache artifacts that should not appear in commits.").option("-d, --dir <dir>", "project root").option("--dry-run", "print what would be removed without deleting", false).action(async (opts) => {
|
|
9738
10052
|
const root = findProjectRoot37(opts.dir);
|
|
9739
10053
|
const paths = resolveHaivePaths35(root);
|
|
9740
|
-
const cacheDir =
|
|
9741
|
-
if (
|
|
9742
|
-
if (opts.dryRun) ui.info(`would clean ${
|
|
10054
|
+
const cacheDir = path39.join(paths.haiveDir, ".cache");
|
|
10055
|
+
if (existsSync41(cacheDir)) {
|
|
10056
|
+
if (opts.dryRun) ui.info(`would clean ${path39.relative(root, cacheDir)} (preserving .gitignore)`);
|
|
9743
10057
|
else {
|
|
9744
10058
|
const removed = await cleanupCacheDir(cacheDir);
|
|
9745
|
-
ui.success(`cleaned ${
|
|
10059
|
+
ui.success(`cleaned ${path39.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
|
|
9746
10060
|
}
|
|
9747
10061
|
}
|
|
9748
|
-
if (
|
|
9749
|
-
if (opts.dryRun) ui.info(`would clean ${
|
|
10062
|
+
if (existsSync41(paths.runtimeDir)) {
|
|
10063
|
+
if (opts.dryRun) ui.info(`would clean ${path39.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
|
|
9750
10064
|
else {
|
|
9751
10065
|
const removed = await cleanupRuntimeDir(paths.runtimeDir);
|
|
9752
|
-
ui.success(`cleaned ${
|
|
10066
|
+
ui.success(`cleaned ${path39.relative(root, paths.runtimeDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
|
|
9753
10067
|
}
|
|
9754
10068
|
}
|
|
9755
10069
|
});
|
|
@@ -9793,7 +10107,7 @@ function registerEnforce(program2) {
|
|
|
9793
10107
|
const root = resolveRoot(opts.dir, payload);
|
|
9794
10108
|
if (!root) return;
|
|
9795
10109
|
const paths = resolveHaivePaths35(root);
|
|
9796
|
-
if (!
|
|
10110
|
+
if (!existsSync41(paths.haiveDir)) return;
|
|
9797
10111
|
await mkdir16(paths.runtimeDir, { recursive: true });
|
|
9798
10112
|
const sessionId = opts.sessionId ?? payload.session_id;
|
|
9799
10113
|
const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this Hivelore-initialized project.";
|
|
@@ -9856,7 +10170,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
9856
10170
|
const root = resolveRoot(opts.dir, payload);
|
|
9857
10171
|
if (!root) return;
|
|
9858
10172
|
const paths = resolveHaivePaths35(root);
|
|
9859
|
-
if (!
|
|
10173
|
+
if (!existsSync41(paths.haiveDir)) return;
|
|
9860
10174
|
if (!isWriteLikeTool(payload)) return;
|
|
9861
10175
|
const config = await loadConfig12(paths);
|
|
9862
10176
|
if (config.enforcement?.requireBriefingFirst === false) return;
|
|
@@ -9921,7 +10235,7 @@ function emitPreToolUseContext(text) {
|
|
|
9921
10235
|
async function buildFinishReport(dir) {
|
|
9922
10236
|
const root = findProjectRoot37(dir);
|
|
9923
10237
|
const paths = resolveHaivePaths35(root);
|
|
9924
|
-
const initialized =
|
|
10238
|
+
const initialized = existsSync41(paths.haiveDir);
|
|
9925
10239
|
const config = initialized ? await loadConfig12(paths) : {};
|
|
9926
10240
|
const mode = config.enforcement?.mode ?? "strict";
|
|
9927
10241
|
const findings = [];
|
|
@@ -10113,8 +10427,8 @@ async function buildFinishReport(dir) {
|
|
|
10113
10427
|
async function checkFailureCapture(paths, config) {
|
|
10114
10428
|
const gate = config.enforcement?.failureCaptureGate ?? "warn";
|
|
10115
10429
|
if (gate === "off") return [];
|
|
10116
|
-
const obsFile =
|
|
10117
|
-
if (!
|
|
10430
|
+
const obsFile = path39.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
10431
|
+
if (!existsSync41(obsFile)) return [];
|
|
10118
10432
|
const failures = [];
|
|
10119
10433
|
try {
|
|
10120
10434
|
const raw = await readFile18(obsFile, "utf8");
|
|
@@ -10131,7 +10445,7 @@ async function checkFailureCapture(paths, config) {
|
|
|
10131
10445
|
return [];
|
|
10132
10446
|
}
|
|
10133
10447
|
if (failures.length === 0) return [];
|
|
10134
|
-
const memories =
|
|
10448
|
+
const memories = existsSync41(paths.memoriesDir) ? await loadMemoriesFromDir14(paths.memoriesDir) : [];
|
|
10135
10449
|
const captureTimes = memories.filter(({ memory: memory2 }) => ["attempt", "gotcha"].includes(memory2.frontmatter.type)).map(({ memory: memory2 }) => memory2.frontmatter.created_at);
|
|
10136
10450
|
const uncaptured = findUncapturedFailures(failures, captureTimes);
|
|
10137
10451
|
if (uncaptured.length === 0) {
|
|
@@ -10166,7 +10480,7 @@ function finishReport(root, initialized, mode, findings, config) {
|
|
|
10166
10480
|
async function runWithEnforcement(command, args, opts) {
|
|
10167
10481
|
const root = findProjectRoot37(opts.dir);
|
|
10168
10482
|
const paths = resolveHaivePaths35(root);
|
|
10169
|
-
if (!
|
|
10483
|
+
if (!existsSync41(paths.haiveDir)) {
|
|
10170
10484
|
ui.error(`No .ai/ found at ${root}. Run \`hivelore init\` first.`);
|
|
10171
10485
|
process.exit(1);
|
|
10172
10486
|
}
|
|
@@ -10185,7 +10499,7 @@ async function runWithEnforcement(command, args, opts) {
|
|
|
10185
10499
|
process.exit(2);
|
|
10186
10500
|
}
|
|
10187
10501
|
ui.info(`Hivelore briefing marker created for wrapped agent session: ${sessionId}`);
|
|
10188
|
-
ui.info(`Briefing written to ${
|
|
10502
|
+
ui.info(`Briefing written to ${path39.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
|
|
10189
10503
|
const child = spawn4(command, args, {
|
|
10190
10504
|
cwd: root,
|
|
10191
10505
|
stdio: "inherit",
|
|
@@ -10237,9 +10551,9 @@ async function writeWrapperBriefing(paths, sessionId, task) {
|
|
|
10237
10551
|
source: "haive-run",
|
|
10238
10552
|
memoryIds: briefing.memories.map((m) => m.id)
|
|
10239
10553
|
});
|
|
10240
|
-
const dir =
|
|
10554
|
+
const dir = path39.join(paths.runtimeDir, "enforcement", "briefings");
|
|
10241
10555
|
await mkdir16(dir, { recursive: true });
|
|
10242
|
-
const file =
|
|
10556
|
+
const file = path39.join(dir, `${sessionId}.md`);
|
|
10243
10557
|
const parts = [
|
|
10244
10558
|
"# Hivelore Briefing",
|
|
10245
10559
|
"",
|
|
@@ -10277,7 +10591,7 @@ async function checkBootstrapComplete(paths, config, productionCodeChanged) {
|
|
|
10277
10591
|
projectContextRaw = await readFile18(paths.projectContext, "utf8");
|
|
10278
10592
|
} catch {
|
|
10279
10593
|
}
|
|
10280
|
-
const memories =
|
|
10594
|
+
const memories = existsSync41(paths.memoriesDir) ? await loadMemoriesFromDir14(paths.memoriesDir) : [];
|
|
10281
10595
|
const codeMap = await loadCodeMap7(paths);
|
|
10282
10596
|
const codeFiles = codeMap ? Object.keys(codeMap.files) : [];
|
|
10283
10597
|
let existingModules = [];
|
|
@@ -10309,7 +10623,7 @@ ${renderBootstrapChecklist(assessment)}`,
|
|
|
10309
10623
|
async function buildEnforcementReport(dir, stage, sessionId) {
|
|
10310
10624
|
const root = findProjectRoot37(dir);
|
|
10311
10625
|
const paths = resolveHaivePaths35(root);
|
|
10312
|
-
const initialized =
|
|
10626
|
+
const initialized = existsSync41(paths.haiveDir);
|
|
10313
10627
|
const config = initialized ? await loadConfig12(paths) : {};
|
|
10314
10628
|
if (initialized) {
|
|
10315
10629
|
await applyLightweightRepairs(root, paths);
|
|
@@ -10343,7 +10657,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
10343
10657
|
findings: [{ severity: "info", code: "enforcement-off", message: "Hivelore enforcement is disabled." }]
|
|
10344
10658
|
});
|
|
10345
10659
|
}
|
|
10346
|
-
findings.push(...await inspectIntegrationVersions(root, "0.
|
|
10660
|
+
findings.push(...await inspectIntegrationVersions(root, "0.35.0"));
|
|
10347
10661
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
10348
10662
|
const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
|
|
10349
10663
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent Hivelore briefing marker exists." } : {
|
|
@@ -10423,7 +10737,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
10423
10737
|
}];
|
|
10424
10738
|
}
|
|
10425
10739
|
const hasErrors = effectiveFindings.some((f) => f.severity === "error");
|
|
10426
|
-
|
|
10740
|
+
const report = withCategories({
|
|
10427
10741
|
root,
|
|
10428
10742
|
initialized,
|
|
10429
10743
|
mode,
|
|
@@ -10432,6 +10746,18 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
10432
10746
|
should_block: mode === "strict" && hasErrors,
|
|
10433
10747
|
findings: effectiveFindings
|
|
10434
10748
|
});
|
|
10749
|
+
if (!report.should_block && (stage === "pre-commit" || stage === "ci")) {
|
|
10750
|
+
const headSha = await gitHeadSha(root);
|
|
10751
|
+
await appendSensorEvaluations(paths, [evaluation({
|
|
10752
|
+
memory_id: "__gate__",
|
|
10753
|
+
kind: "shell",
|
|
10754
|
+
stage,
|
|
10755
|
+
head_sha: headSha,
|
|
10756
|
+
scope_hash: "",
|
|
10757
|
+
outcome: "silent"
|
|
10758
|
+
})]);
|
|
10759
|
+
}
|
|
10760
|
+
return report;
|
|
10435
10761
|
}
|
|
10436
10762
|
function withCategories(report) {
|
|
10437
10763
|
return {
|
|
@@ -10446,8 +10772,8 @@ function withCategories(report) {
|
|
|
10446
10772
|
async function hasRecentSessionRecap(paths) {
|
|
10447
10773
|
const handoffAge = await handoffAgeMs(paths.root);
|
|
10448
10774
|
if (handoffAge !== null && handoffAge < SESSION_RECAP_TTL_MS) return true;
|
|
10449
|
-
if (!
|
|
10450
|
-
const all = await
|
|
10775
|
+
if (!existsSync41(paths.memoriesDir)) return false;
|
|
10776
|
+
const all = await loadMemoriesFromDir14(paths.memoriesDir);
|
|
10451
10777
|
return all.some(({ memory: memory2 }) => {
|
|
10452
10778
|
const fm = memory2.frontmatter;
|
|
10453
10779
|
const freshnessDate = fm.verified_at ?? fm.created_at;
|
|
@@ -10455,8 +10781,8 @@ async function hasRecentSessionRecap(paths) {
|
|
|
10455
10781
|
});
|
|
10456
10782
|
}
|
|
10457
10783
|
async function verifyMemoryPolicy(paths, config) {
|
|
10458
|
-
if (!
|
|
10459
|
-
const all = await
|
|
10784
|
+
if (!existsSync41(paths.memoriesDir)) return [];
|
|
10785
|
+
const all = await loadMemoriesFromDir14(paths.memoriesDir);
|
|
10460
10786
|
const findings = [];
|
|
10461
10787
|
const staleImportant = [];
|
|
10462
10788
|
let verified = 0;
|
|
@@ -10493,12 +10819,12 @@ async function verifyMemoryPolicy(paths, config) {
|
|
|
10493
10819
|
return findings;
|
|
10494
10820
|
}
|
|
10495
10821
|
async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
10496
|
-
if (!
|
|
10822
|
+
if (!existsSync41(paths.memoriesDir)) return [];
|
|
10497
10823
|
const changedFiles = (await getChangedFiles(paths.root, stage)).filter((f) => !isGeneratedArtifact(f));
|
|
10498
10824
|
if (changedFiles.length === 0) {
|
|
10499
10825
|
return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
|
|
10500
10826
|
}
|
|
10501
|
-
const all = await
|
|
10827
|
+
const all = await loadMemoriesFromDir14(paths.memoriesDir);
|
|
10502
10828
|
const changedSet = new Set(changedFiles);
|
|
10503
10829
|
const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention"]);
|
|
10504
10830
|
const relevant = all.filter(({ memory: memory2 }) => {
|
|
@@ -10527,7 +10853,7 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
|
10527
10853
|
const consulted = new Set(marker?.memory_ids ?? []);
|
|
10528
10854
|
const missing = relevant.filter(({ memory: memory2, filePath }) => {
|
|
10529
10855
|
if (consulted.has(memory2.frontmatter.id)) return false;
|
|
10530
|
-
if (changedSet.has(
|
|
10856
|
+
if (changedSet.has(path39.relative(paths.root, filePath))) return false;
|
|
10531
10857
|
return true;
|
|
10532
10858
|
}).map(({ memory: memory2 }) => memory2);
|
|
10533
10859
|
if (missing.length === 0) {
|
|
@@ -10587,12 +10913,12 @@ async function runPrecommitPolicy(paths, gate, stage) {
|
|
|
10587
10913
|
anchored_blocks,
|
|
10588
10914
|
semantic: true
|
|
10589
10915
|
}, { paths });
|
|
10590
|
-
const sensorFindings = await runSensorGate(paths, snapshot.diff);
|
|
10916
|
+
const sensorFindings = await runSensorGate(paths, snapshot.diff, stage);
|
|
10591
10917
|
const reviewWarnings = result.warnings.filter(
|
|
10592
10918
|
(w) => w.level === "review" && !w.reasons.includes("sensor")
|
|
10593
10919
|
);
|
|
10594
10920
|
const REVIEW_SEEN_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
10595
|
-
const reviewSeenFile =
|
|
10921
|
+
const reviewSeenFile = path39.join(paths.runtimeDir, "enforcement", "review-seen.json");
|
|
10596
10922
|
let reviewSeen = {};
|
|
10597
10923
|
try {
|
|
10598
10924
|
reviewSeen = JSON.parse(await readFile18(reviewSeenFile, "utf8"));
|
|
@@ -10619,7 +10945,7 @@ async function runPrecommitPolicy(paths, gate, stage) {
|
|
|
10619
10945
|
for (const [id, at] of Object.entries(reviewSeen)) {
|
|
10620
10946
|
if (!Number.isFinite(Date.parse(at)) || now - Date.parse(at) >= REVIEW_SEEN_TTL_MS) delete reviewSeen[id];
|
|
10621
10947
|
}
|
|
10622
|
-
await mkdir16(
|
|
10948
|
+
await mkdir16(path39.dirname(reviewSeenFile), { recursive: true });
|
|
10623
10949
|
await writeFile25(reviewSeenFile, JSON.stringify(reviewSeen, null, 2), "utf8");
|
|
10624
10950
|
} catch {
|
|
10625
10951
|
}
|
|
@@ -10650,10 +10976,10 @@ async function runPrecommitPolicy(paths, gate, stage) {
|
|
|
10650
10976
|
...sensorFindings
|
|
10651
10977
|
];
|
|
10652
10978
|
}
|
|
10653
|
-
async function runSensorGate(paths, diff) {
|
|
10654
|
-
if (!diff || !
|
|
10979
|
+
async function runSensorGate(paths, diff, stage) {
|
|
10980
|
+
if (!diff || !existsSync41(paths.memoriesDir)) return [];
|
|
10655
10981
|
try {
|
|
10656
|
-
const loaded = await
|
|
10982
|
+
const loaded = await loadMemoriesFromDir14(paths.memoriesDir);
|
|
10657
10983
|
const scannable = loaded.map((l) => l.memory).filter((m) => Boolean(m.frontmatter.sensor) && !isRetiredMemory2(m.frontmatter, m.body));
|
|
10658
10984
|
if (scannable.length === 0) return [];
|
|
10659
10985
|
const targets = sensorTargetsFromDiff(diff).filter((t) => isSensorScannablePath(t.path));
|
|
@@ -10661,8 +10987,22 @@ async function runSensorGate(paths, diff) {
|
|
|
10661
10987
|
const findings = [];
|
|
10662
10988
|
const seen = /* @__PURE__ */ new Set();
|
|
10663
10989
|
const firedIds = /* @__PURE__ */ new Set();
|
|
10990
|
+
const ledgerRows = [];
|
|
10991
|
+
const headSha = await gitHeadSha(paths.root);
|
|
10664
10992
|
const regexSensorMemories = scannable.filter((m) => m.frontmatter.sensor.kind === "regex");
|
|
10665
10993
|
const hits = regexSensorMemories.length > 0 ? runSensors(regexSensorMemories, targets) : [];
|
|
10994
|
+
for (const memory2 of regexSensorMemories) {
|
|
10995
|
+
const sensor = memory2.frontmatter.sensor;
|
|
10996
|
+
if (!targets.some((target) => sensorAppliesToPath2(sensor, memory2.frontmatter.anchor.paths, target.path))) continue;
|
|
10997
|
+
ledgerRows.push(evaluation({
|
|
10998
|
+
memory_id: memory2.frontmatter.id,
|
|
10999
|
+
kind: "regex",
|
|
11000
|
+
stage,
|
|
11001
|
+
head_sha: headSha,
|
|
11002
|
+
scope_hash: "",
|
|
11003
|
+
outcome: hits.some((hit) => hit.memory_id === memory2.frontmatter.id) ? "fired" : "silent"
|
|
11004
|
+
}));
|
|
11005
|
+
}
|
|
10666
11006
|
for (const hit of hits) {
|
|
10667
11007
|
if (seen.has(hit.memory_id)) continue;
|
|
10668
11008
|
seen.add(hit.memory_id);
|
|
@@ -10674,7 +11014,8 @@ async function runSensorGate(paths, diff) {
|
|
|
10674
11014
|
code: "sensor-block",
|
|
10675
11015
|
message: `Block sensor fired \u2014 ${hit.memory_id}: ${hit.message}${where}`,
|
|
10676
11016
|
fix: "Remove the flagged pattern, or run `hivelore sensors check` to inspect the match.",
|
|
10677
|
-
impact: 45
|
|
11017
|
+
impact: 45,
|
|
11018
|
+
memory_ids: [hit.memory_id]
|
|
10678
11019
|
});
|
|
10679
11020
|
} else {
|
|
10680
11021
|
findings.push({
|
|
@@ -10682,7 +11023,8 @@ async function runSensorGate(paths, diff) {
|
|
|
10682
11023
|
code: "sensor-warn",
|
|
10683
11024
|
message: `Sensor flagged ${hit.memory_id}: ${hit.message}${where}`,
|
|
10684
11025
|
fix: "Review the flagged line; `hivelore sensors check` shows the matched code.",
|
|
10685
|
-
impact: 5
|
|
11026
|
+
impact: 5,
|
|
11027
|
+
memory_ids: [hit.memory_id]
|
|
10686
11028
|
});
|
|
10687
11029
|
}
|
|
10688
11030
|
}
|
|
@@ -10690,7 +11032,34 @@ async function runSensorGate(paths, diff) {
|
|
|
10690
11032
|
if (config?.enforcement?.runCommandSensors === true) {
|
|
10691
11033
|
const changedPaths = targets.map((t) => t.path).filter(Boolean);
|
|
10692
11034
|
const specs = selectCommandSensors(scannable, changedPaths).filter((sp) => !seen.has(sp.memory_id));
|
|
10693
|
-
|
|
11035
|
+
const runs = await executeCommandSensors(specs, paths.root);
|
|
11036
|
+
for (const run of runs) {
|
|
11037
|
+
const spec = specs.find((candidate) => candidate.memory_id === run.memory_id);
|
|
11038
|
+
ledgerRows.push(evaluation({
|
|
11039
|
+
memory_id: run.memory_id,
|
|
11040
|
+
kind: run.kind,
|
|
11041
|
+
stage,
|
|
11042
|
+
head_sha: headSha,
|
|
11043
|
+
scope_hash: await commandScopeHash(paths.root, spec),
|
|
11044
|
+
outcome: run.status === "failed" ? "fired" : run.status === "passed" ? "silent" : "unrunnable"
|
|
11045
|
+
}, { exit_code: run.exit_code, duration_ms: run.duration_ms }));
|
|
11046
|
+
}
|
|
11047
|
+
const prior = await loadSensorLedger3(paths);
|
|
11048
|
+
const health = new Map(assessSensorHealth3([...prior, ...ledgerRows]).map((h) => [h.memory_id, h]));
|
|
11049
|
+
for (const run of runs) {
|
|
11050
|
+
const sensorHealth = health.get(run.memory_id);
|
|
11051
|
+
const quarantined = sensorHealth?.quarantine_pending === true;
|
|
11052
|
+
if (quarantined && run.severity === "block") {
|
|
11053
|
+
const last = sensorHealth.flaps.at(-1);
|
|
11054
|
+
findings.push({
|
|
11055
|
+
severity: "warn",
|
|
11056
|
+
code: "sensor-flaky",
|
|
11057
|
+
message: `Command sensor ${run.memory_id} flapped ${sensorHealth.flap_count}\xD7 on identical inputs; treated as warn pending sync quarantine. Last contradiction: ${last.previous.at} ${last.previous.outcome} \u2192 ${last.current.at} ${last.current.outcome}.`,
|
|
11058
|
+
fix: "Run `hivelore sync`, fix the flaky oracle, then re-promote with `hivelore sensors promote <id> --yes`.",
|
|
11059
|
+
impact: 5,
|
|
11060
|
+
memory_ids: [run.memory_id]
|
|
11061
|
+
});
|
|
11062
|
+
}
|
|
10694
11063
|
if (run.status === "passed") continue;
|
|
10695
11064
|
seen.add(run.memory_id);
|
|
10696
11065
|
if (run.status === "unrunnable") {
|
|
@@ -10707,14 +11076,15 @@ ${run.output_tail}` : ""),
|
|
|
10707
11076
|
firedIds.add(run.memory_id);
|
|
10708
11077
|
const outputBlock = run.output_tail ? `
|
|
10709
11078
|
${run.output_tail}` : "";
|
|
10710
|
-
if (run.severity === "block") {
|
|
11079
|
+
if (run.severity === "block" && !quarantined) {
|
|
10711
11080
|
findings.push({
|
|
10712
11081
|
severity: "error",
|
|
10713
11082
|
code: "sensor-block",
|
|
10714
11083
|
message: `Block ${run.kind} sensor fired \u2014 ${run.memory_id}: ${run.message}
|
|
10715
11084
|
command: ${run.command} (exit ${run.exit_code}, ${run.duration_ms}ms)${outputBlock}`,
|
|
10716
11085
|
fix: "Fix the behaviour the command checks, or run `hivelore sensors check --commands` to inspect it.",
|
|
10717
|
-
impact: 45
|
|
11086
|
+
impact: 45,
|
|
11087
|
+
memory_ids: [run.memory_id]
|
|
10718
11088
|
});
|
|
10719
11089
|
} else {
|
|
10720
11090
|
findings.push({
|
|
@@ -10722,13 +11092,19 @@ command: ${run.command} (exit ${run.exit_code}, ${run.duration_ms}ms)${outputBlo
|
|
|
10722
11092
|
code: "sensor-warn",
|
|
10723
11093
|
message: `${run.kind} sensor flagged ${run.memory_id}: ${run.message} (exit ${run.exit_code})${outputBlock}`,
|
|
10724
11094
|
fix: "Review the failing command; `hivelore sensors check --commands` re-runs it.",
|
|
10725
|
-
impact: 5
|
|
11095
|
+
impact: 5,
|
|
11096
|
+
memory_ids: [run.memory_id]
|
|
10726
11097
|
});
|
|
10727
11098
|
}
|
|
10728
11099
|
}
|
|
10729
11100
|
}
|
|
11101
|
+
await appendSensorEvaluations(paths, ledgerRows);
|
|
10730
11102
|
if (firedIds.size > 0) {
|
|
10731
|
-
|
|
11103
|
+
const details = Object.fromEntries([...firedIds].map((id) => {
|
|
11104
|
+
const row = ledgerRows.find((entry) => entry.memory_id === id && entry.outcome === "fired");
|
|
11105
|
+
return [id, { kind: row?.kind ?? "regex", stage, ...row?.exit_code !== void 0 ? { exit_code: row.exit_code } : {} }];
|
|
11106
|
+
}));
|
|
11107
|
+
await recordPreventionHits(paths, [...firedIds], "sensor", /* @__PURE__ */ new Date(), details).catch(() => {
|
|
10732
11108
|
});
|
|
10733
11109
|
}
|
|
10734
11110
|
return findings;
|
|
@@ -10766,16 +11142,16 @@ async function cleanupRuntimeDir(runtimeDir) {
|
|
|
10766
11142
|
for (const entry of entries) {
|
|
10767
11143
|
if (entry.name === ".gitignore" || entry.name === "README.md") continue;
|
|
10768
11144
|
if (entry.name === "enforcement") {
|
|
10769
|
-
removed += await cleanupEnforcementDir(
|
|
11145
|
+
removed += await cleanupEnforcementDir(path39.join(runtimeDir, entry.name));
|
|
10770
11146
|
continue;
|
|
10771
11147
|
}
|
|
10772
|
-
await rm2(
|
|
11148
|
+
await rm2(path39.join(runtimeDir, entry.name), { recursive: true, force: true });
|
|
10773
11149
|
removed++;
|
|
10774
11150
|
}
|
|
10775
|
-
await writeFile25(
|
|
10776
|
-
if (!
|
|
11151
|
+
await writeFile25(path39.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
|
|
11152
|
+
if (!existsSync41(path39.join(runtimeDir, "README.md"))) {
|
|
10777
11153
|
await writeFile25(
|
|
10778
|
-
|
|
11154
|
+
path39.join(runtimeDir, "README.md"),
|
|
10779
11155
|
"# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. Hivelore cleanup preserves briefing markers so enforcement state remains valid.\n",
|
|
10780
11156
|
"utf8"
|
|
10781
11157
|
);
|
|
@@ -10788,10 +11164,10 @@ async function cleanupCacheDir(cacheDir) {
|
|
|
10788
11164
|
const entries = await readdir4(cacheDir, { withFileTypes: true }).catch(() => []);
|
|
10789
11165
|
for (const entry of entries) {
|
|
10790
11166
|
if (entry.name === ".gitignore") continue;
|
|
10791
|
-
await rm2(
|
|
11167
|
+
await rm2(path39.join(cacheDir, entry.name), { recursive: true, force: true });
|
|
10792
11168
|
removed++;
|
|
10793
11169
|
}
|
|
10794
|
-
await writeFile25(
|
|
11170
|
+
await writeFile25(path39.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
|
|
10795
11171
|
return removed;
|
|
10796
11172
|
}
|
|
10797
11173
|
async function cleanupEnforcementDir(enforcementDir) {
|
|
@@ -10799,7 +11175,7 @@ async function cleanupEnforcementDir(enforcementDir) {
|
|
|
10799
11175
|
const entries = await readdir4(enforcementDir, { withFileTypes: true }).catch(() => []);
|
|
10800
11176
|
for (const entry of entries) {
|
|
10801
11177
|
if (entry.name === "briefings") continue;
|
|
10802
|
-
await rm2(
|
|
11178
|
+
await rm2(path39.join(enforcementDir, entry.name), { recursive: true, force: true });
|
|
10803
11179
|
removed++;
|
|
10804
11180
|
}
|
|
10805
11181
|
return removed;
|
|
@@ -10817,8 +11193,8 @@ async function inspectIntegrationVersions(root, expectedVersion) {
|
|
|
10817
11193
|
const missingBins = /* @__PURE__ */ new Map();
|
|
10818
11194
|
const staleBins = /* @__PURE__ */ new Map();
|
|
10819
11195
|
for (const rel of files) {
|
|
10820
|
-
const file =
|
|
10821
|
-
if (!
|
|
11196
|
+
const file = path39.join(root, rel);
|
|
11197
|
+
if (!existsSync41(file)) continue;
|
|
10822
11198
|
const text = await readFile18(file, "utf8").catch(() => "");
|
|
10823
11199
|
for (const bin of extractAbsoluteHaiveBins2(text)) {
|
|
10824
11200
|
const version = versionForBinary2(bin);
|
|
@@ -10935,7 +11311,7 @@ async function resolveCiDiffRange(root) {
|
|
|
10935
11311
|
}
|
|
10936
11312
|
async function resolveGithubEventRange(root) {
|
|
10937
11313
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
10938
|
-
if (!eventPath || !
|
|
11314
|
+
if (!eventPath || !existsSync41(eventPath)) return null;
|
|
10939
11315
|
try {
|
|
10940
11316
|
const event = JSON.parse(await readFile18(eventPath, "utf8"));
|
|
10941
11317
|
const prBase = cleanGitSha(event.pull_request?.base?.sha);
|
|
@@ -11040,7 +11416,7 @@ function isShippablePath(file) {
|
|
|
11040
11416
|
}
|
|
11041
11417
|
var CI_SKIP_DIRECTIVE = /\[skip ci\]|\[ci skip\]|\[no ci\]|\[skip actions\]|\*\*\*NO_CI\*\*\*|skip-checks: *true/i;
|
|
11042
11418
|
async function checkCommitMessageSkipCi(root, msgfile) {
|
|
11043
|
-
const file =
|
|
11419
|
+
const file = path39.isAbsolute(msgfile) ? msgfile : path39.join(root, msgfile);
|
|
11044
11420
|
const raw = await readFile18(file, "utf8").catch(() => "");
|
|
11045
11421
|
const cleaned = raw.split("\n").filter((line) => !line.startsWith("#")).join("\n");
|
|
11046
11422
|
if (!CI_SKIP_DIRECTIVE.test(cleaned)) return { block: false, message: "" };
|
|
@@ -11069,7 +11445,7 @@ async function inspectReleaseVersionState(root, upstream) {
|
|
|
11069
11445
|
}
|
|
11070
11446
|
async function readPackageVersion(root, relPath) {
|
|
11071
11447
|
try {
|
|
11072
|
-
const data = JSON.parse(await readFile18(
|
|
11448
|
+
const data = JSON.parse(await readFile18(path39.join(root, relPath), "utf8"));
|
|
11073
11449
|
return typeof data.version === "string" ? data.version : void 0;
|
|
11074
11450
|
} catch {
|
|
11075
11451
|
return void 0;
|
|
@@ -11257,8 +11633,8 @@ function buildScore(findings, threshold = 80) {
|
|
|
11257
11633
|
};
|
|
11258
11634
|
}
|
|
11259
11635
|
async function installGitEnforcement(root) {
|
|
11260
|
-
const hooksDir =
|
|
11261
|
-
if (!
|
|
11636
|
+
const hooksDir = path39.join(root, ".git", "hooks");
|
|
11637
|
+
if (!existsSync41(path39.join(root, ".git"))) {
|
|
11262
11638
|
ui.warn("No .git directory found; git enforcement hooks skipped.");
|
|
11263
11639
|
return;
|
|
11264
11640
|
}
|
|
@@ -11314,8 +11690,8 @@ _hivelore sync --quiet --since ORIG_HEAD || true
|
|
|
11314
11690
|
}
|
|
11315
11691
|
];
|
|
11316
11692
|
for (const hook of hooks) {
|
|
11317
|
-
const file =
|
|
11318
|
-
if (
|
|
11693
|
+
const file = path39.join(hooksDir, hook.name);
|
|
11694
|
+
if (existsSync41(file)) {
|
|
11319
11695
|
const current = await readFile18(file, "utf8").catch(() => "");
|
|
11320
11696
|
if (current.includes(ENFORCE_HOOK_MARKER)) {
|
|
11321
11697
|
await writeFile25(file, hook.body, "utf8");
|
|
@@ -11332,13 +11708,29 @@ ${hook.body}`, "utf8");
|
|
|
11332
11708
|
ui.success("Installed git hooks: pre-commit, pre-push, commit-msg (blocking) + post-merge, post-rewrite (sync)");
|
|
11333
11709
|
}
|
|
11334
11710
|
async function installCiEnforcement(root) {
|
|
11335
|
-
const workflowPath =
|
|
11336
|
-
await mkdir16(
|
|
11337
|
-
|
|
11338
|
-
|
|
11711
|
+
const workflowPath = path39.join(root, ".github", "workflows", "haive-enforcement.yml");
|
|
11712
|
+
await mkdir16(path39.dirname(workflowPath), { recursive: true });
|
|
11713
|
+
const workflow = renderCiEnforcementWorkflow();
|
|
11714
|
+
if (existsSync41(workflowPath)) {
|
|
11715
|
+
const existing = await readFile18(workflowPath, "utf8");
|
|
11716
|
+
const start = "# haive:enforcement-workflow:start";
|
|
11717
|
+
const end = "# haive:enforcement-workflow:end";
|
|
11718
|
+
const startAt = existing.indexOf(start);
|
|
11719
|
+
const endAt = existing.indexOf(end);
|
|
11720
|
+
if (startAt >= 0 && endAt > startAt) {
|
|
11721
|
+
await writeFile25(workflowPath, existing.slice(0, startAt) + workflow + existing.slice(endAt + end.length), "utf8");
|
|
11722
|
+
ui.success(`Updated ${path39.relative(root, workflowPath)} managed block`);
|
|
11723
|
+
} else {
|
|
11724
|
+
ui.info("GitHub Actions enforcement workflow already exists without Hivelore markers \u2014 preserved");
|
|
11725
|
+
}
|
|
11339
11726
|
return;
|
|
11340
11727
|
}
|
|
11341
|
-
await writeFile25(workflowPath,
|
|
11728
|
+
await writeFile25(workflowPath, workflow, "utf8");
|
|
11729
|
+
ui.success(`Created ${path39.relative(root, workflowPath)}`);
|
|
11730
|
+
}
|
|
11731
|
+
function renderCiEnforcementWorkflow() {
|
|
11732
|
+
return `# haive:enforcement-workflow:start
|
|
11733
|
+
name: haive-enforcement
|
|
11342
11734
|
|
|
11343
11735
|
on:
|
|
11344
11736
|
pull_request:
|
|
@@ -11350,6 +11742,7 @@ jobs:
|
|
|
11350
11742
|
runs-on: ubuntu-latest
|
|
11351
11743
|
permissions:
|
|
11352
11744
|
contents: read
|
|
11745
|
+
pull-requests: write
|
|
11353
11746
|
steps:
|
|
11354
11747
|
- uses: actions/checkout@v4
|
|
11355
11748
|
with:
|
|
@@ -11360,12 +11753,51 @@ jobs:
|
|
|
11360
11753
|
- name: Install Hivelore
|
|
11361
11754
|
run: npm install -g @hivelore/cli
|
|
11362
11755
|
- name: Enforce Hivelore policy
|
|
11756
|
+
id: gate
|
|
11363
11757
|
env:
|
|
11364
11758
|
HAIVE_BASE_SHA: \${{ github.event.pull_request.base.sha || github.event.before }}
|
|
11365
11759
|
HAIVE_HEAD_SHA: \${{ github.event.pull_request.head.sha || github.sha }}
|
|
11366
|
-
run:
|
|
11367
|
-
|
|
11368
|
-
|
|
11760
|
+
run: |
|
|
11761
|
+
set +e
|
|
11762
|
+
hivelore enforce ci --json > "$RUNNER_TEMP/haive-gate.json"
|
|
11763
|
+
echo "exit_code=$?" >> "$GITHUB_OUTPUT"
|
|
11764
|
+
exit 0
|
|
11765
|
+
- name: Upsert prevention receipt
|
|
11766
|
+
if: always() && github.event_name == 'pull_request'
|
|
11767
|
+
env:
|
|
11768
|
+
GH_TOKEN: \${{ github.token }}
|
|
11769
|
+
PR_NUMBER: \${{ github.event.pull_request.number }}
|
|
11770
|
+
run: |
|
|
11771
|
+
if [ -z "\${GH_TOKEN:-}" ] || ! command -v gh >/dev/null 2>&1; then exit 0; fi
|
|
11772
|
+
receipt="$(hivelore stats receipt --since 7d --json 2>/dev/null)" || exit 0
|
|
11773
|
+
gate="$(cat "$RUNNER_TEMP/haive-gate.json" 2>/dev/null)" || gate='{"findings":[]}'
|
|
11774
|
+
body="$(jq -nr --arg marker '<!-- haive:prevention-receipt -->' --argjson receipt "$receipt" --argjson gate "$gate" '
|
|
11775
|
+
$marker + "
|
|
11776
|
+
## Hivelore prevention receipt
|
|
11777
|
+
|
|
11778
|
+
" +
|
|
11779
|
+
(([$gate.findings[]? | select(.code == "sensor-block" or .code == "sensor-warn") |
|
|
11780
|
+
"- **" + (.memory_ids[0] // "sensor") + "** \u2014 " + .message] | if length == 0 then
|
|
11781
|
+
"No documented sensor fired on this PR." else "### Fired on this PR
|
|
11782
|
+
" + join("
|
|
11783
|
+
") end)) +
|
|
11784
|
+
"
|
|
11785
|
+
|
|
11786
|
+
Weekly total: **" + ($receipt.total|tostring) + "** refused; previous window: **" +
|
|
11787
|
+
($receipt.previous_total|tostring) + "**."
|
|
11788
|
+
')" || exit 0
|
|
11789
|
+
comments="$(gh api "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" --paginate 2>/dev/null)" || exit 0
|
|
11790
|
+
comment_id="$(printf '%s' "$comments" | jq -r '.[] | select(.body | contains("<!-- haive:prevention-receipt -->")) | .id' | head -1)"
|
|
11791
|
+
if [ -n "$comment_id" ]; then
|
|
11792
|
+
gh api --method PATCH "repos/$GITHUB_REPOSITORY/issues/comments/$comment_id" -f body="$body" >/dev/null 2>&1 || true
|
|
11793
|
+
else
|
|
11794
|
+
gh api --method POST "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" -f body="$body" >/dev/null 2>&1 || true
|
|
11795
|
+
fi
|
|
11796
|
+
- name: Fail when enforcement blocked
|
|
11797
|
+
if: steps.gate.outputs.exit_code != '0'
|
|
11798
|
+
run: exit \${{ steps.gate.outputs.exit_code }}
|
|
11799
|
+
# haive:enforcement-workflow:end
|
|
11800
|
+
`;
|
|
11369
11801
|
}
|
|
11370
11802
|
function printReport(report, json, explain = false) {
|
|
11371
11803
|
if (json) {
|
|
@@ -11469,15 +11901,15 @@ function extractToolPaths(payload, root) {
|
|
|
11469
11901
|
}
|
|
11470
11902
|
function normalizeToolPath(file, root) {
|
|
11471
11903
|
const normalized = file.replace(/\\/g, "/");
|
|
11472
|
-
if (!
|
|
11473
|
-
return
|
|
11904
|
+
if (!path39.isAbsolute(normalized)) return normalized.replace(/^\.\//, "");
|
|
11905
|
+
return path39.relative(root, normalized).replace(/\\/g, "/");
|
|
11474
11906
|
}
|
|
11475
11907
|
async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
|
|
11476
|
-
if (!
|
|
11908
|
+
if (!existsSync41(paths.memoriesDir)) return [];
|
|
11477
11909
|
const marker = await readRecentBriefingMarker(paths, sessionId);
|
|
11478
11910
|
const consulted = new Set(marker?.memory_ids ?? []);
|
|
11479
11911
|
const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention", "attempt"]);
|
|
11480
|
-
const all = await
|
|
11912
|
+
const all = await loadMemoriesFromDir14(paths.memoriesDir);
|
|
11481
11913
|
return all.filter(({ memory: memory2 }) => {
|
|
11482
11914
|
const fm = memory2.frontmatter;
|
|
11483
11915
|
if (!policyTypes.has(fm.type)) return false;
|
|
@@ -11518,7 +11950,7 @@ async function readStdin2(maxBytes) {
|
|
|
11518
11950
|
}
|
|
11519
11951
|
var ATOMIC_STAGE_EXCLUDE = ["/.usage/", "/.runtime/", "/.cache/"];
|
|
11520
11952
|
async function stageResyncedArtifacts(root, paths) {
|
|
11521
|
-
const aiRel =
|
|
11953
|
+
const aiRel = path39.relative(root, paths.haiveDir);
|
|
11522
11954
|
const out = await runCommand2("git", ["diff", "--name-only", "--", aiRel], root).catch(() => "");
|
|
11523
11955
|
const toStage = out.split("\n").map((line) => line.trim()).filter(Boolean).filter((file) => !ATOMIC_STAGE_EXCLUDE.some((excl) => `/${file}`.includes(excl)));
|
|
11524
11956
|
if (toStage.length === 0) return;
|
|
@@ -11545,14 +11977,14 @@ function runCommand2(cmd, args, cwd) {
|
|
|
11545
11977
|
}
|
|
11546
11978
|
|
|
11547
11979
|
// src/commands/release.ts
|
|
11548
|
-
import { existsSync as
|
|
11980
|
+
import { existsSync as existsSync42 } from "fs";
|
|
11549
11981
|
import { readFile as readFile19, writeFile as writeFile26 } from "fs/promises";
|
|
11550
|
-
import
|
|
11982
|
+
import path40 from "path";
|
|
11551
11983
|
import "commander";
|
|
11552
11984
|
import { findProjectRoot as findProjectRoot38 } from "@hivelore/core";
|
|
11553
|
-
import { execFile as
|
|
11554
|
-
import { promisify as
|
|
11555
|
-
var
|
|
11985
|
+
import { execFile as execFile6 } from "child_process";
|
|
11986
|
+
import { promisify as promisify6 } from "util";
|
|
11987
|
+
var exec4 = promisify6(execFile6);
|
|
11556
11988
|
var VERSION_FILES2 = [
|
|
11557
11989
|
"package.json",
|
|
11558
11990
|
"packages/core/package.json",
|
|
@@ -11561,7 +11993,7 @@ var VERSION_FILES2 = [
|
|
|
11561
11993
|
"packages/embeddings/package.json"
|
|
11562
11994
|
];
|
|
11563
11995
|
async function readCurrentVersion(root) {
|
|
11564
|
-
const pkg = JSON.parse(await readFile19(
|
|
11996
|
+
const pkg = JSON.parse(await readFile19(path40.join(root, "package.json"), "utf8"));
|
|
11565
11997
|
if (!pkg.version) throw new Error("Root package.json has no version field.");
|
|
11566
11998
|
return pkg.version;
|
|
11567
11999
|
}
|
|
@@ -11582,8 +12014,8 @@ function registerRelease(program2) {
|
|
|
11582
12014
|
const current = await readCurrentVersion(root);
|
|
11583
12015
|
const next = nextVersion(current, spec);
|
|
11584
12016
|
for (const rel of VERSION_FILES2) {
|
|
11585
|
-
const file =
|
|
11586
|
-
if (!
|
|
12017
|
+
const file = path40.join(root, rel);
|
|
12018
|
+
if (!existsSync42(file)) {
|
|
11587
12019
|
ui.warn(`skip ${rel} (missing)`);
|
|
11588
12020
|
continue;
|
|
11589
12021
|
}
|
|
@@ -11596,8 +12028,8 @@ function registerRelease(program2) {
|
|
|
11596
12028
|
}
|
|
11597
12029
|
await writeFile26(file, updated, "utf8");
|
|
11598
12030
|
}
|
|
11599
|
-
const changelog =
|
|
11600
|
-
if (
|
|
12031
|
+
const changelog = path40.join(root, "CHANGELOG.md");
|
|
12032
|
+
if (existsSync42(changelog)) {
|
|
11601
12033
|
const raw = await readFile19(changelog, "utf8");
|
|
11602
12034
|
const heading = `## [${next}]${opts.title ? ` \u2014 ${opts.title}` : ""}`;
|
|
11603
12035
|
if (!raw.includes(`## [${next}]`)) {
|
|
@@ -11620,8 +12052,8 @@ ${heading}
|
|
|
11620
12052
|
const root = findProjectRoot38(opts.dir);
|
|
11621
12053
|
const version = await readCurrentVersion(root);
|
|
11622
12054
|
for (const rel of VERSION_FILES2.slice(1)) {
|
|
11623
|
-
const file =
|
|
11624
|
-
if (!
|
|
12055
|
+
const file = path40.join(root, rel);
|
|
12056
|
+
if (!existsSync42(file)) continue;
|
|
11625
12057
|
const v = JSON.parse(await readFile19(file, "utf8")).version;
|
|
11626
12058
|
if (v !== version) {
|
|
11627
12059
|
ui.error(`${rel} is at ${v}, root at ${version} \u2014 lockstep broken; run \`hivelore release bump\` first.`);
|
|
@@ -11629,24 +12061,24 @@ ${heading}
|
|
|
11629
12061
|
return;
|
|
11630
12062
|
}
|
|
11631
12063
|
}
|
|
11632
|
-
const dirty = (await
|
|
12064
|
+
const dirty = (await exec4("git", ["status", "--porcelain"], { cwd: root })).stdout.trim();
|
|
11633
12065
|
if (dirty.length > 0) {
|
|
11634
12066
|
ui.error("Working tree is not clean \u2014 commit the bump before tagging.");
|
|
11635
12067
|
process.exitCode = 1;
|
|
11636
12068
|
return;
|
|
11637
12069
|
}
|
|
11638
12070
|
const tag = `v${version}`;
|
|
11639
|
-
const existing = (await
|
|
12071
|
+
const existing = (await exec4("git", ["tag", "--list", tag], { cwd: root })).stdout.trim();
|
|
11640
12072
|
if (existing) {
|
|
11641
12073
|
ui.error(`Tag ${tag} already exists \u2014 bump the version first.`);
|
|
11642
12074
|
process.exitCode = 1;
|
|
11643
12075
|
return;
|
|
11644
12076
|
}
|
|
11645
|
-
await
|
|
12077
|
+
await exec4("git", ["tag", tag], { cwd: root });
|
|
11646
12078
|
ui.success(`Created ${tag} at HEAD.`);
|
|
11647
12079
|
if (opts.push !== false) {
|
|
11648
|
-
await
|
|
11649
|
-
await
|
|
12080
|
+
await exec4("git", ["push"], { cwd: root });
|
|
12081
|
+
await exec4("git", ["push", "origin", tag], { cwd: root });
|
|
11650
12082
|
ui.success(`Pushed branch and ${tag}.`);
|
|
11651
12083
|
ui.info("Next: `hivelore enforce finish --wait` (polls CI), then publish via `pnpm run publish:all` (human step).");
|
|
11652
12084
|
}
|
|
@@ -11666,29 +12098,34 @@ function registerRun(program2) {
|
|
|
11666
12098
|
}
|
|
11667
12099
|
|
|
11668
12100
|
// src/commands/sensors.ts
|
|
11669
|
-
import { execFile as
|
|
11670
|
-
import { existsSync as
|
|
12101
|
+
import { execFile as execFile7 } from "child_process";
|
|
12102
|
+
import { existsSync as existsSync43 } from "fs";
|
|
11671
12103
|
import { chmod as chmod2, mkdir as mkdir17, readFile as readFile20, writeFile as writeFile27 } from "fs/promises";
|
|
11672
|
-
import
|
|
11673
|
-
import { promisify as
|
|
12104
|
+
import path41 from "path";
|
|
12105
|
+
import { promisify as promisify7 } from "util";
|
|
11674
12106
|
import "commander";
|
|
11675
12107
|
import {
|
|
11676
12108
|
extractSensorExamples,
|
|
12109
|
+
appendSensorEvaluations as appendSensorEvaluations2,
|
|
12110
|
+
assessSensorHealth as assessSensorHealth4,
|
|
11677
12111
|
findProjectRoot as findProjectRoot39,
|
|
11678
12112
|
isRetiredMemory as isRetiredMemory3,
|
|
11679
12113
|
judgeProposedSensor,
|
|
11680
12114
|
loadConfig as loadConfig13,
|
|
11681
|
-
|
|
12115
|
+
loadSensorLedger as loadSensorLedger4,
|
|
12116
|
+
loadMemoriesFromDir as loadMemoriesFromDir15,
|
|
11682
12117
|
recordPreventionHits as recordPreventionHits2,
|
|
11683
12118
|
resolveHaivePaths as resolveHaivePaths36,
|
|
11684
12119
|
runSensors as runSensors2,
|
|
11685
12120
|
selectCommandSensors as selectCommandSensors2,
|
|
11686
12121
|
sensorPatternBrittleness as sensorPatternBrittleness2,
|
|
11687
12122
|
sensorSelfCheck as sensorSelfCheck2,
|
|
12123
|
+
sensorAppliesToPath as sensorAppliesToPath3,
|
|
11688
12124
|
scannableSensorTargets,
|
|
11689
|
-
serializeMemory as serializeMemory15
|
|
12125
|
+
serializeMemory as serializeMemory15,
|
|
12126
|
+
withoutQuarantineNote
|
|
11690
12127
|
} from "@hivelore/core";
|
|
11691
|
-
var
|
|
12128
|
+
var exec5 = promisify7(execFile7);
|
|
11692
12129
|
function registerSensors(program2) {
|
|
11693
12130
|
const sensors = program2.command("sensors").description("Operate executable sensors derived from Hivelore memories");
|
|
11694
12131
|
sensors.command("list").description("List memories carrying executable sensors").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
@@ -11724,7 +12161,7 @@ function registerSensors(program2) {
|
|
|
11724
12161
|
const root = findProjectRoot39(opts.dir);
|
|
11725
12162
|
const paths = resolveHaivePaths36(root);
|
|
11726
12163
|
const memories = await runnableSensorMemories(paths);
|
|
11727
|
-
const diff = opts.diffFile ? await readFile20(
|
|
12164
|
+
const diff = opts.diffFile ? await readFile20(path41.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
|
|
11728
12165
|
const targets = scannableSensorTargets(diff);
|
|
11729
12166
|
const hits = runSensors2(memories, targets);
|
|
11730
12167
|
const config = await loadConfig13(paths);
|
|
@@ -11735,12 +12172,44 @@ function registerSensors(program2) {
|
|
|
11735
12172
|
const commandHits = [];
|
|
11736
12173
|
const commandUnrunnable = [];
|
|
11737
12174
|
const commandSkipped = [];
|
|
12175
|
+
const ledgerRows = [];
|
|
12176
|
+
const headSha = await gitHeadSha(root);
|
|
12177
|
+
for (const memory2 of memories) {
|
|
12178
|
+
const sensor = memory2.frontmatter.sensor;
|
|
12179
|
+
if (!sensor || sensor.kind !== "regex") continue;
|
|
12180
|
+
const anchors = memory2.frontmatter.anchor.paths;
|
|
12181
|
+
const applicable = targets.some((target) => sensorAppliesToPath3(sensor, anchors, target.path));
|
|
12182
|
+
if (!applicable) continue;
|
|
12183
|
+
ledgerRows.push(evaluation({
|
|
12184
|
+
memory_id: memory2.frontmatter.id,
|
|
12185
|
+
kind: "regex",
|
|
12186
|
+
stage: "manual",
|
|
12187
|
+
head_sha: headSha,
|
|
12188
|
+
scope_hash: "",
|
|
12189
|
+
outcome: hits.some((hit) => hit.memory_id === memory2.frontmatter.id) ? "fired" : "silent"
|
|
12190
|
+
}));
|
|
12191
|
+
}
|
|
11738
12192
|
if (commandSpecs.length > 0 && runCommands) {
|
|
11739
|
-
|
|
12193
|
+
const runs = await executeCommandSensors(commandSpecs, root);
|
|
12194
|
+
for (const run of runs) {
|
|
12195
|
+
const spec = commandSpecs.find((candidate) => candidate.memory_id === run.memory_id);
|
|
12196
|
+
ledgerRows.push(evaluation({
|
|
12197
|
+
memory_id: run.memory_id,
|
|
12198
|
+
kind: run.kind,
|
|
12199
|
+
stage: "manual",
|
|
12200
|
+
head_sha: headSha,
|
|
12201
|
+
scope_hash: await commandScopeHash(root, spec),
|
|
12202
|
+
outcome: run.status === "failed" ? "fired" : run.status === "passed" ? "silent" : "unrunnable"
|
|
12203
|
+
}, { exit_code: run.exit_code, duration_ms: run.duration_ms }));
|
|
12204
|
+
}
|
|
12205
|
+
const prior = await loadSensorLedger4(paths);
|
|
12206
|
+
const health = new Map(assessSensorHealth4([...prior, ...ledgerRows]).map((h) => [h.memory_id, h]));
|
|
12207
|
+
for (const run of runs) {
|
|
12208
|
+
const quarantined = health.get(run.memory_id)?.quarantine_pending === true;
|
|
11740
12209
|
if (run.status === "failed") {
|
|
11741
12210
|
commandHits.push({
|
|
11742
12211
|
memory_id: run.memory_id,
|
|
11743
|
-
severity: run.severity,
|
|
12212
|
+
severity: quarantined ? "warn" : run.severity,
|
|
11744
12213
|
message: run.message,
|
|
11745
12214
|
matched_line: `command failed (exit ${run.exit_code}, ${run.duration_ms}ms): ${run.command}`,
|
|
11746
12215
|
...run.output_tail ? { output_tail: run.output_tail } : {}
|
|
@@ -11752,8 +12221,17 @@ function registerSensors(program2) {
|
|
|
11752
12221
|
} else if (commandSpecs.length > 0) {
|
|
11753
12222
|
for (const spec of commandSpecs) commandSkipped.push(spec.memory_id);
|
|
11754
12223
|
}
|
|
12224
|
+
await appendSensorEvaluations2(paths, ledgerRows);
|
|
11755
12225
|
const firedIds = [...new Set([...hits, ...commandHits].map((hit) => hit.memory_id))];
|
|
11756
|
-
|
|
12226
|
+
const preventionDetails = Object.fromEntries([
|
|
12227
|
+
...hits.map((hit) => [hit.memory_id, { kind: "regex", stage: "manual" }]),
|
|
12228
|
+
...commandHits.map((hit) => [hit.memory_id, {
|
|
12229
|
+
kind: commandSpecs.find((spec) => spec.memory_id === hit.memory_id)?.kind ?? "shell",
|
|
12230
|
+
stage: "manual",
|
|
12231
|
+
exit_code: Number(/exit (\d+)/.exec(hit.matched_line)?.[1] ?? 1)
|
|
12232
|
+
}])
|
|
12233
|
+
]);
|
|
12234
|
+
await recordPreventionHits2(paths, firedIds, "sensor", /* @__PURE__ */ new Date(), preventionDetails);
|
|
11757
12235
|
const output = {
|
|
11758
12236
|
scanned: memories.length,
|
|
11759
12237
|
hits: hits.map((hit) => ({
|
|
@@ -11812,7 +12290,7 @@ function registerSensors(program2) {
|
|
|
11812
12290
|
}
|
|
11813
12291
|
const root = findProjectRoot39(opts.dir);
|
|
11814
12292
|
const paths = resolveHaivePaths36(root);
|
|
11815
|
-
const loaded =
|
|
12293
|
+
const loaded = existsSync43(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
|
|
11816
12294
|
const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
|
|
11817
12295
|
if (!found) {
|
|
11818
12296
|
ui.error(`No memory found with id ${id}`);
|
|
@@ -11860,7 +12338,7 @@ function registerSensors(program2) {
|
|
|
11860
12338
|
...found.memory.frontmatter,
|
|
11861
12339
|
sensor: { ...sensor, severity }
|
|
11862
12340
|
},
|
|
11863
|
-
body: found.memory.body
|
|
12341
|
+
body: severity === "block" ? withoutQuarantineNote(found.memory.body) : found.memory.body
|
|
11864
12342
|
};
|
|
11865
12343
|
await writeFile27(found.filePath, serializeMemory15(next), "utf8");
|
|
11866
12344
|
ui.success(`Updated ${id}: sensor severity=${severity}`);
|
|
@@ -11877,7 +12355,7 @@ function registerSensors(program2) {
|
|
|
11877
12355
|
return;
|
|
11878
12356
|
}
|
|
11879
12357
|
const root2 = findProjectRoot39(opts.dir);
|
|
11880
|
-
const { proposeSensor } = await import("./server-
|
|
12358
|
+
const { proposeSensor } = await import("./server-2FUYM6KI.js");
|
|
11881
12359
|
const out = await proposeSensor(
|
|
11882
12360
|
{
|
|
11883
12361
|
memory_id: id,
|
|
@@ -11920,7 +12398,7 @@ function registerSensors(program2) {
|
|
|
11920
12398
|
}
|
|
11921
12399
|
const root = findProjectRoot39(opts.dir);
|
|
11922
12400
|
const paths = resolveHaivePaths36(root);
|
|
11923
|
-
const loaded =
|
|
12401
|
+
const loaded = existsSync43(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
|
|
11924
12402
|
const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
|
|
11925
12403
|
if (!found) {
|
|
11926
12404
|
ui.error(`No memory found with id ${id}`);
|
|
@@ -11974,13 +12452,13 @@ function registerSensors(program2) {
|
|
|
11974
12452
|
const root = findProjectRoot39(opts.dir);
|
|
11975
12453
|
const paths = resolveHaivePaths36(root);
|
|
11976
12454
|
const rows = await sensorRows(paths);
|
|
11977
|
-
const outDir =
|
|
12455
|
+
const outDir = path41.resolve(root, opts.outDir ?? ".ai/generated");
|
|
11978
12456
|
await mkdir17(outDir, { recursive: true });
|
|
11979
|
-
const outPath =
|
|
12457
|
+
const outPath = path41.join(outDir, format === "grep" ? "haive-sensors-grep.sh" : "haive-sensors-eslint.json");
|
|
11980
12458
|
const content = format === "grep" ? renderGrepScript(rows) : JSON.stringify({ sensors: rows }, null, 2) + "\n";
|
|
11981
12459
|
await writeFile27(outPath, content, "utf8");
|
|
11982
12460
|
if (format === "grep") await chmod2(outPath, 493);
|
|
11983
|
-
ui.success(`Exported ${rows.length} sensor(s): ${
|
|
12461
|
+
ui.success(`Exported ${rows.length} sensor(s): ${path41.relative(root, outPath)}`);
|
|
11984
12462
|
});
|
|
11985
12463
|
}
|
|
11986
12464
|
function deriveProposedMessage(body, pattern, absent) {
|
|
@@ -12013,8 +12491,8 @@ async function sensorRows(paths) {
|
|
|
12013
12491
|
});
|
|
12014
12492
|
}
|
|
12015
12493
|
async function runnableSensorMemories(paths, regexOnly = true) {
|
|
12016
|
-
if (!
|
|
12017
|
-
const loaded = await
|
|
12494
|
+
if (!existsSync43(paths.memoriesDir)) return [];
|
|
12495
|
+
const loaded = await loadMemoriesFromDir15(paths.memoriesDir);
|
|
12018
12496
|
return loaded.map(({ memory: memory2 }) => memory2).filter((memory2) => {
|
|
12019
12497
|
const sensor = memory2.frontmatter.sensor;
|
|
12020
12498
|
if (!sensor) return false;
|
|
@@ -12024,7 +12502,7 @@ async function runnableSensorMemories(paths, regexOnly = true) {
|
|
|
12024
12502
|
}
|
|
12025
12503
|
async function stagedDiff(root) {
|
|
12026
12504
|
try {
|
|
12027
|
-
const { stdout } = await
|
|
12505
|
+
const { stdout } = await exec5("git", ["diff", "--cached"], { cwd: root });
|
|
12028
12506
|
return stdout;
|
|
12029
12507
|
} catch (err) {
|
|
12030
12508
|
throw new Error(`git diff --cached failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -12055,15 +12533,15 @@ function shellQuote(value) {
|
|
|
12055
12533
|
}
|
|
12056
12534
|
|
|
12057
12535
|
// src/commands/ingest.ts
|
|
12058
|
-
import { existsSync as
|
|
12536
|
+
import { existsSync as existsSync44 } from "fs";
|
|
12059
12537
|
import { mkdir as mkdir18, readFile as readFile21, writeFile as writeFile28 } from "fs/promises";
|
|
12060
|
-
import
|
|
12538
|
+
import path42 from "path";
|
|
12061
12539
|
import "commander";
|
|
12062
12540
|
import {
|
|
12063
12541
|
draftsFromFindings,
|
|
12064
12542
|
filterNewDrafts,
|
|
12065
12543
|
findProjectRoot as findProjectRoot40,
|
|
12066
|
-
loadMemoriesFromDir as
|
|
12544
|
+
loadMemoriesFromDir as loadMemoriesFromDir16,
|
|
12067
12545
|
memoryFilePath as memoryFilePath7,
|
|
12068
12546
|
parseFindings,
|
|
12069
12547
|
resolveHaivePaths as resolveHaivePaths37,
|
|
@@ -12093,7 +12571,7 @@ function registerIngest(program2) {
|
|
|
12093
12571
|
}
|
|
12094
12572
|
const root = findProjectRoot40(opts.dir);
|
|
12095
12573
|
const paths = resolveHaivePaths37(root);
|
|
12096
|
-
if (!
|
|
12574
|
+
if (!existsSync44(paths.haiveDir)) {
|
|
12097
12575
|
ui.error(`No .ai/ found at ${root}. Run \`hivelore init\` first.`);
|
|
12098
12576
|
process.exitCode = 1;
|
|
12099
12577
|
return;
|
|
@@ -12114,8 +12592,8 @@ function registerIngest(program2) {
|
|
|
12114
12592
|
process.exitCode = 1;
|
|
12115
12593
|
return;
|
|
12116
12594
|
}
|
|
12117
|
-
const reportPath =
|
|
12118
|
-
if (!
|
|
12595
|
+
const reportPath = path42.resolve(root, file);
|
|
12596
|
+
if (!existsSync44(reportPath)) {
|
|
12119
12597
|
ui.error(`Report file not found: ${reportPath}`);
|
|
12120
12598
|
process.exitCode = 1;
|
|
12121
12599
|
return;
|
|
@@ -12147,7 +12625,7 @@ function registerIngest(program2) {
|
|
|
12147
12625
|
process.exitCode = 1;
|
|
12148
12626
|
return;
|
|
12149
12627
|
}
|
|
12150
|
-
const existing =
|
|
12628
|
+
const existing = existsSync44(paths.memoriesDir) ? await loadMemoriesFromDir16(paths.memoriesDir) : [];
|
|
12151
12629
|
const existingTopics = new Set(
|
|
12152
12630
|
existing.map(({ memory: memory2 }) => memory2.frontmatter.topic).filter((t) => Boolean(t))
|
|
12153
12631
|
);
|
|
@@ -12209,13 +12687,13 @@ function registerIngest(program2) {
|
|
|
12209
12687
|
await writeDraft(paths, draft);
|
|
12210
12688
|
created++;
|
|
12211
12689
|
}
|
|
12212
|
-
ui.success(`Created ${created} proposed memory(ies) under ${
|
|
12690
|
+
ui.success(`Created ${created} proposed memory(ies) under ${path42.relative(root, paths.memoriesDir)}/`);
|
|
12213
12691
|
ui.info("Review with `hivelore memory pending`; promote sensors with `hivelore sensors promote <id> --yes`.");
|
|
12214
12692
|
});
|
|
12215
12693
|
}
|
|
12216
12694
|
async function writeDraft(paths, draft) {
|
|
12217
12695
|
const file = memoryFilePath7(paths, draft.frontmatter.scope, draft.frontmatter.id, draft.frontmatter.module);
|
|
12218
|
-
await mkdir18(
|
|
12696
|
+
await mkdir18(path42.dirname(file), { recursive: true });
|
|
12219
12697
|
await writeFile28(file, serializeMemory16({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
|
|
12220
12698
|
return file;
|
|
12221
12699
|
}
|
|
@@ -12257,14 +12735,14 @@ async function fetchSonarIssues(opts) {
|
|
|
12257
12735
|
}
|
|
12258
12736
|
|
|
12259
12737
|
// src/commands/dashboard.ts
|
|
12260
|
-
import { existsSync as
|
|
12738
|
+
import { existsSync as existsSync45 } from "fs";
|
|
12261
12739
|
import "commander";
|
|
12262
12740
|
import {
|
|
12263
12741
|
buildDashboard,
|
|
12264
12742
|
findProjectRoot as findProjectRoot41,
|
|
12265
12743
|
loadConfig as loadConfig14,
|
|
12266
|
-
loadMemoriesFromDir as
|
|
12267
|
-
loadPreventionEvents as
|
|
12744
|
+
loadMemoriesFromDir as loadMemoriesFromDir17,
|
|
12745
|
+
loadPreventionEvents as loadPreventionEvents4,
|
|
12268
12746
|
loadUsageIndex as loadUsageIndex16,
|
|
12269
12747
|
resolveHaivePaths as resolveHaivePaths38
|
|
12270
12748
|
} from "@hivelore/core";
|
|
@@ -12274,14 +12752,14 @@ function registerDashboard(program2) {
|
|
|
12274
12752
|
).option("--json", "emit the full report as JSON", false).option("--top <n>", "rows per top-list", "10").option("--dormant-days <n>", "dormancy window for impact scoring").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
12275
12753
|
const root = findProjectRoot41(opts.dir);
|
|
12276
12754
|
const paths = resolveHaivePaths38(root);
|
|
12277
|
-
if (!
|
|
12755
|
+
if (!existsSync45(paths.haiveDir)) {
|
|
12278
12756
|
ui.error(`No .ai/ found at ${root}. Run \`hivelore init\` first.`);
|
|
12279
12757
|
process.exitCode = 1;
|
|
12280
12758
|
return;
|
|
12281
12759
|
}
|
|
12282
|
-
const memories =
|
|
12760
|
+
const memories = existsSync45(paths.memoriesDir) ? await loadMemoriesFromDir17(paths.memoriesDir) : [];
|
|
12283
12761
|
const usage = await loadUsageIndex16(paths);
|
|
12284
|
-
const preventionEvents = await
|
|
12762
|
+
const preventionEvents = await loadPreventionEvents4(paths);
|
|
12285
12763
|
const config = await loadConfig14(paths);
|
|
12286
12764
|
const top = Math.max(1, Number.parseInt(opts.top ?? "10", 10) || 10);
|
|
12287
12765
|
const dormantDays = opts.dormantDays ? Number.parseInt(opts.dormantDays, 10) : void 0;
|
|
@@ -12403,30 +12881,30 @@ function warnNum(n) {
|
|
|
12403
12881
|
}
|
|
12404
12882
|
|
|
12405
12883
|
// src/commands/dev-link.ts
|
|
12406
|
-
import { execFile as
|
|
12884
|
+
import { execFile as execFile8 } from "child_process";
|
|
12407
12885
|
import { cp, readFile as readFile22 } from "fs/promises";
|
|
12408
|
-
import { existsSync as
|
|
12409
|
-
import
|
|
12410
|
-
import { promisify as
|
|
12886
|
+
import { existsSync as existsSync46 } from "fs";
|
|
12887
|
+
import path43 from "path";
|
|
12888
|
+
import { promisify as promisify8 } from "util";
|
|
12411
12889
|
import "commander";
|
|
12412
12890
|
import { findProjectRoot as findProjectRoot42 } from "@hivelore/core";
|
|
12413
|
-
var
|
|
12891
|
+
var exec6 = promisify8(execFile8);
|
|
12414
12892
|
function registerDevLink(program2) {
|
|
12415
12893
|
const dev = program2.commands.find((c) => c.name() === "dev") ?? program2.command("dev").description("Developer utilities for working on Hivelore itself.");
|
|
12416
12894
|
dev.command("link").description("Hot-swap this repo's built dist into the global @hivelore (or legacy @hiveai) install so the global binary runs your local code.").option("-d, --dir <dir>", "repo root (default: discovered from cwd)").option("--json", "emit a machine-readable summary", false).action(async (opts) => {
|
|
12417
12895
|
const root = findProjectRoot42(opts.dir);
|
|
12418
|
-
if (!
|
|
12896
|
+
if (!existsSync46(path43.join(root, "packages", "cli", "dist", "index.js"))) {
|
|
12419
12897
|
ui.error(`Not the Hivelore monorepo (no packages/cli/dist) at ${root}. Run \`pnpm -r build\` first, or pass --dir.`);
|
|
12420
12898
|
process.exitCode = 1;
|
|
12421
12899
|
return;
|
|
12422
12900
|
}
|
|
12423
12901
|
let globalModules;
|
|
12424
12902
|
try {
|
|
12425
|
-
globalModules = (await
|
|
12903
|
+
globalModules = (await exec6("npm", ["root", "-g"])).stdout.trim();
|
|
12426
12904
|
} catch {
|
|
12427
|
-
globalModules =
|
|
12905
|
+
globalModules = path43.join(path43.dirname(path43.dirname(process.execPath)), "lib", "node_modules");
|
|
12428
12906
|
}
|
|
12429
|
-
const scopeDirs = ["@hivelore", "@hiveai"].map((scope) =>
|
|
12907
|
+
const scopeDirs = ["@hivelore", "@hiveai"].map((scope) => path43.join(globalModules, scope)).filter((dir) => existsSync46(dir));
|
|
12430
12908
|
if (scopeDirs.length === 0) {
|
|
12431
12909
|
ui.error(`No global @hivelore (or legacy @hiveai) install under ${globalModules}. Install once with \`npm i -g @hivelore/cli\`, then re-run.`);
|
|
12432
12910
|
process.exitCode = 1;
|
|
@@ -12434,24 +12912,24 @@ function registerDevLink(program2) {
|
|
|
12434
12912
|
}
|
|
12435
12913
|
const linked = [];
|
|
12436
12914
|
const copyDist = async (fromPkg, toDistDir) => {
|
|
12437
|
-
const from =
|
|
12438
|
-
if (!
|
|
12915
|
+
const from = path43.join(root, "packages", fromPkg, "dist");
|
|
12916
|
+
if (!existsSync46(from) || !existsSync46(path43.dirname(toDistDir))) return;
|
|
12439
12917
|
await cp(from, toDistDir, { recursive: true });
|
|
12440
|
-
linked.push(
|
|
12918
|
+
linked.push(path43.relative(globalModules, toDistDir));
|
|
12441
12919
|
};
|
|
12442
12920
|
for (const globalHive of scopeDirs) {
|
|
12443
|
-
const nestedScope =
|
|
12921
|
+
const nestedScope = path43.basename(globalHive);
|
|
12444
12922
|
for (const pkg of ["cli", "mcp"]) {
|
|
12445
|
-
await copyDist(pkg,
|
|
12923
|
+
await copyDist(pkg, path43.join(globalHive, pkg, "dist"));
|
|
12446
12924
|
for (const nested of ["core", "embeddings"]) {
|
|
12447
|
-
await copyDist(nested,
|
|
12925
|
+
await copyDist(nested, path43.join(globalHive, pkg, "node_modules", nestedScope, nested, "dist"));
|
|
12448
12926
|
}
|
|
12449
12927
|
}
|
|
12450
|
-
await copyDist("core",
|
|
12928
|
+
await copyDist("core", path43.join(globalHive, "core", "dist"));
|
|
12451
12929
|
}
|
|
12452
12930
|
let version = "unknown";
|
|
12453
12931
|
try {
|
|
12454
|
-
version = JSON.parse(await readFile22(
|
|
12932
|
+
version = JSON.parse(await readFile22(path43.join(root, "package.json"), "utf8")).version ?? "unknown";
|
|
12455
12933
|
} catch {
|
|
12456
12934
|
}
|
|
12457
12935
|
if (opts.json) {
|
|
@@ -12462,7 +12940,7 @@ function registerDevLink(program2) {
|
|
|
12462
12940
|
ui.warn("Nothing linked \u2014 no matching dist targets were found in the global install.");
|
|
12463
12941
|
return;
|
|
12464
12942
|
}
|
|
12465
|
-
ui.success(`Linked local dist (v${version}) into the global install(s): ${scopeDirs.map((d) =>
|
|
12943
|
+
ui.success(`Linked local dist (v${version}) into the global install(s): ${scopeDirs.map((d) => path43.basename(d)).join(", ")}`);
|
|
12466
12944
|
for (const t of linked) console.log(` ${ui.dim("\u2192")} ${t}`);
|
|
12467
12945
|
console.log(ui.dim("The global binary now runs your local build (git hooks + MCP included)."));
|
|
12468
12946
|
});
|
|
@@ -12470,8 +12948,8 @@ function registerDevLink(program2) {
|
|
|
12470
12948
|
|
|
12471
12949
|
// src/commands/coverage.ts
|
|
12472
12950
|
import { readFile as readFile23 } from "fs/promises";
|
|
12473
|
-
import { existsSync as
|
|
12474
|
-
import
|
|
12951
|
+
import { existsSync as existsSync47 } from "fs";
|
|
12952
|
+
import path44 from "path";
|
|
12475
12953
|
import "commander";
|
|
12476
12954
|
import {
|
|
12477
12955
|
findCoverageGaps,
|
|
@@ -12481,7 +12959,7 @@ import {
|
|
|
12481
12959
|
tallyHotFiles
|
|
12482
12960
|
} from "@hivelore/core";
|
|
12483
12961
|
async function readAgentHotFiles(root, cacheFile, sinceMs) {
|
|
12484
|
-
if (!
|
|
12962
|
+
if (!existsSync47(cacheFile)) return [];
|
|
12485
12963
|
const raw = await readFile23(cacheFile, "utf8").catch(() => "");
|
|
12486
12964
|
const files = [];
|
|
12487
12965
|
for (const line of raw.split("\n")) {
|
|
@@ -12495,7 +12973,7 @@ async function readAgentHotFiles(root, cacheFile, sinceMs) {
|
|
|
12495
12973
|
}
|
|
12496
12974
|
for (const f of obs.files ?? []) {
|
|
12497
12975
|
if (typeof f !== "string" || !f) continue;
|
|
12498
|
-
const rel =
|
|
12976
|
+
const rel = path44.isAbsolute(f) ? path44.relative(root, f) : f;
|
|
12499
12977
|
if (rel.startsWith("..")) continue;
|
|
12500
12978
|
files.push(rel);
|
|
12501
12979
|
}
|
|
@@ -12539,7 +13017,7 @@ function registerCoverage(program2) {
|
|
|
12539
13017
|
}) : null;
|
|
12540
13018
|
const gitHotFiles = (radar?.hotFiles ?? []).filter((h) => !isNoisePath(h.path)).map((h) => ({ path: h.path, changes: h.changes, source: "git" }));
|
|
12541
13019
|
const sinceMs = Date.now() - days * 864e5;
|
|
12542
|
-
const agentHotFiles = useAgent ? (await readAgentHotFiles(root,
|
|
13020
|
+
const agentHotFiles = useAgent ? (await readAgentHotFiles(root, path44.join(paths.haiveDir, ".cache", "observations.jsonl"), sinceMs)).filter((h) => !isNoisePath(h.path)) : [];
|
|
12543
13021
|
const hotFiles = mergeHotFiles(gitHotFiles, agentHotFiles);
|
|
12544
13022
|
const memories = await loadMemoriesFromDir(paths.memoriesDir);
|
|
12545
13023
|
const gaps = findCoverageGaps(hotFiles, memories, { minChanges, limit });
|
|
@@ -12577,8 +13055,8 @@ function registerCoverage(program2) {
|
|
|
12577
13055
|
|
|
12578
13056
|
// src/commands/merge-driver.ts
|
|
12579
13057
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
12580
|
-
import { readFileSync, writeFileSync, existsSync as
|
|
12581
|
-
import
|
|
13058
|
+
import { readFileSync, writeFileSync, existsSync as existsSync48 } from "fs";
|
|
13059
|
+
import path45 from "path";
|
|
12582
13060
|
import "commander";
|
|
12583
13061
|
import { findProjectRoot as findProjectRoot44, mergeMemoryVersions } from "@hivelore/core";
|
|
12584
13062
|
var GITATTRIBUTES_MARK = "# Hivelore merge driver";
|
|
@@ -12610,8 +13088,8 @@ function registerMergeDriver(program2) {
|
|
|
12610
13088
|
process.exitCode = 1;
|
|
12611
13089
|
return;
|
|
12612
13090
|
}
|
|
12613
|
-
const gaPath =
|
|
12614
|
-
let content =
|
|
13091
|
+
const gaPath = path45.join(root, ".gitattributes");
|
|
13092
|
+
let content = existsSync48(gaPath) ? readFileSync(gaPath, "utf8") : "";
|
|
12615
13093
|
if (!content.includes(GITATTRIBUTES_MARK)) {
|
|
12616
13094
|
if (content.length > 0 && !content.endsWith("\n")) content += "\n";
|
|
12617
13095
|
content += GITATTRIBUTES_BLOCK + "\n";
|
|
@@ -12625,21 +13103,21 @@ function registerMergeDriver(program2) {
|
|
|
12625
13103
|
}
|
|
12626
13104
|
|
|
12627
13105
|
// src/commands/bridges.ts
|
|
12628
|
-
import { existsSync as
|
|
12629
|
-
import
|
|
13106
|
+
import { existsSync as existsSync49 } from "fs";
|
|
13107
|
+
import path46 from "path";
|
|
12630
13108
|
import "commander";
|
|
12631
13109
|
import {
|
|
12632
13110
|
findProjectRoot as findProjectRoot45,
|
|
12633
13111
|
resolveHaivePaths as resolveHaivePaths40,
|
|
12634
|
-
BRIDGE_TARGET_PATH as
|
|
12635
|
-
BRIDGE_TARGETS as
|
|
13112
|
+
BRIDGE_TARGET_PATH as BRIDGE_TARGET_PATH3,
|
|
13113
|
+
BRIDGE_TARGETS as BRIDGE_TARGETS4
|
|
12636
13114
|
} from "@hivelore/core";
|
|
12637
13115
|
function registerBridges(program2) {
|
|
12638
13116
|
const bridges = program2.command("bridges").description(
|
|
12639
13117
|
"Generate native agent bridge files from the Hivelore corpus.\n Bridges inject top validated memories and block sensors into agent-harness-specific\n config files (.cursor/rules/haive-memories.mdc, .clinerules, .windsurfrules,\n .continuerules, .sourcegraph/cody-rules.md, .rules, AGENTS.md,\n .github/copilot-instructions.md).\n This is the reach differentiator vs memories.sh: our bridges carry enforcement, not just injection.\n\n Example:\n hivelore bridges sync --all\n hivelore bridges sync --only cline,windsurf\n"
|
|
12640
13118
|
);
|
|
12641
13119
|
bridges.command("sync").description(
|
|
12642
|
-
"Regenerate bridge files idempotently (marker-based, preserves manual content outside markers).\n Supported targets: " +
|
|
13120
|
+
"Regenerate bridge files idempotently (marker-based, preserves manual content outside markers).\n Supported targets: " + BRIDGE_TARGETS4.join(", ") + "\n"
|
|
12643
13121
|
).option("--all", "generate all supported bridge targets").option(
|
|
12644
13122
|
"--only <targets>",
|
|
12645
13123
|
"comma-separated list of targets to generate (e.g. cline,windsurf,agents)"
|
|
@@ -12647,7 +13125,7 @@ function registerBridges(program2) {
|
|
|
12647
13125
|
const root = findProjectRoot45(opts.dir);
|
|
12648
13126
|
const paths = resolveHaivePaths40(root);
|
|
12649
13127
|
const dryRun = opts.dryRun === true;
|
|
12650
|
-
if (!
|
|
13128
|
+
if (!existsSync49(paths.memoriesDir)) {
|
|
12651
13129
|
ui.warn(`No .ai/memories at ${root}. Run \`hivelore init\` first.`);
|
|
12652
13130
|
process.exitCode = 1;
|
|
12653
13131
|
return;
|
|
@@ -12655,18 +13133,18 @@ function registerBridges(program2) {
|
|
|
12655
13133
|
let targets;
|
|
12656
13134
|
if (opts.only) {
|
|
12657
13135
|
const requested = opts.only.split(",").map((t) => t.trim().toLowerCase()).filter(Boolean);
|
|
12658
|
-
const invalid = requested.filter((t) => !
|
|
13136
|
+
const invalid = requested.filter((t) => !BRIDGE_TARGETS4.includes(t));
|
|
12659
13137
|
if (invalid.length > 0) {
|
|
12660
|
-
ui.error(`Unknown bridge target(s): ${invalid.join(", ")}. Valid: ${
|
|
13138
|
+
ui.error(`Unknown bridge target(s): ${invalid.join(", ")}. Valid: ${BRIDGE_TARGETS4.join(", ")}`);
|
|
12661
13139
|
process.exitCode = 1;
|
|
12662
13140
|
return;
|
|
12663
13141
|
}
|
|
12664
13142
|
targets = requested;
|
|
12665
13143
|
} else if (opts.all) {
|
|
12666
|
-
targets =
|
|
13144
|
+
targets = BRIDGE_TARGETS4;
|
|
12667
13145
|
} else {
|
|
12668
|
-
targets =
|
|
12669
|
-
(t) =>
|
|
13146
|
+
targets = BRIDGE_TARGETS4.filter(
|
|
13147
|
+
(t) => existsSync49(path46.join(root, BRIDGE_TARGET_PATH3[t]))
|
|
12670
13148
|
);
|
|
12671
13149
|
if (targets.length === 0) {
|
|
12672
13150
|
ui.info(
|
|
@@ -12698,7 +13176,7 @@ function registerBridges(program2) {
|
|
|
12698
13176
|
const root = findProjectRoot45(opts.dir);
|
|
12699
13177
|
const paths = resolveHaivePaths40(root);
|
|
12700
13178
|
const statuses = await getBridgeFileStatuses(root, paths, {
|
|
12701
|
-
targets:
|
|
13179
|
+
targets: BRIDGE_TARGETS4,
|
|
12702
13180
|
maxMemories: Math.max(1, Number(opts.maxMemories ?? 8))
|
|
12703
13181
|
});
|
|
12704
13182
|
console.log(ui.bold("Hivelore bridge targets:"));
|
|
@@ -12714,7 +13192,7 @@ function registerBridges(program2) {
|
|
|
12714
13192
|
|
|
12715
13193
|
// src/index.ts
|
|
12716
13194
|
var program = new Command48();
|
|
12717
|
-
program.name("hivelore").description("Hivelore - repo-native memory and context policy for coding-agent harnesses").version("0.
|
|
13195
|
+
program.name("hivelore").description("Hivelore - repo-native memory and context policy for coding-agent harnesses").version("0.35.0").option("--advanced", "show maintenance and experimental commands in help").showSuggestionAfterError(true);
|
|
12718
13196
|
registerInit(program);
|
|
12719
13197
|
registerResolveProject(program);
|
|
12720
13198
|
registerEnforce(program);
|