@hiveai/cli 0.21.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +613 -441
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Command as Command64 } from "commander";
|
|
|
6
6
|
// src/commands/briefing.ts
|
|
7
7
|
import { existsSync as existsSync3 } from "fs";
|
|
8
8
|
import { mkdir, readFile as readFile2 } from "fs/promises";
|
|
9
|
-
import
|
|
9
|
+
import path4 from "path";
|
|
10
10
|
import "commander";
|
|
11
11
|
import {
|
|
12
12
|
classifyMemoryPriority,
|
|
@@ -67,6 +67,7 @@ var ui = {
|
|
|
67
67
|
|
|
68
68
|
// src/utils/briefing-radar.ts
|
|
69
69
|
import { execFile } from "child_process";
|
|
70
|
+
import path from "path";
|
|
70
71
|
import { promisify } from "util";
|
|
71
72
|
var exec = promisify(execFile);
|
|
72
73
|
var DEFAULT_DAYS_BACK = 14;
|
|
@@ -94,8 +95,11 @@ var SOURCE_GLOBS = [
|
|
|
94
95
|
];
|
|
95
96
|
async function isGitRepo(root) {
|
|
96
97
|
try {
|
|
97
|
-
await exec("git", ["rev-parse", "--
|
|
98
|
-
|
|
98
|
+
const { stdout } = await exec("git", ["rev-parse", "--show-toplevel"], { cwd: root });
|
|
99
|
+
const toplevel = path.resolve(stdout.trim());
|
|
100
|
+
const resolvedRoot = path.resolve(root);
|
|
101
|
+
if (toplevel === resolvedRoot) return true;
|
|
102
|
+
return toplevel.startsWith(resolvedRoot + path.sep);
|
|
99
103
|
} catch {
|
|
100
104
|
return false;
|
|
101
105
|
}
|
|
@@ -208,7 +212,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
|
|
|
208
212
|
if (!f) continue;
|
|
209
213
|
counts.set(f, (counts.get(f) ?? 0) + 1);
|
|
210
214
|
}
|
|
211
|
-
let entries = [...counts.entries()].map(([
|
|
215
|
+
let entries = [...counts.entries()].map(([path63, changes]) => ({ path: path63, changes }));
|
|
212
216
|
const lowerPaths = filePaths.map((p) => p.toLowerCase());
|
|
213
217
|
if (lowerPaths.length > 0) {
|
|
214
218
|
entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
|
|
@@ -238,7 +242,7 @@ function radarHasContent(r) {
|
|
|
238
242
|
// src/utils/autopilot.ts
|
|
239
243
|
import { existsSync as existsSync2 } from "fs";
|
|
240
244
|
import { readFile, writeFile as writeFile2 } from "fs/promises";
|
|
241
|
-
import
|
|
245
|
+
import path3 from "path";
|
|
242
246
|
import {
|
|
243
247
|
AUTOPILOT_DEFAULTS,
|
|
244
248
|
buildCodeMap,
|
|
@@ -253,7 +257,7 @@ import {
|
|
|
253
257
|
import { existsSync } from "fs";
|
|
254
258
|
import { writeFile } from "fs/promises";
|
|
255
259
|
import { spawnSync } from "child_process";
|
|
256
|
-
import
|
|
260
|
+
import path2 from "path";
|
|
257
261
|
import "commander";
|
|
258
262
|
import {
|
|
259
263
|
findProjectRoot,
|
|
@@ -441,7 +445,7 @@ function suggestAnchors(root, loaded, codeMap, trackedFiles) {
|
|
|
441
445
|
for (const match of body.matchAll(/`([^`\n]+\.[A-Za-z0-9]+)`|(?:^|\s)([A-Za-z0-9_./-]+\.[A-Za-z0-9]+)/gm)) {
|
|
442
446
|
const candidate = (match[1] ?? match[2] ?? "").replace(/^\.?\//, "");
|
|
443
447
|
if (!candidate || candidate.startsWith("http")) continue;
|
|
444
|
-
if (existsSync(
|
|
448
|
+
if (existsSync(path2.join(root, candidate)) && isSafeAnchorPath(candidate, trackedFiles)) {
|
|
445
449
|
paths.add(candidate);
|
|
446
450
|
}
|
|
447
451
|
}
|
|
@@ -704,7 +708,7 @@ async function projectContextVersionStatus(root, paths) {
|
|
|
704
708
|
if (!existsSync2(paths.projectContext)) {
|
|
705
709
|
return { mismatch: false, canSync: false };
|
|
706
710
|
}
|
|
707
|
-
const packagePath =
|
|
711
|
+
const packagePath = path3.join(root, "package.json");
|
|
708
712
|
if (!existsSync2(packagePath)) {
|
|
709
713
|
return { mismatch: false, canSync: false };
|
|
710
714
|
}
|
|
@@ -940,7 +944,7 @@ function registerBriefing(program2) {
|
|
|
940
944
|
continue;
|
|
941
945
|
}
|
|
942
946
|
const otherMemories = await loadMemoriesFromDir3(otherPaths.memoriesDir);
|
|
943
|
-
const tag =
|
|
947
|
+
const tag = path4.basename(otherRoot);
|
|
944
948
|
for (const m of otherMemories) {
|
|
945
949
|
ownMemories.push({ ...m, origin: tag });
|
|
946
950
|
}
|
|
@@ -1066,7 +1070,7 @@ function registerBriefing(program2) {
|
|
|
1066
1070
|
status: item.memory.frontmatter.status,
|
|
1067
1071
|
priority: priorities[i],
|
|
1068
1072
|
score: item.score,
|
|
1069
|
-
file:
|
|
1073
|
+
file: path4.relative(root, item.filePath),
|
|
1070
1074
|
summary: (item.memory.body.split("\n").map((l) => l.replace(/^#+\s*/, "").trim()).find((l) => l.length > 0) ?? "").slice(0, 140)
|
|
1071
1075
|
}))
|
|
1072
1076
|
}, null, 2));
|
|
@@ -1088,7 +1092,7 @@ function registerBriefing(program2) {
|
|
|
1088
1092
|
`${ui.bold(fm.id)} ${priorityBadge(priority)} ${ui.dim(fm.scope + "/" + fm.type)} ${badge}${draftMarker}${unverifiedMarker}${originMarker}${hitMarker}`
|
|
1089
1093
|
);
|
|
1090
1094
|
if (opts.explainSource) {
|
|
1091
|
-
const relPath =
|
|
1095
|
+
const relPath = path4.relative(root, item.filePath);
|
|
1092
1096
|
const anchorPaths = fm.anchor?.paths ?? [];
|
|
1093
1097
|
const anchorSymbols = fm.anchor?.symbols ?? [];
|
|
1094
1098
|
const parts = [`source: ${relPath}`];
|
|
@@ -1212,7 +1216,7 @@ function registerTui(program2) {
|
|
|
1212
1216
|
|
|
1213
1217
|
// src/commands/embeddings.ts
|
|
1214
1218
|
import { existsSync as existsSync4 } from "fs";
|
|
1215
|
-
import
|
|
1219
|
+
import path5 from "path";
|
|
1216
1220
|
import "commander";
|
|
1217
1221
|
import { findProjectRoot as findProjectRoot4, resolveHaivePaths as resolveHaivePaths3 } from "@hiveai/core";
|
|
1218
1222
|
function registerEmbeddings(program2) {
|
|
@@ -1254,7 +1258,7 @@ function registerEmbeddings(program2) {
|
|
|
1254
1258
|
for (const hit of result.hits) {
|
|
1255
1259
|
const score = hit.score.toFixed(3);
|
|
1256
1260
|
console.log(`${ui.bold(score)} ${hit.id}`);
|
|
1257
|
-
console.log(` ${ui.dim(
|
|
1261
|
+
console.log(` ${ui.dim(path5.relative(root, hit.file_path))}`);
|
|
1258
1262
|
}
|
|
1259
1263
|
});
|
|
1260
1264
|
embeddings.command("status").description("Show the embeddings index status").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
@@ -1285,7 +1289,7 @@ async function loadEmbeddings() {
|
|
|
1285
1289
|
|
|
1286
1290
|
// src/commands/index-code.ts
|
|
1287
1291
|
import { existsSync as existsSync5, statSync } from "fs";
|
|
1288
|
-
import
|
|
1292
|
+
import path6 from "path";
|
|
1289
1293
|
import "commander";
|
|
1290
1294
|
import {
|
|
1291
1295
|
buildCodeMap as buildCodeMap2,
|
|
@@ -1334,7 +1338,7 @@ function registerIndexCode(program2) {
|
|
|
1334
1338
|
const fileCount = Object.keys(map.files).length;
|
|
1335
1339
|
const exportCount = Object.values(map.files).reduce((s, f) => s + f.exports.length, 0);
|
|
1336
1340
|
ui.success(
|
|
1337
|
-
`Indexed ${fileCount} file(s) with ${exportCount} export(s) \u2192 ${
|
|
1341
|
+
`Indexed ${fileCount} file(s) with ${exportCount} export(s) \u2192 ${path6.relative(root, codeMapPath(paths))}`
|
|
1338
1342
|
);
|
|
1339
1343
|
});
|
|
1340
1344
|
idx.command("code-search").description(
|
|
@@ -1371,12 +1375,12 @@ async function reportIndexStatus(root, paths, asJson) {
|
|
|
1371
1375
|
const fileCount = map ? Object.keys(map.files).length : 0;
|
|
1372
1376
|
const exportCount = map ? Object.values(map.files).reduce((s, f) => s + f.exports.length, 0) : 0;
|
|
1373
1377
|
const mapMtime = existsSync5(mapFile) ? statSync(mapFile).mtime.toISOString() : null;
|
|
1374
|
-
const searchIndexFile =
|
|
1378
|
+
const searchIndexFile = path6.join(paths.haiveDir, ".cache", "embeddings", "code-embeddings-index.json");
|
|
1375
1379
|
const searchIndexPresent = existsSync5(searchIndexFile);
|
|
1376
1380
|
const status = {
|
|
1377
1381
|
code_map: {
|
|
1378
1382
|
present: map !== null,
|
|
1379
|
-
path:
|
|
1383
|
+
path: path6.relative(root, mapFile),
|
|
1380
1384
|
files: fileCount,
|
|
1381
1385
|
exports: exportCount,
|
|
1382
1386
|
generated_at: map?.generated_at ?? null,
|
|
@@ -1384,7 +1388,7 @@ async function reportIndexStatus(root, paths, asJson) {
|
|
|
1384
1388
|
},
|
|
1385
1389
|
code_search_index: {
|
|
1386
1390
|
present: searchIndexPresent,
|
|
1387
|
-
path:
|
|
1391
|
+
path: path6.relative(root, searchIndexFile)
|
|
1388
1392
|
}
|
|
1389
1393
|
};
|
|
1390
1394
|
if (asJson) {
|
|
@@ -1409,7 +1413,7 @@ async function reportIndexStatus(root, paths, asJson) {
|
|
|
1409
1413
|
import { execFile as execFile2 } from "child_process";
|
|
1410
1414
|
import { mkdir as mkdir6, readFile as readFile6, readdir as readdir2, writeFile as writeFile7 } from "fs/promises";
|
|
1411
1415
|
import { existsSync as existsSync11 } from "fs";
|
|
1412
|
-
import
|
|
1416
|
+
import path12 from "path";
|
|
1413
1417
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
1414
1418
|
import { promisify as promisify2 } from "util";
|
|
1415
1419
|
import "commander";
|
|
@@ -1430,7 +1434,7 @@ import {
|
|
|
1430
1434
|
// src/utils/bridge-files.ts
|
|
1431
1435
|
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
1432
1436
|
import { existsSync as existsSync6 } from "fs";
|
|
1433
|
-
import
|
|
1437
|
+
import path7 from "path";
|
|
1434
1438
|
import {
|
|
1435
1439
|
BRIDGE_MARKERS,
|
|
1436
1440
|
generateBridges,
|
|
@@ -1457,14 +1461,14 @@ async function writeBridgeFiles(root, paths, opts) {
|
|
|
1457
1461
|
const maxMemories = Math.max(1, opts.maxMemories ?? 8);
|
|
1458
1462
|
const outputs = generateBridges(memories, sensors, { maxMemories, targets: opts.targets });
|
|
1459
1463
|
for (const output of outputs) {
|
|
1460
|
-
const targetFile =
|
|
1464
|
+
const targetFile = path7.join(root, output.path);
|
|
1461
1465
|
const fileExists = existsSync6(targetFile);
|
|
1462
1466
|
if (opts.onlyExisting && !fileExists) continue;
|
|
1463
1467
|
if (opts.dryRun) {
|
|
1464
1468
|
(fileExists ? result.updated : result.created).push(output.path);
|
|
1465
1469
|
continue;
|
|
1466
1470
|
}
|
|
1467
|
-
await mkdir2(
|
|
1471
|
+
await mkdir2(path7.dirname(targetFile), { recursive: true });
|
|
1468
1472
|
if (!fileExists) {
|
|
1469
1473
|
await writeFile3(targetFile, output.content, "utf8");
|
|
1470
1474
|
result.created.push(output.path);
|
|
@@ -1513,7 +1517,7 @@ import { spawnSync as spawnSync2 } from "child_process";
|
|
|
1513
1517
|
import { existsSync as existsSync8 } from "fs";
|
|
1514
1518
|
import { mkdir as mkdir4, writeFile as writeFile5 } from "fs/promises";
|
|
1515
1519
|
import os2 from "os";
|
|
1516
|
-
import
|
|
1520
|
+
import path9 from "path";
|
|
1517
1521
|
import { createInterface } from "readline/promises";
|
|
1518
1522
|
import "commander";
|
|
1519
1523
|
import { findProjectRoot as findProjectRoot6, resolveHaivePaths as resolveHaivePaths5 } from "@hiveai/core";
|
|
@@ -1521,7 +1525,7 @@ import { findProjectRoot as findProjectRoot6, resolveHaivePaths as resolveHaiveP
|
|
|
1521
1525
|
// src/commands/init-mcp-setup.ts
|
|
1522
1526
|
import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir3 } from "fs/promises";
|
|
1523
1527
|
import { existsSync as existsSync7 } from "fs";
|
|
1524
|
-
import
|
|
1528
|
+
import path8 from "path";
|
|
1525
1529
|
import os from "os";
|
|
1526
1530
|
var HOME = os.homedir();
|
|
1527
1531
|
var HAIVE_MCP_ENTRY = {
|
|
@@ -1536,11 +1540,11 @@ function projectMcpEntry(root) {
|
|
|
1536
1540
|
};
|
|
1537
1541
|
}
|
|
1538
1542
|
function cursorMcpPath() {
|
|
1539
|
-
return
|
|
1543
|
+
return path8.join(HOME, ".cursor", "mcp.json");
|
|
1540
1544
|
}
|
|
1541
1545
|
async function configureCursor() {
|
|
1542
1546
|
const mcpPath = cursorMcpPath();
|
|
1543
|
-
const cursorDir =
|
|
1547
|
+
const cursorDir = path8.join(HOME, ".cursor");
|
|
1544
1548
|
if (!existsSync7(cursorDir)) return { client: "Cursor", status: "not_installed" };
|
|
1545
1549
|
let config = {};
|
|
1546
1550
|
if (existsSync7(mcpPath)) {
|
|
@@ -1558,16 +1562,16 @@ async function configureCursor() {
|
|
|
1558
1562
|
}
|
|
1559
1563
|
function vscodeMcpPath() {
|
|
1560
1564
|
const candidates = [
|
|
1561
|
-
|
|
1565
|
+
path8.join(HOME, ".config", "Code", "User", "mcp.json"),
|
|
1562
1566
|
// Linux
|
|
1563
|
-
|
|
1567
|
+
path8.join(HOME, "Library", "Application Support", "Code", "User", "mcp.json"),
|
|
1564
1568
|
// macOS
|
|
1565
|
-
|
|
1569
|
+
path8.join(HOME, "AppData", "Roaming", "Code", "User", "mcp.json"),
|
|
1566
1570
|
// Windows
|
|
1567
|
-
|
|
1571
|
+
path8.join(HOME, ".config", "Code - Insiders", "User", "mcp.json")
|
|
1568
1572
|
];
|
|
1569
1573
|
for (const c of candidates) {
|
|
1570
|
-
if (existsSync7(
|
|
1574
|
+
if (existsSync7(path8.dirname(c))) return c;
|
|
1571
1575
|
}
|
|
1572
1576
|
return null;
|
|
1573
1577
|
}
|
|
@@ -1584,20 +1588,20 @@ async function configureVSCode() {
|
|
|
1584
1588
|
config.servers ??= {};
|
|
1585
1589
|
if (config.servers["haive"]) return { client: "VS Code", status: "already_configured" };
|
|
1586
1590
|
config.servers["haive"] = { ...HAIVE_MCP_ENTRY, type: "stdio" };
|
|
1587
|
-
await mkdir3(
|
|
1591
|
+
await mkdir3(path8.dirname(mcpPath), { recursive: true });
|
|
1588
1592
|
await writeFile4(mcpPath, JSON.stringify(config, null, 2), "utf8");
|
|
1589
1593
|
return { client: "VS Code", status: "configured", path: mcpPath };
|
|
1590
1594
|
}
|
|
1591
1595
|
function claudeConfigPath() {
|
|
1592
|
-
const p =
|
|
1596
|
+
const p = path8.join(HOME, ".claude.json");
|
|
1593
1597
|
if (existsSync7(p)) return p;
|
|
1594
|
-
const p2 =
|
|
1595
|
-
if (existsSync7(
|
|
1598
|
+
const p2 = path8.join(HOME, ".config", "claude", "claude.json");
|
|
1599
|
+
if (existsSync7(path8.dirname(p2))) return p2;
|
|
1596
1600
|
return null;
|
|
1597
1601
|
}
|
|
1598
1602
|
async function configureClaude() {
|
|
1599
|
-
const cfgPath = claudeConfigPath() ??
|
|
1600
|
-
if (!existsSync7(cfgPath) && !existsSync7(
|
|
1603
|
+
const cfgPath = claudeConfigPath() ?? path8.join(HOME, ".claude.json");
|
|
1604
|
+
if (!existsSync7(cfgPath) && !existsSync7(path8.join(HOME, ".claude"))) {
|
|
1601
1605
|
return { client: "Claude Code", status: "not_installed" };
|
|
1602
1606
|
}
|
|
1603
1607
|
let config = {};
|
|
@@ -1615,11 +1619,11 @@ async function configureClaude() {
|
|
|
1615
1619
|
}
|
|
1616
1620
|
function windsurfMcpPath() {
|
|
1617
1621
|
const candidates = [
|
|
1618
|
-
|
|
1619
|
-
|
|
1622
|
+
path8.join(HOME, ".codeium", "windsurf", "mcp_config.json"),
|
|
1623
|
+
path8.join(HOME, ".windsurf", "mcp.json")
|
|
1620
1624
|
];
|
|
1621
1625
|
for (const c of candidates) {
|
|
1622
|
-
if (existsSync7(
|
|
1626
|
+
if (existsSync7(path8.dirname(c))) return c;
|
|
1623
1627
|
}
|
|
1624
1628
|
return null;
|
|
1625
1629
|
}
|
|
@@ -1636,7 +1640,7 @@ async function configureWindsurf() {
|
|
|
1636
1640
|
config.mcpServers ??= {};
|
|
1637
1641
|
if (config.mcpServers["haive"]) return { client: "Windsurf", status: "already_configured" };
|
|
1638
1642
|
config.mcpServers["haive"] = HAIVE_MCP_ENTRY;
|
|
1639
|
-
await mkdir3(
|
|
1643
|
+
await mkdir3(path8.dirname(mcpPath), { recursive: true });
|
|
1640
1644
|
await writeFile4(mcpPath, JSON.stringify(config, null, 2), "utf8");
|
|
1641
1645
|
return { client: "Windsurf", status: "configured", path: mcpPath };
|
|
1642
1646
|
}
|
|
@@ -1657,7 +1661,7 @@ async function configureProjectMcpClients(root) {
|
|
|
1657
1661
|
const entry = projectMcpEntry(root);
|
|
1658
1662
|
const results = [];
|
|
1659
1663
|
try {
|
|
1660
|
-
const cursorPath =
|
|
1664
|
+
const cursorPath = path8.join(root, ".cursor", "mcp.json");
|
|
1661
1665
|
let config = {};
|
|
1662
1666
|
if (existsSync7(cursorPath)) {
|
|
1663
1667
|
try {
|
|
@@ -1667,14 +1671,14 @@ async function configureProjectMcpClients(root) {
|
|
|
1667
1671
|
}
|
|
1668
1672
|
config.mcpServers ??= {};
|
|
1669
1673
|
config.mcpServers["haive"] = entry;
|
|
1670
|
-
await mkdir3(
|
|
1674
|
+
await mkdir3(path8.dirname(cursorPath), { recursive: true });
|
|
1671
1675
|
await writeFile4(cursorPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
1672
1676
|
results.push({ client: "Cursor (project)", status: "configured", path: cursorPath });
|
|
1673
1677
|
} catch (err) {
|
|
1674
1678
|
results.push({ client: "Cursor (project)", status: "error", error: String(err) });
|
|
1675
1679
|
}
|
|
1676
1680
|
try {
|
|
1677
|
-
const vscodePath =
|
|
1681
|
+
const vscodePath = path8.join(root, ".vscode", "mcp.json");
|
|
1678
1682
|
let config = {};
|
|
1679
1683
|
if (existsSync7(vscodePath)) {
|
|
1680
1684
|
try {
|
|
@@ -1684,14 +1688,14 @@ async function configureProjectMcpClients(root) {
|
|
|
1684
1688
|
}
|
|
1685
1689
|
config.servers ??= {};
|
|
1686
1690
|
config.servers["haive"] = { ...entry, type: "stdio" };
|
|
1687
|
-
await mkdir3(
|
|
1691
|
+
await mkdir3(path8.dirname(vscodePath), { recursive: true });
|
|
1688
1692
|
await writeFile4(vscodePath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
1689
1693
|
results.push({ client: "VS Code (workspace)", status: "configured", path: vscodePath });
|
|
1690
1694
|
} catch (err) {
|
|
1691
1695
|
results.push({ client: "VS Code (workspace)", status: "error", error: String(err) });
|
|
1692
1696
|
}
|
|
1693
1697
|
try {
|
|
1694
|
-
const mcpPath =
|
|
1698
|
+
const mcpPath = path8.join(root, ".mcp.json");
|
|
1695
1699
|
let config = {};
|
|
1696
1700
|
if (existsSync7(mcpPath)) {
|
|
1697
1701
|
try {
|
|
@@ -1767,9 +1771,9 @@ async function detectAgentMode(dir) {
|
|
|
1767
1771
|
const root = findProjectRoot6(dir);
|
|
1768
1772
|
const paths = resolveHaivePaths5(root);
|
|
1769
1773
|
const projectMcp = [
|
|
1770
|
-
{ client: "Claude Code", path:
|
|
1771
|
-
{ client: "Cursor", path:
|
|
1772
|
-
{ client: "VS Code", path:
|
|
1774
|
+
{ client: "Claude Code", path: path9.join(root, ".mcp.json"), present: existsSync8(path9.join(root, ".mcp.json")) },
|
|
1775
|
+
{ client: "Cursor", path: path9.join(root, ".cursor", "mcp.json"), present: existsSync8(path9.join(root, ".cursor", "mcp.json")) },
|
|
1776
|
+
{ client: "VS Code", path: path9.join(root, ".vscode", "mcp.json"), present: existsSync8(path9.join(root, ".vscode", "mcp.json")) }
|
|
1773
1777
|
];
|
|
1774
1778
|
const installedAgents = [
|
|
1775
1779
|
{ agent: "Codex", command: "codex", installed: commandExists("codex"), mcp_configured: codexMcpConfigured() },
|
|
@@ -1792,9 +1796,9 @@ async function detectAgentMode(dir) {
|
|
|
1792
1796
|
};
|
|
1793
1797
|
}
|
|
1794
1798
|
async function writeAgentModeRecord(paths, detection, skippedReason) {
|
|
1795
|
-
const dir =
|
|
1799
|
+
const dir = path9.join(paths.runtimeDir, "enforcement");
|
|
1796
1800
|
await mkdir4(dir, { recursive: true });
|
|
1797
|
-
const file =
|
|
1801
|
+
const file = path9.join(dir, "agent-mode.json");
|
|
1798
1802
|
const record = {
|
|
1799
1803
|
selected_mode: detection.recommended_mode,
|
|
1800
1804
|
recommended_command: detection.recommended_command,
|
|
@@ -1835,7 +1839,7 @@ async function configureCodexIfAvailable(root) {
|
|
|
1835
1839
|
"mcp",
|
|
1836
1840
|
"--stdio"
|
|
1837
1841
|
], { encoding: "utf8" });
|
|
1838
|
-
if (result.status === 0) return { client: "Codex", status: "configured", path:
|
|
1842
|
+
if (result.status === 0) return { client: "Codex", status: "configured", path: path9.join(os2.homedir(), ".codex", "config.toml") };
|
|
1839
1843
|
return { client: "Codex", status: "error", error: result.stderr || result.stdout || "codex mcp add failed" };
|
|
1840
1844
|
}
|
|
1841
1845
|
function commandExists(command) {
|
|
@@ -1862,7 +1866,7 @@ function printDetection(detection, json) {
|
|
|
1862
1866
|
console.log(ui.dim(` root: ${detection.root}`));
|
|
1863
1867
|
console.log(`${detection.initialized ? ui.green("\u2713") : ui.red("\u2717")} project initialized`);
|
|
1864
1868
|
for (const cfg of detection.project_mcp) {
|
|
1865
|
-
console.log(`${cfg.present ? ui.green("\u2713") : ui.yellow("\u2022")} ${cfg.client} project MCP ${ui.dim(
|
|
1869
|
+
console.log(`${cfg.present ? ui.green("\u2713") : ui.yellow("\u2022")} ${cfg.client} project MCP ${ui.dim(path9.relative(detection.root, cfg.path))}`);
|
|
1866
1870
|
}
|
|
1867
1871
|
for (const agent of detection.installed_agents) {
|
|
1868
1872
|
const marker = agent.installed ? ui.green("\u2713") : ui.dim("\u2022");
|
|
@@ -1892,7 +1896,7 @@ function printSetupResult(result) {
|
|
|
1892
1896
|
// src/commands/init-bootstrap.ts
|
|
1893
1897
|
import { readdir, readFile as readFile5 } from "fs/promises";
|
|
1894
1898
|
import { existsSync as existsSync9, readdirSync } from "fs";
|
|
1895
|
-
import
|
|
1899
|
+
import path10 from "path";
|
|
1896
1900
|
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
1897
1901
|
"node_modules",
|
|
1898
1902
|
"dist",
|
|
@@ -1978,12 +1982,12 @@ function detectKeyDeps(allDeps) {
|
|
|
1978
1982
|
return KEY_DEPS.filter((d) => allDeps[d] !== void 0);
|
|
1979
1983
|
}
|
|
1980
1984
|
function detectLanguage(root) {
|
|
1981
|
-
if (existsSync9(
|
|
1982
|
-
if (existsSync9(
|
|
1983
|
-
if (existsSync9(
|
|
1984
|
-
if (existsSync9(
|
|
1985
|
-
if (existsSync9(
|
|
1986
|
-
if (existsSync9(
|
|
1985
|
+
if (existsSync9(path10.join(root, "tsconfig.json"))) return "TypeScript";
|
|
1986
|
+
if (existsSync9(path10.join(root, "pyproject.toml")) || existsSync9(path10.join(root, "setup.py"))) return "Python";
|
|
1987
|
+
if (existsSync9(path10.join(root, "go.mod"))) return "Go";
|
|
1988
|
+
if (existsSync9(path10.join(root, "pom.xml")) || existsSync9(path10.join(root, "build.gradle"))) return "Java/Kotlin";
|
|
1989
|
+
if (existsSync9(path10.join(root, "Cargo.toml"))) return "Rust";
|
|
1990
|
+
if (existsSync9(path10.join(root, "package.json"))) {
|
|
1987
1991
|
return hasSourceWithExt(root, [".ts", ".tsx", ".mts", ".cts"]) ? "TypeScript" : "JavaScript";
|
|
1988
1992
|
}
|
|
1989
1993
|
return "Unknown";
|
|
@@ -2003,7 +2007,7 @@ function hasSourceWithExt(root, exts) {
|
|
|
2003
2007
|
if (depth <= 0) return false;
|
|
2004
2008
|
for (const entry of entries) {
|
|
2005
2009
|
if (entry.isDirectory() && !IGNORE_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
|
|
2006
|
-
if (scanDir(
|
|
2010
|
+
if (scanDir(path10.join(dir, entry.name), depth - 1)) return true;
|
|
2007
2011
|
}
|
|
2008
2012
|
}
|
|
2009
2013
|
return false;
|
|
@@ -2040,9 +2044,9 @@ async function scanDirs(root, maxDepth = 2) {
|
|
|
2040
2044
|
for (const entry of entries) {
|
|
2041
2045
|
if (!entry.isDirectory()) continue;
|
|
2042
2046
|
if (IGNORE_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
2043
|
-
const rel =
|
|
2047
|
+
const rel = path10.relative(root, path10.join(dir, entry.name));
|
|
2044
2048
|
results.push(rel);
|
|
2045
|
-
await walk(
|
|
2049
|
+
await walk(path10.join(dir, entry.name), depth + 1);
|
|
2046
2050
|
}
|
|
2047
2051
|
}
|
|
2048
2052
|
await walk(root, 0);
|
|
@@ -2159,7 +2163,7 @@ function readmeExcerpt(readme) {
|
|
|
2159
2163
|
}
|
|
2160
2164
|
async function generateBootstrapContext(root) {
|
|
2161
2165
|
let pkg = {};
|
|
2162
|
-
const pkgPath =
|
|
2166
|
+
const pkgPath = path10.join(root, "package.json");
|
|
2163
2167
|
if (existsSync9(pkgPath)) {
|
|
2164
2168
|
try {
|
|
2165
2169
|
pkg = JSON.parse(await readFile5(pkgPath, "utf8"));
|
|
@@ -2172,11 +2176,11 @@ async function generateBootstrapContext(root) {
|
|
|
2172
2176
|
const language = detectLanguage(root);
|
|
2173
2177
|
const isMonorepo = pkg.workspaces !== void 0 && (Array.isArray(pkg.workspaces) ? pkg.workspaces.length > 0 : true);
|
|
2174
2178
|
const projectType = detectProjectType(frameworks, pkg.scripts ?? {}, isMonorepo);
|
|
2175
|
-
const projectName = pkg.name ??
|
|
2179
|
+
const projectName = pkg.name ?? path10.basename(root);
|
|
2176
2180
|
const projectDesc = pkg.description ?? "";
|
|
2177
2181
|
let readmeSummary = "";
|
|
2178
2182
|
for (const name of ["README.md", "readme.md", "README"]) {
|
|
2179
|
-
const p =
|
|
2183
|
+
const p = path10.join(root, name);
|
|
2180
2184
|
if (existsSync9(p)) {
|
|
2181
2185
|
try {
|
|
2182
2186
|
const content = await readFile5(p, "utf8");
|
|
@@ -2245,7 +2249,7 @@ async function generateBootstrapContext(root) {
|
|
|
2245
2249
|
// src/commands/init-stack-packs.ts
|
|
2246
2250
|
import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
|
|
2247
2251
|
import { existsSync as existsSync10 } from "fs";
|
|
2248
|
-
import
|
|
2252
|
+
import path11 from "path";
|
|
2249
2253
|
import {
|
|
2250
2254
|
buildFrontmatter,
|
|
2251
2255
|
loadMemoriesFromDir as loadMemoriesFromDir5,
|
|
@@ -3374,7 +3378,7 @@ ${mem.body}`;
|
|
|
3374
3378
|
const content = serializeMemory2({ frontmatter: fm, body: `${titledBody}
|
|
3375
3379
|
|
|
3376
3380
|
${SEED_FOOTER(stack)}` });
|
|
3377
|
-
await mkdir5(
|
|
3381
|
+
await mkdir5(path11.dirname(filePath), { recursive: true });
|
|
3378
3382
|
await writeFile6(filePath, content, "utf8");
|
|
3379
3383
|
existingTopics.add(topic);
|
|
3380
3384
|
existingSignatures.add(signature);
|
|
@@ -3386,7 +3390,7 @@ ${SEED_FOOTER(stack)}` });
|
|
|
3386
3390
|
|
|
3387
3391
|
// src/commands/init.ts
|
|
3388
3392
|
var execFileAsync = promisify2(execFile2);
|
|
3389
|
-
var HAIVE_GITHUB_ACTION_REF = `v${"0.
|
|
3393
|
+
var HAIVE_GITHUB_ACTION_REF = `v${"0.24.0"}`;
|
|
3390
3394
|
var PROJECT_CONTEXT_TEMPLATE = `# Project context
|
|
3391
3395
|
|
|
3392
3396
|
> Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
|
|
@@ -3614,7 +3618,7 @@ function registerInit(program2) {
|
|
|
3614
3618
|
"approve user-level AI client configuration prompts during agent setup",
|
|
3615
3619
|
false
|
|
3616
3620
|
).option("--json", "emit a machine-readable summary on stdout (human logs go to stderr)", false).action(async (opts) => {
|
|
3617
|
-
const root =
|
|
3621
|
+
const root = path12.resolve(opts.dir);
|
|
3618
3622
|
const paths = resolveHaivePaths6(root);
|
|
3619
3623
|
const autopilot = opts.manual !== true;
|
|
3620
3624
|
const json = opts.json === true;
|
|
@@ -3641,7 +3645,7 @@ function registerInit(program2) {
|
|
|
3641
3645
|
await mkdir6(paths.moduleDir, { recursive: true });
|
|
3642
3646
|
await mkdir6(paths.modulesContextDir, { recursive: true });
|
|
3643
3647
|
await ensureAiRuntimeLayout(paths.runtimeDir);
|
|
3644
|
-
await ensureAiCacheLayout(
|
|
3648
|
+
await ensureAiCacheLayout(path12.join(paths.haiveDir, ".cache"));
|
|
3645
3649
|
if (!existsSync11(paths.projectContext)) {
|
|
3646
3650
|
if (wantBootstrap) {
|
|
3647
3651
|
ui.info("Bootstrapping project context from local files\u2026");
|
|
@@ -3655,11 +3659,11 @@ function registerInit(program2) {
|
|
|
3655
3659
|
}
|
|
3656
3660
|
} else {
|
|
3657
3661
|
await writeFile7(paths.projectContext, PROJECT_CONTEXT_TEMPLATE, "utf8");
|
|
3658
|
-
ui.success(`Created ${
|
|
3662
|
+
ui.success(`Created ${path12.relative(root, paths.projectContext)}`);
|
|
3659
3663
|
}
|
|
3660
3664
|
}
|
|
3661
3665
|
const configExists = existsSync11(
|
|
3662
|
-
|
|
3666
|
+
path12.join(paths.haiveDir, "haive.config.json")
|
|
3663
3667
|
);
|
|
3664
3668
|
if (!configExists) {
|
|
3665
3669
|
await saveConfig2(paths, autopilot ? AUTOPILOT_DEFAULTS2 : { autopilot: false });
|
|
@@ -3726,13 +3730,13 @@ function registerInit(program2) {
|
|
|
3726
3730
|
}
|
|
3727
3731
|
const wantCi = opts.withCi || autopilot;
|
|
3728
3732
|
if (wantCi) {
|
|
3729
|
-
const ciPath =
|
|
3733
|
+
const ciPath = path12.join(root, ".github", "workflows", "haive-sync.yml");
|
|
3730
3734
|
if (existsSync11(ciPath)) {
|
|
3731
3735
|
ui.info("CI workflow already exists \u2014 skipped");
|
|
3732
3736
|
} else {
|
|
3733
|
-
await mkdir6(
|
|
3737
|
+
await mkdir6(path12.dirname(ciPath), { recursive: true });
|
|
3734
3738
|
await writeFile7(ciPath, CI_WORKFLOW, "utf8");
|
|
3735
|
-
ui.success(`Created ${
|
|
3739
|
+
ui.success(`Created ${path12.relative(root, ciPath)}`);
|
|
3736
3740
|
}
|
|
3737
3741
|
}
|
|
3738
3742
|
if (autopilot) {
|
|
@@ -3772,7 +3776,7 @@ function registerInit(program2) {
|
|
|
3772
3776
|
interactive: process.stdin.isTTY
|
|
3773
3777
|
});
|
|
3774
3778
|
for (const r of agentSetup.project_results) {
|
|
3775
|
-
if (r.status === "configured" && r.path) ui.success(`haive MCP project config written (${
|
|
3779
|
+
if (r.status === "configured" && r.path) ui.success(`haive MCP project config written (${path12.relative(root, r.path)})`);
|
|
3776
3780
|
else if (r.status === "error") ui.warn(`${r.client}: ${r.error}`);
|
|
3777
3781
|
}
|
|
3778
3782
|
for (const r of agentSetup.global_results) {
|
|
@@ -3871,7 +3875,7 @@ async function autoDetectStacksFromRoot(root) {
|
|
|
3871
3875
|
let requirementsTxt;
|
|
3872
3876
|
let goMod;
|
|
3873
3877
|
let pomXml;
|
|
3874
|
-
const pkgPath =
|
|
3878
|
+
const pkgPath = path12.join(root, "package.json");
|
|
3875
3879
|
if (existsSync11(pkgPath)) {
|
|
3876
3880
|
try {
|
|
3877
3881
|
const pkg = JSON.parse(await readFile6(pkgPath, "utf8"));
|
|
@@ -3880,7 +3884,7 @@ async function autoDetectStacksFromRoot(root) {
|
|
|
3880
3884
|
}
|
|
3881
3885
|
}
|
|
3882
3886
|
for (const name of ["requirements.txt", "requirements/base.txt", "requirements/prod.txt"]) {
|
|
3883
|
-
const reqPath =
|
|
3887
|
+
const reqPath = path12.join(root, name);
|
|
3884
3888
|
if (existsSync11(reqPath)) {
|
|
3885
3889
|
try {
|
|
3886
3890
|
requirementsTxt = await readFile6(reqPath, "utf8");
|
|
@@ -3889,14 +3893,14 @@ async function autoDetectStacksFromRoot(root) {
|
|
|
3889
3893
|
}
|
|
3890
3894
|
}
|
|
3891
3895
|
}
|
|
3892
|
-
const goModPath =
|
|
3896
|
+
const goModPath = path12.join(root, "go.mod");
|
|
3893
3897
|
if (existsSync11(goModPath)) {
|
|
3894
3898
|
try {
|
|
3895
3899
|
goMod = await readFile6(goModPath, "utf8");
|
|
3896
3900
|
} catch {
|
|
3897
3901
|
}
|
|
3898
3902
|
}
|
|
3899
|
-
const pomPath =
|
|
3903
|
+
const pomPath = path12.join(root, "pom.xml");
|
|
3900
3904
|
if (existsSync11(pomPath)) {
|
|
3901
3905
|
try {
|
|
3902
3906
|
pomXml = await readFile6(pomPath, "utf8");
|
|
@@ -3904,7 +3908,7 @@ async function autoDetectStacksFromRoot(root) {
|
|
|
3904
3908
|
}
|
|
3905
3909
|
}
|
|
3906
3910
|
let composerJson;
|
|
3907
|
-
const composerPath =
|
|
3911
|
+
const composerPath = path12.join(root, "composer.json");
|
|
3908
3912
|
if (existsSync11(composerPath)) {
|
|
3909
3913
|
try {
|
|
3910
3914
|
composerJson = await readFile6(composerPath, "utf8");
|
|
@@ -3912,16 +3916,16 @@ async function autoDetectStacksFromRoot(root) {
|
|
|
3912
3916
|
}
|
|
3913
3917
|
}
|
|
3914
3918
|
let gemfile;
|
|
3915
|
-
const gemfilePath =
|
|
3919
|
+
const gemfilePath = path12.join(root, "Gemfile");
|
|
3916
3920
|
if (existsSync11(gemfilePath)) {
|
|
3917
3921
|
try {
|
|
3918
3922
|
gemfile = await readFile6(gemfilePath, "utf8");
|
|
3919
3923
|
} catch {
|
|
3920
3924
|
}
|
|
3921
3925
|
}
|
|
3922
|
-
const hasDockerfile = existsSync11(
|
|
3923
|
-
const hasTurboJson = existsSync11(
|
|
3924
|
-
const hasNxJson = existsSync11(
|
|
3926
|
+
const hasDockerfile = existsSync11(path12.join(root, "Dockerfile"));
|
|
3927
|
+
const hasTurboJson = existsSync11(path12.join(root, "turbo.json"));
|
|
3928
|
+
const hasNxJson = existsSync11(path12.join(root, "nx.json"));
|
|
3925
3929
|
let hasCsproj = false;
|
|
3926
3930
|
try {
|
|
3927
3931
|
const entries = await readdir2(root);
|
|
@@ -3974,7 +3978,7 @@ _Seeded from git ${p.kind} commit ${p.source_sha}. Review and validate (or delet
|
|
|
3974
3978
|
`;
|
|
3975
3979
|
const file = memoryFilePath2(paths, fm.scope, fm.id, fm.module);
|
|
3976
3980
|
if (existsSync11(file)) continue;
|
|
3977
|
-
await mkdir6(
|
|
3981
|
+
await mkdir6(path12.dirname(file), { recursive: true });
|
|
3978
3982
|
await writeFile7(file, serializeMemory3({ frontmatter: fm, body }), "utf8");
|
|
3979
3983
|
written++;
|
|
3980
3984
|
}
|
|
@@ -4036,12 +4040,12 @@ function printInitReport(r) {
|
|
|
4036
4040
|
}
|
|
4037
4041
|
async function writeCursorHaiveRule(root) {
|
|
4038
4042
|
const relPath = ".cursor/rules/haive-mcp-required.mdc";
|
|
4039
|
-
const target =
|
|
4043
|
+
const target = path12.join(root, relPath);
|
|
4040
4044
|
if (existsSync11(target)) {
|
|
4041
4045
|
ui.info(`Cursor rule ${relPath} already exists \u2014 skipped`);
|
|
4042
4046
|
return;
|
|
4043
4047
|
}
|
|
4044
|
-
await mkdir6(
|
|
4048
|
+
await mkdir6(path12.dirname(target), { recursive: true });
|
|
4045
4049
|
await writeFile7(target, CURSOR_HAIVE_RULE_MDC, "utf8");
|
|
4046
4050
|
ui.success(`Created Cursor rule ${relPath}`);
|
|
4047
4051
|
}
|
|
@@ -4070,25 +4074,25 @@ var RUNTIME_GITIGNORE_BODY = `*
|
|
|
4070
4074
|
`;
|
|
4071
4075
|
async function ensureAiRuntimeLayout(runtimeDir) {
|
|
4072
4076
|
await mkdir6(runtimeDir, { recursive: true });
|
|
4073
|
-
const gi =
|
|
4077
|
+
const gi = path12.join(runtimeDir, ".gitignore");
|
|
4074
4078
|
if (!existsSync11(gi)) {
|
|
4075
4079
|
await writeFile7(gi, RUNTIME_GITIGNORE_BODY, "utf8");
|
|
4076
4080
|
}
|
|
4077
|
-
const readme =
|
|
4081
|
+
const readme = path12.join(runtimeDir, "README.md");
|
|
4078
4082
|
if (!existsSync11(readme)) {
|
|
4079
4083
|
await writeFile7(readme, RUNTIME_README_BODY, "utf8");
|
|
4080
4084
|
}
|
|
4081
4085
|
}
|
|
4082
4086
|
async function ensureAiCacheLayout(cacheDir) {
|
|
4083
4087
|
await mkdir6(cacheDir, { recursive: true });
|
|
4084
|
-
const gi =
|
|
4088
|
+
const gi = path12.join(cacheDir, ".gitignore");
|
|
4085
4089
|
if (!existsSync11(gi)) {
|
|
4086
4090
|
await writeFile7(gi, "*\n!.gitignore\n", "utf8");
|
|
4087
4091
|
}
|
|
4088
4092
|
}
|
|
4089
4093
|
async function ensureGitignoreEntries(root, patterns) {
|
|
4090
4094
|
try {
|
|
4091
|
-
const gitignorePath =
|
|
4095
|
+
const gitignorePath = path12.join(root, ".gitignore");
|
|
4092
4096
|
let existing = "";
|
|
4093
4097
|
if (existsSync11(gitignorePath)) {
|
|
4094
4098
|
existing = await readFile6(gitignorePath, "utf8");
|
|
@@ -4105,14 +4109,14 @@ async function ensureGitignoreEntries(root, patterns) {
|
|
|
4105
4109
|
// src/commands/install-hooks.ts
|
|
4106
4110
|
import { mkdir as mkdir8, writeFile as writeFile9, chmod, readFile as readFile8 } from "fs/promises";
|
|
4107
4111
|
import { existsSync as existsSync13 } from "fs";
|
|
4108
|
-
import
|
|
4112
|
+
import path14 from "path";
|
|
4109
4113
|
import "commander";
|
|
4110
4114
|
import { findProjectRoot as findProjectRoot8 } from "@hiveai/core";
|
|
4111
4115
|
|
|
4112
4116
|
// src/utils/claude-hooks.ts
|
|
4113
4117
|
import { existsSync as existsSync12 } from "fs";
|
|
4114
4118
|
import { mkdir as mkdir7, readFile as readFile7, writeFile as writeFile8 } from "fs/promises";
|
|
4115
|
-
import
|
|
4119
|
+
import path13 from "path";
|
|
4116
4120
|
var HAIVE_HOOK_TAG = "haive-enforcement";
|
|
4117
4121
|
var POST_TOOL_USE_GROUP = {
|
|
4118
4122
|
matcher: "Edit|Write|Bash",
|
|
@@ -4208,7 +4212,7 @@ async function installClaudeHooksAtPath(settingsPath) {
|
|
|
4208
4212
|
created = true;
|
|
4209
4213
|
}
|
|
4210
4214
|
const patched = patchClaudeSettings(raw);
|
|
4211
|
-
await mkdir7(
|
|
4215
|
+
await mkdir7(path13.dirname(settingsPath), { recursive: true });
|
|
4212
4216
|
await writeFile8(settingsPath, JSON.stringify(patched, null, 2) + "\n", "utf8");
|
|
4213
4217
|
return { settingsPath, created };
|
|
4214
4218
|
}
|
|
@@ -4224,9 +4228,9 @@ async function uninstallClaudeHooksAtPath(settingsPath) {
|
|
|
4224
4228
|
function defaultClaudeSettingsPath(scope, projectRoot) {
|
|
4225
4229
|
if (scope === "user") {
|
|
4226
4230
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
4227
|
-
return
|
|
4231
|
+
return path13.join(home, ".claude", "settings.json");
|
|
4228
4232
|
}
|
|
4229
|
-
return
|
|
4233
|
+
return path13.join(projectRoot, ".claude", "settings.local.json");
|
|
4230
4234
|
}
|
|
4231
4235
|
|
|
4232
4236
|
// src/commands/install-hooks.ts
|
|
@@ -4283,18 +4287,18 @@ fi
|
|
|
4283
4287
|
];
|
|
4284
4288
|
async function installGitHooks(opts) {
|
|
4285
4289
|
const root = findProjectRoot8(opts.dir);
|
|
4286
|
-
const gitDir =
|
|
4290
|
+
const gitDir = path14.join(root, ".git");
|
|
4287
4291
|
if (!existsSync13(gitDir)) {
|
|
4288
4292
|
ui.error(`No .git directory at ${root}.`);
|
|
4289
4293
|
process.exitCode = 1;
|
|
4290
4294
|
return;
|
|
4291
4295
|
}
|
|
4292
|
-
const hooksDir =
|
|
4296
|
+
const hooksDir = path14.join(gitDir, "hooks");
|
|
4293
4297
|
await mkdir8(hooksDir, { recursive: true });
|
|
4294
4298
|
let installed = 0;
|
|
4295
4299
|
let skipped = 0;
|
|
4296
4300
|
for (const { name, body } of HOOKS) {
|
|
4297
|
-
const file =
|
|
4301
|
+
const file = path14.join(hooksDir, name);
|
|
4298
4302
|
if (existsSync13(file) && !opts.force) {
|
|
4299
4303
|
const existing = await readFile8(file, "utf8");
|
|
4300
4304
|
if (!existing.includes(HOOK_MARKER)) {
|
|
@@ -4360,7 +4364,7 @@ function registerInstallHooks(program2) {
|
|
|
4360
4364
|
// src/commands/observe.ts
|
|
4361
4365
|
import { appendFile, mkdir as mkdir9 } from "fs/promises";
|
|
4362
4366
|
import { existsSync as existsSync14 } from "fs";
|
|
4363
|
-
import
|
|
4367
|
+
import path15 from "path";
|
|
4364
4368
|
import "commander";
|
|
4365
4369
|
import { findProjectRoot as findProjectRoot9, resolveHaivePaths as resolveHaivePaths7 } from "@hiveai/core";
|
|
4366
4370
|
var MAX_STDIN_BYTES = 256 * 1024;
|
|
@@ -4478,10 +4482,10 @@ function registerObserve(program2) {
|
|
|
4478
4482
|
files: extractFiles(payload),
|
|
4479
4483
|
...failureHint ? { failure_hint: true } : {}
|
|
4480
4484
|
};
|
|
4481
|
-
const cacheDir =
|
|
4485
|
+
const cacheDir = path15.join(paths.haiveDir, ".cache");
|
|
4482
4486
|
await mkdir9(cacheDir, { recursive: true });
|
|
4483
4487
|
await appendFile(
|
|
4484
|
-
|
|
4488
|
+
path15.join(cacheDir, "observations.jsonl"),
|
|
4485
4489
|
JSON.stringify(observation) + "\n",
|
|
4486
4490
|
"utf8"
|
|
4487
4491
|
);
|
|
@@ -4500,7 +4504,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
4500
4504
|
import { findProjectRoot as findProjectRoot10, resolveHaivePaths as resolveHaivePaths8 } from "@hiveai/core";
|
|
4501
4505
|
import { mkdir as mkdir10, writeFile as writeFile10 } from "fs/promises";
|
|
4502
4506
|
import { existsSync as existsSync15 } from "fs";
|
|
4503
|
-
import
|
|
4507
|
+
import path16 from "path";
|
|
4504
4508
|
import { z } from "zod";
|
|
4505
4509
|
import { readFile as readFile9, readdir as readdir3 } from "fs/promises";
|
|
4506
4510
|
import { existsSync as existsSync22 } from "fs";
|
|
@@ -4670,6 +4674,7 @@ import path82 from "path";
|
|
|
4670
4674
|
import { execSync } from "child_process";
|
|
4671
4675
|
import { readFile as readFile52, writeFile as writeFile13 } from "fs/promises";
|
|
4672
4676
|
import { existsSync as existsSync21 } from "fs";
|
|
4677
|
+
import path112 from "path";
|
|
4673
4678
|
import {
|
|
4674
4679
|
allocateBudget,
|
|
4675
4680
|
briefingProofLine,
|
|
@@ -4729,7 +4734,7 @@ import { z as z23 } from "zod";
|
|
|
4729
4734
|
import { z as z24 } from "zod";
|
|
4730
4735
|
import { existsSync as existsSync24 } from "fs";
|
|
4731
4736
|
import { spawn } from "child_process";
|
|
4732
|
-
import
|
|
4737
|
+
import path122 from "path";
|
|
4733
4738
|
import {
|
|
4734
4739
|
deriveConfidence as deriveConfidence5,
|
|
4735
4740
|
getUsage as getUsage7,
|
|
@@ -4742,7 +4747,6 @@ import { z as z25 } from "zod";
|
|
|
4742
4747
|
import { existsSync as existsSync25 } from "fs";
|
|
4743
4748
|
import {
|
|
4744
4749
|
addedLinesFromDiff,
|
|
4745
|
-
appendPreventionEvent,
|
|
4746
4750
|
BRIDGE_TARGET_PATH,
|
|
4747
4751
|
buildDocFrequency,
|
|
4748
4752
|
CODE_STOPWORDS,
|
|
@@ -4754,9 +4758,8 @@ import {
|
|
|
4754
4758
|
loadUsageIndex as loadUsageIndex10,
|
|
4755
4759
|
literalMatchesAnyToken as literalMatchesAnyToken3,
|
|
4756
4760
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths4,
|
|
4757
|
-
|
|
4761
|
+
recordPreventionHits,
|
|
4758
4762
|
runSensors,
|
|
4759
|
-
saveUsageIndex as saveUsageIndex4,
|
|
4760
4763
|
sensorTargetsFromDiff,
|
|
4761
4764
|
tokenizeQuery as tokenizeQuery3
|
|
4762
4765
|
} from "@hiveai/core";
|
|
@@ -4790,7 +4793,7 @@ import { z as z29 } from "zod";
|
|
|
4790
4793
|
import { z as z30 } from "zod";
|
|
4791
4794
|
import { mkdir as mkdir82, writeFile as writeFile14 } from "fs/promises";
|
|
4792
4795
|
import { existsSync as existsSync29 } from "fs";
|
|
4793
|
-
import
|
|
4796
|
+
import path132 from "path";
|
|
4794
4797
|
import { execSync as execSync2 } from "child_process";
|
|
4795
4798
|
import {
|
|
4796
4799
|
buildFrontmatter as buildFrontmatter5,
|
|
@@ -4803,7 +4806,8 @@ import { existsSync as existsSync30 } from "fs";
|
|
|
4803
4806
|
import {
|
|
4804
4807
|
findLexicalConflictPairs,
|
|
4805
4808
|
findTopicStatusConflictPairs,
|
|
4806
|
-
loadMemoriesFromDir as loadMemoriesFromDir23
|
|
4809
|
+
loadMemoriesFromDir as loadMemoriesFromDir23,
|
|
4810
|
+
planConflictResolution
|
|
4807
4811
|
} from "@hiveai/core";
|
|
4808
4812
|
import { z as z32 } from "zod";
|
|
4809
4813
|
import { resolveProjectInfo } from "@hiveai/core";
|
|
@@ -4835,14 +4839,14 @@ var BootstrapProjectSaveInputSchema = {
|
|
|
4835
4839
|
overwrite: z.boolean().default(false).describe("Overwrite an existing file instead of failing")
|
|
4836
4840
|
};
|
|
4837
4841
|
async function bootstrapProjectSave(input, ctx) {
|
|
4838
|
-
const target = input.module ?
|
|
4842
|
+
const target = input.module ? path16.join(ctx.paths.modulesContextDir, input.module, "context.md") : ctx.paths.projectContext;
|
|
4839
4843
|
const exists = existsSync15(target);
|
|
4840
4844
|
if (exists && !input.overwrite) {
|
|
4841
4845
|
throw new Error(
|
|
4842
4846
|
`${target} already exists. Pass overwrite=true to replace it.`
|
|
4843
4847
|
);
|
|
4844
4848
|
}
|
|
4845
|
-
await mkdir10(
|
|
4849
|
+
await mkdir10(path16.dirname(target), { recursive: true });
|
|
4846
4850
|
await writeFile10(target, input.content, "utf8");
|
|
4847
4851
|
return {
|
|
4848
4852
|
file_path: target,
|
|
@@ -5807,7 +5811,15 @@ async function memTried(input, ctx) {
|
|
|
5807
5811
|
throw new Error(`Memory already exists at ${file}`);
|
|
5808
5812
|
}
|
|
5809
5813
|
await writeFile82(file, serializeMemory7({ frontmatter, body }), "utf8");
|
|
5810
|
-
|
|
5814
|
+
const sensorGenerated = Boolean(sensor);
|
|
5815
|
+
const hint = sensorGenerated ? void 0 : input.paths.length === 0 ? "No sensor was generated (no `paths` given), so this lesson is feedforward-only \u2014 it will be briefed but the gate cannot block the repeat. Re-run with `paths` set to the file(s) where the mistake lives to close the loop." : "No sensor could be derived from the wording (no distinctive code token). The lesson is briefed but not enforced; add a concrete forbidden token/value, or attach a sensor manually, to make the gate block the repeat.";
|
|
5816
|
+
return {
|
|
5817
|
+
id: frontmatter.id,
|
|
5818
|
+
scope: frontmatter.scope,
|
|
5819
|
+
file_path: file,
|
|
5820
|
+
sensor_generated: sensorGenerated,
|
|
5821
|
+
...hint ? { hint } : {}
|
|
5822
|
+
};
|
|
5811
5823
|
}
|
|
5812
5824
|
var IngestFindingsInputSchema = {
|
|
5813
5825
|
format: z16.enum(["sarif", "sonar"]).describe("Report format: 'sarif' (ESLint/Semgrep/CodeQL) or 'sonar' (SonarQube issues JSON)"),
|
|
@@ -6572,6 +6584,7 @@ async function getBriefing(input, ctx) {
|
|
|
6572
6584
|
const topSymbols = Object.entries(codeMap.files).flatMap(
|
|
6573
6585
|
([fp, entry]) => entry.exports.slice(0, 3).map((e) => `${e.name} (${fp.split("/").slice(-2).join("/")})`)
|
|
6574
6586
|
).slice(0, 15).join(", ");
|
|
6587
|
+
const commands = await detectRunCommands(ctx.paths.root);
|
|
6575
6588
|
projectContext = `# Project context (auto-generated by hAIve)
|
|
6576
6589
|
|
|
6577
6590
|
> \u26A0 This is a minimal auto-generated context based on the code-map. Invoke the \`bootstrap_project\` MCP prompt to replace it with a full analysis.
|
|
@@ -6581,7 +6594,10 @@ async function getBriefing(input, ctx) {
|
|
|
6581
6594
|
- **Main file types:** ${topExts}
|
|
6582
6595
|
- **Generated at:** ${codeMap.generated_at}
|
|
6583
6596
|
|
|
6584
|
-
|
|
6597
|
+
` + (commands ? `## Commands
|
|
6598
|
+
${commands}
|
|
6599
|
+
|
|
6600
|
+
` : "") + `## Key exports (sample)
|
|
6585
6601
|
` + topSymbols + "\n";
|
|
6586
6602
|
autoContextGenerated = true;
|
|
6587
6603
|
setupWarnings.push(
|
|
@@ -6861,6 +6877,19 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
6861
6877
|
}
|
|
6862
6878
|
};
|
|
6863
6879
|
}
|
|
6880
|
+
async function detectRunCommands(root) {
|
|
6881
|
+
const pkgPath = path112.join(root, "package.json");
|
|
6882
|
+
if (!existsSync21(pkgPath)) return null;
|
|
6883
|
+
try {
|
|
6884
|
+
const pkg = JSON.parse(await readFile52(pkgPath, "utf8"));
|
|
6885
|
+
const scripts = pkg.scripts ?? {};
|
|
6886
|
+
const order = ["test", "build", "lint", "typecheck", "type-check", "dev", "start"];
|
|
6887
|
+
const lines = order.filter((name) => typeof scripts[name] === "string" && scripts[name].trim() !== "").map((name) => `- \`${name}\`: \`${scripts[name]}\``);
|
|
6888
|
+
return lines.length > 0 ? lines.join("\n") : null;
|
|
6889
|
+
} catch {
|
|
6890
|
+
return null;
|
|
6891
|
+
}
|
|
6892
|
+
}
|
|
6864
6893
|
var CodeMapInputSchema = {
|
|
6865
6894
|
file: z20.string().optional().describe("Filter to files whose path contains this substring"),
|
|
6866
6895
|
symbol: z20.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
|
|
@@ -7084,7 +7113,7 @@ var WhyThisFileInputSchema = {
|
|
|
7084
7113
|
memory_limit: z25.number().int().positive().max(20).default(5).describe("Cap on memories anchored to this path.")
|
|
7085
7114
|
};
|
|
7086
7115
|
async function whyThisFile(input, ctx) {
|
|
7087
|
-
const fileExists = existsSync24(
|
|
7116
|
+
const fileExists = existsSync24(path122.join(ctx.paths.root, input.path));
|
|
7088
7117
|
const [commits, memories, codeMap] = await Promise.all([
|
|
7089
7118
|
runGitLog(ctx.paths.root, input.path, input.git_log_limit).catch(() => []),
|
|
7090
7119
|
collectAnchoredMemories(ctx, input.path, input.memory_limit),
|
|
@@ -7358,19 +7387,7 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
7358
7387
|
const strongCatches = warnings.filter(
|
|
7359
7388
|
(w) => w.reasons.includes("sensor") || w.reasons.includes("anchor") && w.reasons.includes("literal")
|
|
7360
7389
|
);
|
|
7361
|
-
|
|
7362
|
-
const recordedIds = [];
|
|
7363
|
-
for (const w of strongCatches) if (recordPrevention(usage, w.id)) recordedIds.push(w.id);
|
|
7364
|
-
if (recordedIds.length > 0) {
|
|
7365
|
-
await saveUsageIndex4(ctx.paths, usage).catch(() => {
|
|
7366
|
-
});
|
|
7367
|
-
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
7368
|
-
for (const id of recordedIds) {
|
|
7369
|
-
await appendPreventionEvent(ctx.paths, { at, id, source: "anti-pattern" }).catch(() => {
|
|
7370
|
-
});
|
|
7371
|
-
}
|
|
7372
|
-
}
|
|
7373
|
-
}
|
|
7390
|
+
await recordPreventionHits(ctx.paths, strongCatches.map((w) => w.id), "anti-pattern");
|
|
7374
7391
|
return {
|
|
7375
7392
|
scanned: negative.length,
|
|
7376
7393
|
warnings
|
|
@@ -8128,13 +8145,13 @@ async function patternDetect(input, ctx) {
|
|
|
8128
8145
|
try {
|
|
8129
8146
|
const changedFiles = gitChangedFiles(ctx.paths.root, input.since_days);
|
|
8130
8147
|
const configFiles = changedFiles.filter(
|
|
8131
|
-
(f) => CONFIG_PATTERNS.some((p) =>
|
|
8148
|
+
(f) => CONFIG_PATTERNS.some((p) => path132.basename(f.toLowerCase()).includes(p))
|
|
8132
8149
|
);
|
|
8133
8150
|
for (const file of configFiles.slice(0, 5)) {
|
|
8134
8151
|
const diff = gitFileDiff(ctx.paths.root, file, input.since_days);
|
|
8135
8152
|
if (!diff) continue;
|
|
8136
|
-
const parentDir =
|
|
8137
|
-
const baseName =
|
|
8153
|
+
const parentDir = path132.basename(path132.dirname(file));
|
|
8154
|
+
const baseName = path132.basename(file).replace(/\.[^.]+$/, "");
|
|
8138
8155
|
const slug = `${parentDir}-${baseName}`.replace(/[^a-z0-9]/gi, "-").toLowerCase().slice(0, 40);
|
|
8139
8156
|
matches.push({
|
|
8140
8157
|
kind: "config_change",
|
|
@@ -8198,7 +8215,7 @@ async function patternDetect(input, ctx) {
|
|
|
8198
8215
|
for (const [p, { count, tools }] of pathCounts) {
|
|
8199
8216
|
if (count < HOT_FILE_MIN) continue;
|
|
8200
8217
|
if (tools.has("mem_tried") || tools.has("mem_observe")) continue;
|
|
8201
|
-
if (CONFIG_PATTERNS.some((cp2) =>
|
|
8218
|
+
if (CONFIG_PATTERNS.some((cp2) => path132.basename(p).includes(cp2))) continue;
|
|
8202
8219
|
const slug = p.replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").slice(0, 40);
|
|
8203
8220
|
matches.push({
|
|
8204
8221
|
kind: "hot_file",
|
|
@@ -8245,7 +8262,7 @@ async function patternDetect(input, ctx) {
|
|
|
8245
8262
|
void 0
|
|
8246
8263
|
);
|
|
8247
8264
|
if (existsSync29(file)) continue;
|
|
8248
|
-
await mkdir82(
|
|
8265
|
+
await mkdir82(path132.dirname(file), { recursive: true });
|
|
8249
8266
|
await writeFile14(
|
|
8250
8267
|
file,
|
|
8251
8268
|
serializeMemory12({ frontmatter: fm, body: match.proposed_body }),
|
|
@@ -8288,6 +8305,18 @@ function gitFileDiff(root, file, sinceDays) {
|
|
|
8288
8305
|
return null;
|
|
8289
8306
|
}
|
|
8290
8307
|
}
|
|
8308
|
+
function suggestResolution(byId, idA, idB) {
|
|
8309
|
+
const a = byId.get(idA);
|
|
8310
|
+
const b = byId.get(idB);
|
|
8311
|
+
if (!a || !b) return null;
|
|
8312
|
+
const plan = planConflictResolution(a, b);
|
|
8313
|
+
return {
|
|
8314
|
+
keep_id: plan.keep_id,
|
|
8315
|
+
supersede_id: plan.supersede_id,
|
|
8316
|
+
reason: plan.reason,
|
|
8317
|
+
command: `haive memory resolve-conflict ${plan.keep_id} ${plan.supersede_id} --yes`
|
|
8318
|
+
};
|
|
8319
|
+
}
|
|
8291
8320
|
var MemConflictCandidatesInputSchema = {
|
|
8292
8321
|
since_days: z32.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
|
|
8293
8322
|
types: z32.array(z32.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
|
|
@@ -8309,6 +8338,7 @@ async function memConflictCandidates(input, ctx) {
|
|
|
8309
8338
|
};
|
|
8310
8339
|
}
|
|
8311
8340
|
const all = await loadMemoriesFromDir23(ctx.paths.memoriesDir);
|
|
8341
|
+
const byId = new Map(all.map((m) => [m.memory.frontmatter.id, m]));
|
|
8312
8342
|
const { pairs, scanned, truncated } = findLexicalConflictPairs(all, {
|
|
8313
8343
|
sinceDays: input.since_days,
|
|
8314
8344
|
types: input.types,
|
|
@@ -8317,8 +8347,22 @@ async function memConflictCandidates(input, ctx) {
|
|
|
8317
8347
|
maxScan: input.max_scan
|
|
8318
8348
|
});
|
|
8319
8349
|
const topicStatusPairs = findTopicStatusConflictPairs(all, input.max_topic_pairs);
|
|
8350
|
+
const enrichedPairs = pairs.map((p) => ({
|
|
8351
|
+
...p,
|
|
8352
|
+
suggested_resolution: suggestResolution(byId, p.id_a, p.id_b)
|
|
8353
|
+
}));
|
|
8354
|
+
const enrichedTopicStatusPairs = topicStatusPairs.map((p) => ({
|
|
8355
|
+
...p,
|
|
8356
|
+
suggested_resolution: suggestResolution(byId, p.id_a, p.id_b)
|
|
8357
|
+
}));
|
|
8320
8358
|
const notice = pairs.length === 0 && topicStatusPairs.length === 0 ? "No lexical or topic-status candidates \u2014 widen since_days/types or lower min_jaccard." : void 0;
|
|
8321
|
-
return {
|
|
8359
|
+
return {
|
|
8360
|
+
pairs: enrichedPairs,
|
|
8361
|
+
topic_status_pairs: enrichedTopicStatusPairs,
|
|
8362
|
+
scanned,
|
|
8363
|
+
truncated,
|
|
8364
|
+
notice
|
|
8365
|
+
};
|
|
8322
8366
|
}
|
|
8323
8367
|
var MemResolveProjectInputSchema = {
|
|
8324
8368
|
cwd: z33.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
|
|
@@ -8632,7 +8676,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
8632
8676
|
};
|
|
8633
8677
|
}
|
|
8634
8678
|
var SERVER_NAME = "haive";
|
|
8635
|
-
var SERVER_VERSION = "0.
|
|
8679
|
+
var SERVER_VERSION = "0.24.0";
|
|
8636
8680
|
function jsonResult(data) {
|
|
8637
8681
|
return {
|
|
8638
8682
|
content: [
|
|
@@ -9666,7 +9710,7 @@ function registerMcp(program2) {
|
|
|
9666
9710
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
9667
9711
|
import { readFile as readFile10, writeFile as writeFile15, mkdir as mkdir11 } from "fs/promises";
|
|
9668
9712
|
import { existsSync as existsSync33 } from "fs";
|
|
9669
|
-
import
|
|
9713
|
+
import path17 from "path";
|
|
9670
9714
|
import "commander";
|
|
9671
9715
|
import {
|
|
9672
9716
|
DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE2,
|
|
@@ -9860,10 +9904,10 @@ function registerSync(program2) {
|
|
|
9860
9904
|
const maxInject = Math.max(1, Number(opts.bridgeMaxMemories ?? 5));
|
|
9861
9905
|
let bridgeTargets;
|
|
9862
9906
|
if (opts.bridgeFile) {
|
|
9863
|
-
bridgeTargets = [
|
|
9907
|
+
bridgeTargets = [path17.resolve(opts.bridgeFile)];
|
|
9864
9908
|
} else {
|
|
9865
|
-
const agentsMd =
|
|
9866
|
-
bridgeTargets = [
|
|
9909
|
+
const agentsMd = path17.join(root, "AGENTS.md");
|
|
9910
|
+
bridgeTargets = [path17.join(root, "CLAUDE.md")];
|
|
9867
9911
|
if (existsSync33(agentsMd)) bridgeTargets.push(agentsMd);
|
|
9868
9912
|
}
|
|
9869
9913
|
for (const bridgeFile of bridgeTargets) {
|
|
@@ -9995,10 +10039,10 @@ Wait for **explicit confirmation** before acting.
|
|
|
9995
10039
|
topic: `dep-bump-${slugParts}`
|
|
9996
10040
|
});
|
|
9997
10041
|
if (!dryRun) {
|
|
9998
|
-
const teamDir =
|
|
10042
|
+
const teamDir = path17.join(paths.memoriesDir, "team");
|
|
9999
10043
|
await mkdir11(teamDir, { recursive: true });
|
|
10000
10044
|
await writeFile15(
|
|
10001
|
-
|
|
10045
|
+
path17.join(teamDir, `${fm.id}.md`),
|
|
10002
10046
|
serializeMemory13({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
10003
10047
|
"utf8"
|
|
10004
10048
|
);
|
|
@@ -10064,10 +10108,10 @@ Wait for **explicit confirmation** before acting.
|
|
|
10064
10108
|
topic: `contract-breaking-${diff.contract}`
|
|
10065
10109
|
});
|
|
10066
10110
|
if (!dryRun) {
|
|
10067
|
-
const teamDir =
|
|
10111
|
+
const teamDir = path17.join(paths.memoriesDir, "team");
|
|
10068
10112
|
await mkdir11(teamDir, { recursive: true });
|
|
10069
10113
|
await writeFile15(
|
|
10070
|
-
|
|
10114
|
+
path17.join(teamDir, `${fm.id}.md`),
|
|
10071
10115
|
serializeMemory13({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
10072
10116
|
"utf8"
|
|
10073
10117
|
);
|
|
@@ -10190,11 +10234,11 @@ ${BRIDGE_END}`;
|
|
|
10190
10234
|
const startIdx = existing.indexOf(BRIDGE_START);
|
|
10191
10235
|
const endIdx = existing.indexOf(BRIDGE_END);
|
|
10192
10236
|
if (startIdx !== -1 && endIdx === -1) {
|
|
10193
|
-
ui.warn(`${
|
|
10237
|
+
ui.warn(`${path17.relative(root, bridgeFile)}: found ${BRIDGE_START} without ${BRIDGE_END}. Fix the file manually before running --inject-bridge.`);
|
|
10194
10238
|
return;
|
|
10195
10239
|
}
|
|
10196
10240
|
if (startIdx === -1 && endIdx !== -1) {
|
|
10197
|
-
ui.warn(`${
|
|
10241
|
+
ui.warn(`${path17.relative(root, bridgeFile)}: found ${BRIDGE_END} without ${BRIDGE_START}. Fix the file manually before running --inject-bridge.`);
|
|
10198
10242
|
return;
|
|
10199
10243
|
}
|
|
10200
10244
|
let updated;
|
|
@@ -10202,14 +10246,14 @@ ${BRIDGE_END}`;
|
|
|
10202
10246
|
updated = existing.slice(0, startIdx) + injected + existing.slice(endIdx + BRIDGE_END.length);
|
|
10203
10247
|
} else {
|
|
10204
10248
|
if (!fileExists && !quiet) {
|
|
10205
|
-
ui.info(`Creating ${
|
|
10249
|
+
ui.info(`Creating ${path17.relative(root, bridgeFile)} with haive memory block.`);
|
|
10206
10250
|
}
|
|
10207
10251
|
updated = existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + injected + "\n";
|
|
10208
10252
|
}
|
|
10209
10253
|
await writeFile15(bridgeFile, updated, "utf8");
|
|
10210
10254
|
if (!quiet) {
|
|
10211
10255
|
console.log(
|
|
10212
|
-
ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${
|
|
10256
|
+
ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path17.relative(root, bridgeFile)}`)
|
|
10213
10257
|
);
|
|
10214
10258
|
}
|
|
10215
10259
|
}
|
|
@@ -10236,7 +10280,7 @@ function collectSinceChanges(root, ref) {
|
|
|
10236
10280
|
import { createHash as createHash2 } from "crypto";
|
|
10237
10281
|
import { mkdir as mkdir12, readFile as readFile11, writeFile as writeFile16 } from "fs/promises";
|
|
10238
10282
|
import { existsSync as existsSync34 } from "fs";
|
|
10239
|
-
import
|
|
10283
|
+
import path18 from "path";
|
|
10240
10284
|
import "commander";
|
|
10241
10285
|
import {
|
|
10242
10286
|
buildFrontmatter as buildFrontmatter7,
|
|
@@ -10294,7 +10338,7 @@ function registerMemoryAdd(memory2) {
|
|
|
10294
10338
|
const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
|
|
10295
10339
|
const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
|
|
10296
10340
|
if (anchorPaths.length > 0) {
|
|
10297
|
-
const missing = anchorPaths.filter((p) => !existsSync34(
|
|
10341
|
+
const missing = anchorPaths.filter((p) => !existsSync34(path18.resolve(root, p)));
|
|
10298
10342
|
if (missing.length > 0) {
|
|
10299
10343
|
ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
|
|
10300
10344
|
for (const p of missing) ui.warn(` \u2717 ${p}`);
|
|
@@ -10358,7 +10402,7 @@ TODO \u2014 write the memory body.
|
|
|
10358
10402
|
const suggestedSensor = !newFrontmatter.sensor ? suggestSensorForCliMemory(opts.type, body, newFrontmatter.anchor.paths) : null;
|
|
10359
10403
|
if (suggestedSensor) newFrontmatter.sensor = suggestedSensor;
|
|
10360
10404
|
await writeFile16(topicMatch.filePath, serializeMemory14({ frontmatter: newFrontmatter, body }), "utf8");
|
|
10361
|
-
ui.success(`Updated (topic upsert) ${
|
|
10405
|
+
ui.success(`Updated (topic upsert) ${path18.relative(root, topicMatch.filePath)}`);
|
|
10362
10406
|
ui.info(`id=${fm.id} revision=${revisionCount}`);
|
|
10363
10407
|
if (suggestedSensor) ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(suggestedSensor.pattern)}`);
|
|
10364
10408
|
await runPostMemoryAutopilot(root, paths, config);
|
|
@@ -10382,7 +10426,7 @@ TODO \u2014 write the memory body.
|
|
|
10382
10426
|
activation
|
|
10383
10427
|
});
|
|
10384
10428
|
const file = memoryFilePath7(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
10385
|
-
await mkdir12(
|
|
10429
|
+
await mkdir12(path18.dirname(file), { recursive: true });
|
|
10386
10430
|
if (existsSync34(file)) {
|
|
10387
10431
|
ui.error(`Memory already exists at ${file}`);
|
|
10388
10432
|
process.exitCode = 1;
|
|
@@ -10401,7 +10445,7 @@ TODO \u2014 write the memory body.
|
|
|
10401
10445
|
}
|
|
10402
10446
|
}
|
|
10403
10447
|
await writeFile16(file, serializeMemory14({ frontmatter, body }), "utf8");
|
|
10404
|
-
ui.success(`Created ${
|
|
10448
|
+
ui.success(`Created ${path18.relative(root, file)}`);
|
|
10405
10449
|
ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
|
|
10406
10450
|
if (frontmatter.sensor?.autogen) {
|
|
10407
10451
|
ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(frontmatter.sensor.pattern)}`);
|
|
@@ -10481,7 +10525,7 @@ function slugify(value) {
|
|
|
10481
10525
|
|
|
10482
10526
|
// src/commands/memory-list.ts
|
|
10483
10527
|
import { existsSync as existsSync35 } from "fs";
|
|
10484
|
-
import
|
|
10528
|
+
import path19 from "path";
|
|
10485
10529
|
import "commander";
|
|
10486
10530
|
import { findProjectRoot as findProjectRoot14, resolveHaivePaths as resolveHaivePaths11 } from "@hiveai/core";
|
|
10487
10531
|
|
|
@@ -10534,7 +10578,7 @@ function registerMemoryList(memory2) {
|
|
|
10534
10578
|
);
|
|
10535
10579
|
const title = mem.body.match(/^#\s+(.+)$/m)?.[1]?.trim();
|
|
10536
10580
|
if (title && title !== fm.id) console.log(` ${title}`);
|
|
10537
|
-
console.log(` ${ui.dim(
|
|
10581
|
+
console.log(` ${ui.dim(path19.relative(root, filePath))}`);
|
|
10538
10582
|
}
|
|
10539
10583
|
const totalLabel = clipped > 0 ? `
|
|
10540
10584
|
${displayed.length} of ${filtered.length} memories shown (use --limit to adjust)` : `
|
|
@@ -10573,7 +10617,7 @@ function matchesFilters(loaded, opts) {
|
|
|
10573
10617
|
// src/commands/memory-promote.ts
|
|
10574
10618
|
import { mkdir as mkdir13, unlink as unlink2, writeFile as writeFile17 } from "fs/promises";
|
|
10575
10619
|
import { existsSync as existsSync36 } from "fs";
|
|
10576
|
-
import
|
|
10620
|
+
import path20 from "path";
|
|
10577
10621
|
import "commander";
|
|
10578
10622
|
import {
|
|
10579
10623
|
findProjectRoot as findProjectRoot15,
|
|
@@ -10620,11 +10664,11 @@ function registerMemoryPromote(memory2) {
|
|
|
10620
10664
|
body: found.memory.body
|
|
10621
10665
|
};
|
|
10622
10666
|
const newPath = memoryFilePath8(paths, "team", updated.frontmatter.id);
|
|
10623
|
-
await mkdir13(
|
|
10667
|
+
await mkdir13(path20.dirname(newPath), { recursive: true });
|
|
10624
10668
|
await writeFile17(newPath, serializeMemory15(updated), "utf8");
|
|
10625
10669
|
await unlink2(found.filePath);
|
|
10626
10670
|
ui.success(`Promoted ${id} to team scope (status=proposed)`);
|
|
10627
|
-
ui.info(`Now at ${
|
|
10671
|
+
ui.info(`Now at ${path20.relative(root, newPath)}`);
|
|
10628
10672
|
console.log(ui.dim(`\u2192 next: haive memory approve ${id} (validate for team use)`));
|
|
10629
10673
|
});
|
|
10630
10674
|
}
|
|
@@ -10632,7 +10676,7 @@ function registerMemoryPromote(memory2) {
|
|
|
10632
10676
|
// src/commands/memory-approve.ts
|
|
10633
10677
|
import { existsSync as existsSync37 } from "fs";
|
|
10634
10678
|
import { writeFile as writeFile18 } from "fs/promises";
|
|
10635
|
-
import
|
|
10679
|
+
import path21 from "path";
|
|
10636
10680
|
import "commander";
|
|
10637
10681
|
import {
|
|
10638
10682
|
findProjectRoot as findProjectRoot16,
|
|
@@ -10696,14 +10740,14 @@ function registerMemoryApprove(memory2) {
|
|
|
10696
10740
|
};
|
|
10697
10741
|
await writeFile18(found.filePath, serializeMemory16(next), "utf8");
|
|
10698
10742
|
ui.success(`Approved ${id} (status=validated)`);
|
|
10699
|
-
ui.info(
|
|
10743
|
+
ui.info(path21.relative(root, found.filePath));
|
|
10700
10744
|
});
|
|
10701
10745
|
}
|
|
10702
10746
|
|
|
10703
10747
|
// src/commands/memory-update.ts
|
|
10704
10748
|
import { readFile as readFile12, writeFile as writeFile19 } from "fs/promises";
|
|
10705
10749
|
import { existsSync as existsSync38 } from "fs";
|
|
10706
|
-
import
|
|
10750
|
+
import path23 from "path";
|
|
10707
10751
|
import "commander";
|
|
10708
10752
|
import {
|
|
10709
10753
|
findProjectRoot as findProjectRoot17,
|
|
@@ -10782,7 +10826,7 @@ function registerMemoryUpdate(memory2) {
|
|
|
10782
10826
|
serializeMemory17({ frontmatter: newFrontmatter, body: newBody }),
|
|
10783
10827
|
"utf8"
|
|
10784
10828
|
);
|
|
10785
|
-
ui.success(`Updated ${
|
|
10829
|
+
ui.success(`Updated ${path23.relative(root, loaded.filePath)}`);
|
|
10786
10830
|
ui.info(`fields: ${updated.join(", ")}`);
|
|
10787
10831
|
});
|
|
10788
10832
|
}
|
|
@@ -10803,7 +10847,7 @@ function parseCsv3(value) {
|
|
|
10803
10847
|
// src/commands/memory-auto-promote.ts
|
|
10804
10848
|
import { writeFile as writeFile20 } from "fs/promises";
|
|
10805
10849
|
import { existsSync as existsSync39 } from "fs";
|
|
10806
|
-
import
|
|
10850
|
+
import path24 from "path";
|
|
10807
10851
|
import "commander";
|
|
10808
10852
|
import {
|
|
10809
10853
|
DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE3,
|
|
@@ -10848,7 +10892,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
10848
10892
|
console.log(
|
|
10849
10893
|
`${ui.bold(opts.apply ? "PROMOTE" : "would promote")} ${mem.frontmatter.id} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
|
|
10850
10894
|
);
|
|
10851
|
-
console.log(` ${ui.dim(
|
|
10895
|
+
console.log(` ${ui.dim(path24.relative(root, filePath))}`);
|
|
10852
10896
|
if (opts.apply) {
|
|
10853
10897
|
const next = {
|
|
10854
10898
|
frontmatter: { ...mem.frontmatter, status: "validated" },
|
|
@@ -10867,7 +10911,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
10867
10911
|
import { spawn as spawn3 } from "child_process";
|
|
10868
10912
|
import { existsSync as existsSync40 } from "fs";
|
|
10869
10913
|
import { readFile as readFile13 } from "fs/promises";
|
|
10870
|
-
import
|
|
10914
|
+
import path25 from "path";
|
|
10871
10915
|
import "commander";
|
|
10872
10916
|
import {
|
|
10873
10917
|
findProjectRoot as findProjectRoot19,
|
|
@@ -10891,7 +10935,7 @@ function registerMemoryEdit(memory2) {
|
|
|
10891
10935
|
return;
|
|
10892
10936
|
}
|
|
10893
10937
|
const editor = opts.editor ?? process.env.EDITOR ?? process.env.VISUAL ?? "vi";
|
|
10894
|
-
ui.info(`Opening ${
|
|
10938
|
+
ui.info(`Opening ${path25.relative(root, found.filePath)} with ${editor}\u2026`);
|
|
10895
10939
|
const code = await runEditor(editor, found.filePath);
|
|
10896
10940
|
if (code !== 0) {
|
|
10897
10941
|
ui.warn(`Editor exited with status ${code}.`);
|
|
@@ -10919,7 +10963,7 @@ function runEditor(editor, file) {
|
|
|
10919
10963
|
|
|
10920
10964
|
// src/commands/memory-for-files.ts
|
|
10921
10965
|
import { existsSync as existsSync41 } from "fs";
|
|
10922
|
-
import
|
|
10966
|
+
import path26 from "path";
|
|
10923
10967
|
import "commander";
|
|
10924
10968
|
import {
|
|
10925
10969
|
deriveConfidence as deriveConfidence9,
|
|
@@ -11041,13 +11085,13 @@ function printGroup(root, label, loaded, usage) {
|
|
|
11041
11085
|
const u = getUsage13(usage, fm.id);
|
|
11042
11086
|
const conf = deriveConfidence9(fm, u);
|
|
11043
11087
|
console.log(`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`);
|
|
11044
|
-
console.log(` ${ui.dim(
|
|
11088
|
+
console.log(` ${ui.dim(path26.relative(root, filePath))}`);
|
|
11045
11089
|
}
|
|
11046
11090
|
}
|
|
11047
11091
|
|
|
11048
11092
|
// src/commands/memory-hot.ts
|
|
11049
11093
|
import { existsSync as existsSync43 } from "fs";
|
|
11050
|
-
import
|
|
11094
|
+
import path27 from "path";
|
|
11051
11095
|
import "commander";
|
|
11052
11096
|
import {
|
|
11053
11097
|
findProjectRoot as findProjectRoot21,
|
|
@@ -11089,7 +11133,7 @@ function registerMemoryHot(memory2) {
|
|
|
11089
11133
|
console.log(
|
|
11090
11134
|
`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(fm.status)} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
|
|
11091
11135
|
);
|
|
11092
|
-
console.log(` ${ui.dim(
|
|
11136
|
+
console.log(` ${ui.dim(path27.relative(root, filePath))}`);
|
|
11093
11137
|
}
|
|
11094
11138
|
ui.info(
|
|
11095
11139
|
`${candidates.length} hot (read \u2265${threshold}\xD7) \u2014 agents rely on these; promote with \`haive memory promote <id>\`.
|
|
@@ -11101,7 +11145,7 @@ function registerMemoryHot(memory2) {
|
|
|
11101
11145
|
// src/commands/memory-tried.ts
|
|
11102
11146
|
import { mkdir as mkdir14, writeFile as writeFile21 } from "fs/promises";
|
|
11103
11147
|
import { existsSync as existsSync44 } from "fs";
|
|
11104
|
-
import
|
|
11148
|
+
import path28 from "path";
|
|
11105
11149
|
import "commander";
|
|
11106
11150
|
import {
|
|
11107
11151
|
buildFrontmatter as buildFrontmatter8,
|
|
@@ -11157,16 +11201,22 @@ function registerMemoryTried(memory2) {
|
|
|
11157
11201
|
frontmatter.sensor = sensor;
|
|
11158
11202
|
}
|
|
11159
11203
|
const file = memoryFilePath9(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
11160
|
-
await mkdir14(
|
|
11204
|
+
await mkdir14(path28.dirname(file), { recursive: true });
|
|
11161
11205
|
if (existsSync44(file)) {
|
|
11162
11206
|
ui.error(`Memory already exists at ${file}`);
|
|
11163
11207
|
process.exitCode = 1;
|
|
11164
11208
|
return;
|
|
11165
11209
|
}
|
|
11166
11210
|
await writeFile21(file, serializeMemory19({ frontmatter, body }), "utf8");
|
|
11167
|
-
ui.success(`Recorded: ${
|
|
11211
|
+
ui.success(`Recorded: ${path28.relative(root, file)}`);
|
|
11168
11212
|
ui.info(`id=${frontmatter.id} type=attempt status=validated (auto-approved)`);
|
|
11169
|
-
if (sensor)
|
|
11213
|
+
if (sensor) {
|
|
11214
|
+
ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(sensor.pattern)}`);
|
|
11215
|
+
} else {
|
|
11216
|
+
ui.warn(
|
|
11217
|
+
frontmatter.anchor.paths.length === 0 ? "No sensor generated (no --paths) \u2014 lesson is briefed but NOT enforced. Add --paths to close the loop." : "No sensor could be derived from the wording \u2014 lesson is briefed but NOT enforced. Use a concrete forbidden token to enable a gate block."
|
|
11218
|
+
);
|
|
11219
|
+
}
|
|
11170
11220
|
});
|
|
11171
11221
|
}
|
|
11172
11222
|
function parseCsv4(value) {
|
|
@@ -11177,7 +11227,7 @@ function parseCsv4(value) {
|
|
|
11177
11227
|
// src/commands/memory-seed.ts
|
|
11178
11228
|
import { readFile as readFile14 } from "fs/promises";
|
|
11179
11229
|
import { existsSync as existsSync45 } from "fs";
|
|
11180
|
-
import
|
|
11230
|
+
import path29 from "path";
|
|
11181
11231
|
import "commander";
|
|
11182
11232
|
import {
|
|
11183
11233
|
findProjectRoot as findProjectRoot23,
|
|
@@ -11186,7 +11236,7 @@ import {
|
|
|
11186
11236
|
} from "@hiveai/core";
|
|
11187
11237
|
async function readDependencyMap(root) {
|
|
11188
11238
|
try {
|
|
11189
|
-
const raw = await readFile14(
|
|
11239
|
+
const raw = await readFile14(path29.join(root, "package.json"), "utf8");
|
|
11190
11240
|
const pkg = JSON.parse(raw);
|
|
11191
11241
|
return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
11192
11242
|
} catch {
|
|
@@ -11273,7 +11323,7 @@ function registerMemorySeed(memory2) {
|
|
|
11273
11323
|
|
|
11274
11324
|
// src/commands/memory-pending.ts
|
|
11275
11325
|
import { existsSync as existsSync46 } from "fs";
|
|
11276
|
-
import
|
|
11326
|
+
import path30 from "path";
|
|
11277
11327
|
import "commander";
|
|
11278
11328
|
import {
|
|
11279
11329
|
findProjectRoot as findProjectRoot24,
|
|
@@ -11319,7 +11369,7 @@ function registerMemoryPending(memory2) {
|
|
|
11319
11369
|
console.log(
|
|
11320
11370
|
` ${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count}`)}`
|
|
11321
11371
|
);
|
|
11322
|
-
console.log(` ${ui.dim(
|
|
11372
|
+
console.log(` ${ui.dim(path30.relative(root, filePath))}`);
|
|
11323
11373
|
}
|
|
11324
11374
|
if (proposed.length > 0) console.log(ui.dim(` \u2192 haive memory approve <id> or haive memory auto-promote`));
|
|
11325
11375
|
console.log();
|
|
@@ -11334,7 +11384,7 @@ function registerMemoryPending(memory2) {
|
|
|
11334
11384
|
console.log(
|
|
11335
11385
|
` ${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count}`)}`
|
|
11336
11386
|
);
|
|
11337
|
-
console.log(` ${ui.dim(
|
|
11387
|
+
console.log(` ${ui.dim(path30.relative(root, filePath))}`);
|
|
11338
11388
|
}
|
|
11339
11389
|
console.log(ui.dim(` \u2192 haive memory approve <id> (activate) | haive memory promote <id> (share with team)`));
|
|
11340
11390
|
}
|
|
@@ -11344,7 +11394,7 @@ function registerMemoryPending(memory2) {
|
|
|
11344
11394
|
|
|
11345
11395
|
// src/commands/memory-query.ts
|
|
11346
11396
|
import { existsSync as existsSync47 } from "fs";
|
|
11347
|
-
import
|
|
11397
|
+
import path31 from "path";
|
|
11348
11398
|
import "commander";
|
|
11349
11399
|
import {
|
|
11350
11400
|
extractSnippet as extractSnippet2,
|
|
@@ -11401,7 +11451,7 @@ function registerMemoryQuery(memory2) {
|
|
|
11401
11451
|
const fm = mem.frontmatter;
|
|
11402
11452
|
const statusBadge = ui.statusBadge(fm.status);
|
|
11403
11453
|
console.log(`${ui.bold(fm.id)} ${ui.dim(fm.scope)} ${statusBadge}`);
|
|
11404
|
-
console.log(` ${ui.dim(
|
|
11454
|
+
console.log(` ${ui.dim(path31.relative(root, filePath))}`);
|
|
11405
11455
|
const snippet = extractSnippet2(mem.body, snippetNeedle);
|
|
11406
11456
|
if (snippet) console.log(` ${snippet}`);
|
|
11407
11457
|
}
|
|
@@ -11426,7 +11476,7 @@ import {
|
|
|
11426
11476
|
loadUsageIndex as loadUsageIndex18,
|
|
11427
11477
|
recordRejection as recordRejection3,
|
|
11428
11478
|
resolveHaivePaths as resolveHaivePaths23,
|
|
11429
|
-
saveUsageIndex as
|
|
11479
|
+
saveUsageIndex as saveUsageIndex4,
|
|
11430
11480
|
serializeMemory as serializeMemory20
|
|
11431
11481
|
} from "@hiveai/core";
|
|
11432
11482
|
function registerMemoryReject(memory2) {
|
|
@@ -11459,7 +11509,7 @@ function registerMemoryReject(memory2) {
|
|
|
11459
11509
|
);
|
|
11460
11510
|
const idx = await loadUsageIndex18(paths);
|
|
11461
11511
|
recordRejection3(idx, id, opts.reason ?? null);
|
|
11462
|
-
await
|
|
11512
|
+
await saveUsageIndex4(paths, idx);
|
|
11463
11513
|
const u = idx.by_id[id];
|
|
11464
11514
|
ui.success(
|
|
11465
11515
|
`Rejected ${id} (status=rejected, ${u.rejected_count} rejection${u.rejected_count === 1 ? "" : "s"})`
|
|
@@ -11471,14 +11521,14 @@ function registerMemoryReject(memory2) {
|
|
|
11471
11521
|
// src/commands/memory-rm.ts
|
|
11472
11522
|
import { existsSync as existsSync49 } from "fs";
|
|
11473
11523
|
import { unlink as unlink3 } from "fs/promises";
|
|
11474
|
-
import
|
|
11524
|
+
import path33 from "path";
|
|
11475
11525
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
11476
11526
|
import "commander";
|
|
11477
11527
|
import {
|
|
11478
11528
|
findProjectRoot as findProjectRoot27,
|
|
11479
11529
|
loadUsageIndex as loadUsageIndex19,
|
|
11480
11530
|
resolveHaivePaths as resolveHaivePaths24,
|
|
11481
|
-
saveUsageIndex as
|
|
11531
|
+
saveUsageIndex as saveUsageIndex5
|
|
11482
11532
|
} from "@hiveai/core";
|
|
11483
11533
|
function registerMemoryRm(memory2) {
|
|
11484
11534
|
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) => {
|
|
@@ -11496,7 +11546,7 @@ function registerMemoryRm(memory2) {
|
|
|
11496
11546
|
process.exitCode = 1;
|
|
11497
11547
|
return;
|
|
11498
11548
|
}
|
|
11499
|
-
const rel =
|
|
11549
|
+
const rel = path33.relative(root, found.filePath);
|
|
11500
11550
|
if (!opts.yes) {
|
|
11501
11551
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
11502
11552
|
const answer = (await rl.question(`Delete ${rel}? [y/N] `)).trim().toLowerCase();
|
|
@@ -11512,7 +11562,7 @@ function registerMemoryRm(memory2) {
|
|
|
11512
11562
|
const idx = await loadUsageIndex19(paths);
|
|
11513
11563
|
if (idx.by_id[id]) {
|
|
11514
11564
|
delete idx.by_id[id];
|
|
11515
|
-
await
|
|
11565
|
+
await saveUsageIndex5(paths, idx);
|
|
11516
11566
|
ui.info("Removed usage entry");
|
|
11517
11567
|
}
|
|
11518
11568
|
}
|
|
@@ -11522,7 +11572,7 @@ function registerMemoryRm(memory2) {
|
|
|
11522
11572
|
// src/commands/memory-show.ts
|
|
11523
11573
|
import { existsSync as existsSync50 } from "fs";
|
|
11524
11574
|
import { readFile as readFile15 } from "fs/promises";
|
|
11525
|
-
import
|
|
11575
|
+
import path34 from "path";
|
|
11526
11576
|
import "commander";
|
|
11527
11577
|
import {
|
|
11528
11578
|
deriveConfidence as deriveConfidence10,
|
|
@@ -11564,7 +11614,7 @@ function registerMemoryShow(memory2) {
|
|
|
11564
11614
|
if (fm.verified_at) console.log(`${ui.dim("verified:")} ${fm.verified_at}`);
|
|
11565
11615
|
if (fm.stale_reason) console.log(`${ui.dim("stale:")} ${fm.stale_reason}`);
|
|
11566
11616
|
console.log(`${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`);
|
|
11567
|
-
console.log(`${ui.dim("file:")} ${
|
|
11617
|
+
console.log(`${ui.dim("file:")} ${path34.relative(root, found.filePath)}`);
|
|
11568
11618
|
if (fm.anchor.paths.length || fm.anchor.symbols.length) {
|
|
11569
11619
|
console.log(ui.dim("anchor:"));
|
|
11570
11620
|
if (fm.anchor.commit) console.log(` ${ui.dim("commit:")} ${fm.anchor.commit}`);
|
|
@@ -11580,7 +11630,7 @@ function registerMemoryShow(memory2) {
|
|
|
11580
11630
|
|
|
11581
11631
|
// src/commands/memory-stats.ts
|
|
11582
11632
|
import { existsSync as existsSync51 } from "fs";
|
|
11583
|
-
import
|
|
11633
|
+
import path35 from "path";
|
|
11584
11634
|
import "commander";
|
|
11585
11635
|
import {
|
|
11586
11636
|
deriveConfidence as deriveConfidence11,
|
|
@@ -11618,7 +11668,7 @@ function registerMemoryStats(memory2) {
|
|
|
11618
11668
|
console.log(
|
|
11619
11669
|
` ${ui.dim("status:")} ${fm.status} ${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`
|
|
11620
11670
|
);
|
|
11621
|
-
console.log(` ${ui.dim(
|
|
11671
|
+
console.log(` ${ui.dim(path35.relative(root, filePath))}`);
|
|
11622
11672
|
}
|
|
11623
11673
|
});
|
|
11624
11674
|
}
|
|
@@ -11725,7 +11775,7 @@ import {
|
|
|
11725
11775
|
recordRejection as recordRejection4,
|
|
11726
11776
|
recommendFeedbackAdjustment as recommendFeedbackAdjustment2,
|
|
11727
11777
|
resolveHaivePaths as resolveHaivePaths28,
|
|
11728
|
-
saveUsageIndex as
|
|
11778
|
+
saveUsageIndex as saveUsageIndex6,
|
|
11729
11779
|
serializeMemory as serializeMemory21
|
|
11730
11780
|
} from "@hiveai/core";
|
|
11731
11781
|
function registerMemoryFeedback(memory2) {
|
|
@@ -11755,7 +11805,7 @@ function registerMemoryFeedback(memory2) {
|
|
|
11755
11805
|
const outcome = opts.applied ? "applied" : "rejected";
|
|
11756
11806
|
if (opts.applied) recordApplied2(index, id);
|
|
11757
11807
|
else recordRejection4(index, id, opts.reason ?? null);
|
|
11758
|
-
await
|
|
11808
|
+
await saveUsageIndex6(paths, index);
|
|
11759
11809
|
const usage = getUsage19(index, id);
|
|
11760
11810
|
const adjustment = opts.rejected ? recommendFeedbackAdjustment2(target.memory.frontmatter, usage) : { action: "none", reason: "No automatic adjustment needed." };
|
|
11761
11811
|
const adjustedFrontmatter = applyFeedbackAdjustment2(target.memory.frontmatter, adjustment);
|
|
@@ -11781,7 +11831,7 @@ function registerMemoryFeedback(memory2) {
|
|
|
11781
11831
|
// src/commands/memory-verify.ts
|
|
11782
11832
|
import { writeFile as writeFile25 } from "fs/promises";
|
|
11783
11833
|
import { existsSync as existsSync55 } from "fs";
|
|
11784
|
-
import
|
|
11834
|
+
import path36 from "path";
|
|
11785
11835
|
import "commander";
|
|
11786
11836
|
import {
|
|
11787
11837
|
findProjectRoot as findProjectRoot32,
|
|
@@ -11823,7 +11873,7 @@ function registerMemoryVerify(memory2) {
|
|
|
11823
11873
|
for (const { memory: mem, filePath } of targets) {
|
|
11824
11874
|
const result = await verifyAnchor3(mem, { projectRoot: root });
|
|
11825
11875
|
const isAnchored = mem.frontmatter.anchor.paths.length > 0 || mem.frontmatter.anchor.symbols.length > 0;
|
|
11826
|
-
const rel =
|
|
11876
|
+
const rel = path36.relative(root, filePath);
|
|
11827
11877
|
if (!isAnchored) {
|
|
11828
11878
|
anchorlessIds.push(mem.frontmatter.id);
|
|
11829
11879
|
entries.push({ id: mem.frontmatter.id, status: "anchorless", path: rel });
|
|
@@ -11968,7 +12018,7 @@ function registerMemoryImport(memory2) {
|
|
|
11968
12018
|
// src/commands/memory-import-changelog.ts
|
|
11969
12019
|
import { existsSync as existsSync57 } from "fs";
|
|
11970
12020
|
import { readFile as readFile17, mkdir as mkdir15, writeFile as writeFile26 } from "fs/promises";
|
|
11971
|
-
import
|
|
12021
|
+
import path37 from "path";
|
|
11972
12022
|
import "commander";
|
|
11973
12023
|
import {
|
|
11974
12024
|
buildFrontmatter as buildFrontmatter9,
|
|
@@ -12040,7 +12090,7 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
12040
12090
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
12041
12091
|
const root = findProjectRoot34(opts.dir);
|
|
12042
12092
|
const paths = resolveHaivePaths31(root);
|
|
12043
|
-
const changelogPath =
|
|
12093
|
+
const changelogPath = path37.resolve(root, opts.fromChangelog);
|
|
12044
12094
|
if (!existsSync57(changelogPath)) {
|
|
12045
12095
|
ui.error(`CHANGELOG not found: ${changelogPath}`);
|
|
12046
12096
|
process.exitCode = 1;
|
|
@@ -12061,9 +12111,9 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
12061
12111
|
entries = entries.filter((e) => requested.includes(e.version));
|
|
12062
12112
|
}
|
|
12063
12113
|
}
|
|
12064
|
-
const pkgName = opts.package ??
|
|
12114
|
+
const pkgName = opts.package ?? path37.basename(path37.dirname(changelogPath));
|
|
12065
12115
|
const scope = opts.scope ?? "team";
|
|
12066
|
-
const teamDir =
|
|
12116
|
+
const teamDir = path37.join(paths.memoriesDir, scope);
|
|
12067
12117
|
await mkdir15(teamDir, { recursive: true });
|
|
12068
12118
|
let saved = 0;
|
|
12069
12119
|
for (const entry of entries) {
|
|
@@ -12086,7 +12136,7 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
12086
12136
|
lines.push("");
|
|
12087
12137
|
}
|
|
12088
12138
|
lines.push(
|
|
12089
|
-
`**Source:** \`${
|
|
12139
|
+
`**Source:** \`${path37.relative(root, changelogPath)}\`
|
|
12090
12140
|
**Action:** Update all usages of ${pkgName} if they rely on any of the above.`
|
|
12091
12141
|
);
|
|
12092
12142
|
const slug = `changelog-${pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase()}-v${entry.version.replace(/\./g, "-")}`;
|
|
@@ -12101,11 +12151,11 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
12101
12151
|
pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase(),
|
|
12102
12152
|
`v${entry.version}`
|
|
12103
12153
|
],
|
|
12104
|
-
paths: [
|
|
12154
|
+
paths: [path37.relative(root, changelogPath)],
|
|
12105
12155
|
topic: `changelog-${pkgName}-${entry.version}`
|
|
12106
12156
|
});
|
|
12107
12157
|
await writeFile26(
|
|
12108
|
-
|
|
12158
|
+
path37.join(teamDir, `${fm.id}.md`),
|
|
12109
12159
|
serializeMemory24({ frontmatter: fm, body: lines.join("\n") }),
|
|
12110
12160
|
"utf8"
|
|
12111
12161
|
);
|
|
@@ -12130,7 +12180,7 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
|
|
|
12130
12180
|
// src/commands/memory-digest.ts
|
|
12131
12181
|
import { existsSync as existsSync58 } from "fs";
|
|
12132
12182
|
import { writeFile as writeFile27 } from "fs/promises";
|
|
12133
|
-
import
|
|
12183
|
+
import path38 from "path";
|
|
12134
12184
|
import "commander";
|
|
12135
12185
|
import {
|
|
12136
12186
|
deriveConfidence as deriveConfidence12,
|
|
@@ -12225,7 +12275,7 @@ function registerMemoryDigest(program2) {
|
|
|
12225
12275
|
);
|
|
12226
12276
|
const digest = lines.join("\n");
|
|
12227
12277
|
if (opts.out) {
|
|
12228
|
-
const outPath =
|
|
12278
|
+
const outPath = path38.resolve(process.cwd(), opts.out);
|
|
12229
12279
|
await writeFile27(outPath, digest, "utf8");
|
|
12230
12280
|
ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
|
|
12231
12281
|
} else {
|
|
@@ -12238,7 +12288,7 @@ function registerMemoryDigest(program2) {
|
|
|
12238
12288
|
import { writeFile as writeFile28, mkdir as mkdir16, readFile as readFile18, rm as rm2 } from "fs/promises";
|
|
12239
12289
|
import { existsSync as existsSync59 } from "fs";
|
|
12240
12290
|
import { spawn as spawn4 } from "child_process";
|
|
12241
|
-
import
|
|
12291
|
+
import path39 from "path";
|
|
12242
12292
|
import "commander";
|
|
12243
12293
|
import {
|
|
12244
12294
|
buildFrontmatter as buildFrontmatter10,
|
|
@@ -12253,7 +12303,7 @@ import {
|
|
|
12253
12303
|
summarizeCaughtForYou
|
|
12254
12304
|
} from "@hiveai/core";
|
|
12255
12305
|
async function buildAutoRecap(paths) {
|
|
12256
|
-
const obsFile =
|
|
12306
|
+
const obsFile = path39.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
12257
12307
|
if (!existsSync59(obsFile)) return await buildGitAutoRecap(paths);
|
|
12258
12308
|
const raw = await readFile18(obsFile, "utf8").catch(() => "");
|
|
12259
12309
|
if (!raw.trim()) return await buildGitAutoRecap(paths);
|
|
@@ -12422,7 +12472,7 @@ function runGit(cwd, args) {
|
|
|
12422
12472
|
});
|
|
12423
12473
|
}
|
|
12424
12474
|
async function observationStart(paths) {
|
|
12425
|
-
const obsFile =
|
|
12475
|
+
const obsFile = path39.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
12426
12476
|
if (!existsSync59(obsFile)) return null;
|
|
12427
12477
|
const raw = await readFile18(obsFile, "utf8").catch(() => "");
|
|
12428
12478
|
let first = null;
|
|
@@ -12511,14 +12561,14 @@ function registerSessionEnd(session2) {
|
|
|
12511
12561
|
});
|
|
12512
12562
|
const topic = recapTopic2(scope, opts.module);
|
|
12513
12563
|
const filesTouched = parseCsv5(resolvedFiles).map((p) => normalizeAnchorPath(root, p));
|
|
12514
|
-
const missingPaths = filesTouched.filter((p) => !existsSync59(
|
|
12564
|
+
const missingPaths = filesTouched.filter((p) => !existsSync59(path39.resolve(root, p)));
|
|
12515
12565
|
if (missingPaths.length > 0 && !opts.quiet) {
|
|
12516
12566
|
ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
|
|
12517
12567
|
for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
|
|
12518
12568
|
}
|
|
12519
12569
|
const cleanupObservations = async () => {
|
|
12520
12570
|
if (!opts.auto) return;
|
|
12521
|
-
const obsFile =
|
|
12571
|
+
const obsFile = path39.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
12522
12572
|
if (existsSync59(obsFile)) await rm2(obsFile).catch(() => {
|
|
12523
12573
|
});
|
|
12524
12574
|
};
|
|
@@ -12543,7 +12593,7 @@ function registerSessionEnd(session2) {
|
|
|
12543
12593
|
await cleanupObservations();
|
|
12544
12594
|
if (!opts.quiet) {
|
|
12545
12595
|
ui.success(`Session recap updated (revision #${revisionCount})`);
|
|
12546
|
-
ui.info(`id=${fm.id} file=${
|
|
12596
|
+
ui.info(`id=${fm.id} file=${path39.relative(root, topicMatch.filePath)}`);
|
|
12547
12597
|
await printCaughtForYou(paths, caughtSince, opts.quiet);
|
|
12548
12598
|
ui.info("Tip: `haive stats --export-report` generates a usage JSON suitable for dashboards.");
|
|
12549
12599
|
}
|
|
@@ -12561,12 +12611,12 @@ function registerSessionEnd(session2) {
|
|
|
12561
12611
|
status: "validated"
|
|
12562
12612
|
});
|
|
12563
12613
|
const file = memoryFilePath10(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
12564
|
-
await mkdir16(
|
|
12614
|
+
await mkdir16(path39.dirname(file), { recursive: true });
|
|
12565
12615
|
await writeFile28(file, serializeMemory25({ frontmatter, body }), "utf8");
|
|
12566
12616
|
await cleanupObservations();
|
|
12567
12617
|
if (!opts.quiet) {
|
|
12568
12618
|
ui.success(`Session recap created`);
|
|
12569
|
-
ui.info(`id=${frontmatter.id} scope=${scope} file=${
|
|
12619
|
+
ui.info(`id=${frontmatter.id} scope=${scope} file=${path39.relative(root, file)}`);
|
|
12570
12620
|
await printCaughtForYou(paths, caughtSince, opts.quiet);
|
|
12571
12621
|
ui.info("Next session: call `get_briefing` \u2014 the recap will be surfaced automatically.");
|
|
12572
12622
|
ui.info("Tip: export a local MCP usage rollup with `haive stats --export-report .ai/tool-usage-roi-report.json`.");
|
|
@@ -12579,8 +12629,8 @@ function parseCsv5(value) {
|
|
|
12579
12629
|
}
|
|
12580
12630
|
function normalizeAnchorPath(root, filePath) {
|
|
12581
12631
|
if (!filePath) return filePath;
|
|
12582
|
-
if (!
|
|
12583
|
-
const rel =
|
|
12632
|
+
if (!path39.isAbsolute(filePath)) return filePath;
|
|
12633
|
+
const rel = path39.relative(root, filePath);
|
|
12584
12634
|
if (rel.startsWith("..")) return filePath;
|
|
12585
12635
|
return rel;
|
|
12586
12636
|
}
|
|
@@ -12588,7 +12638,7 @@ function normalizeAnchorPath(root, filePath) {
|
|
|
12588
12638
|
// src/commands/snapshot.ts
|
|
12589
12639
|
import { existsSync as existsSync60 } from "fs";
|
|
12590
12640
|
import { readdir as readdir4 } from "fs/promises";
|
|
12591
|
-
import
|
|
12641
|
+
import path40 from "path";
|
|
12592
12642
|
import "commander";
|
|
12593
12643
|
import {
|
|
12594
12644
|
diffContract,
|
|
@@ -12627,7 +12677,7 @@ function registerSnapshot(program2) {
|
|
|
12627
12677
|
return;
|
|
12628
12678
|
}
|
|
12629
12679
|
if (opts.list) {
|
|
12630
|
-
const contractsDir =
|
|
12680
|
+
const contractsDir = path40.join(paths.haiveDir, "contracts");
|
|
12631
12681
|
if (!existsSync60(contractsDir)) {
|
|
12632
12682
|
console.log(ui.dim("No contract snapshots found."));
|
|
12633
12683
|
return;
|
|
@@ -12683,7 +12733,7 @@ function registerSnapshot(program2) {
|
|
|
12683
12733
|
return;
|
|
12684
12734
|
}
|
|
12685
12735
|
const contractPath = opts.contract;
|
|
12686
|
-
const name = opts.name ??
|
|
12736
|
+
const name = opts.name ?? path40.basename(contractPath, path40.extname(contractPath));
|
|
12687
12737
|
const format = opts.format ?? detectFormat(contractPath) ?? "openapi";
|
|
12688
12738
|
const contract = { name, path: contractPath, format };
|
|
12689
12739
|
try {
|
|
@@ -12738,8 +12788,8 @@ async function runDiff(root, haiveDir, contract) {
|
|
|
12738
12788
|
}
|
|
12739
12789
|
}
|
|
12740
12790
|
function detectFormat(filePath) {
|
|
12741
|
-
const ext =
|
|
12742
|
-
const base =
|
|
12791
|
+
const ext = path40.extname(filePath).toLowerCase();
|
|
12792
|
+
const base = path40.basename(filePath).toLowerCase();
|
|
12743
12793
|
if (ext === ".yaml" || ext === ".yml" || ext === ".json") {
|
|
12744
12794
|
if (base.includes("openapi") || base.includes("swagger")) return "openapi";
|
|
12745
12795
|
if (base.includes("schema") || base.includes("graphql")) return "graphql";
|
|
@@ -12754,7 +12804,7 @@ function detectFormat(filePath) {
|
|
|
12754
12804
|
// src/commands/hub.ts
|
|
12755
12805
|
import { existsSync as existsSync61 } from "fs";
|
|
12756
12806
|
import { mkdir as mkdir17, readFile as readFile19, writeFile as writeFile29, copyFile } from "fs/promises";
|
|
12757
|
-
import
|
|
12807
|
+
import path41 from "path";
|
|
12758
12808
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
12759
12809
|
import "commander";
|
|
12760
12810
|
import {
|
|
@@ -12773,7 +12823,7 @@ function registerHub(program2) {
|
|
|
12773
12823
|
hub.command("init <hubPath>").description(
|
|
12774
12824
|
"Initialize a new team-knowledge hub repo at <hubPath>.\n\n Creates a git repo with a .ai/ directory structure ready for shared memories.\n\n Example:\n haive hub init ../team-hub\n haive hub init /srv/git/team-knowledge\n"
|
|
12775
12825
|
).action(async (hubPath) => {
|
|
12776
|
-
const absPath =
|
|
12826
|
+
const absPath = path41.resolve(hubPath);
|
|
12777
12827
|
await mkdir17(absPath, { recursive: true });
|
|
12778
12828
|
const gitCheck = spawnSync5("git", ["rev-parse", "--git-dir"], { cwd: absPath });
|
|
12779
12829
|
if (gitCheck.status !== 0) {
|
|
@@ -12784,10 +12834,10 @@ function registerHub(program2) {
|
|
|
12784
12834
|
return;
|
|
12785
12835
|
}
|
|
12786
12836
|
}
|
|
12787
|
-
const sharedDir =
|
|
12837
|
+
const sharedDir = path41.join(absPath, ".ai", "memories", "shared");
|
|
12788
12838
|
await mkdir17(sharedDir, { recursive: true });
|
|
12789
12839
|
await writeFile29(
|
|
12790
|
-
|
|
12840
|
+
path41.join(absPath, ".ai", "README.md"),
|
|
12791
12841
|
`# hAIve Team Knowledge Hub
|
|
12792
12842
|
|
|
12793
12843
|
This repo is a shared knowledge hub for hAIve.
|
|
@@ -12809,7 +12859,7 @@ haive hub pull # import into a project
|
|
|
12809
12859
|
"utf8"
|
|
12810
12860
|
);
|
|
12811
12861
|
await writeFile29(
|
|
12812
|
-
|
|
12862
|
+
path41.join(absPath, ".gitignore"),
|
|
12813
12863
|
".ai/.cache/\n.ai/memories/personal/\n",
|
|
12814
12864
|
"utf8"
|
|
12815
12865
|
);
|
|
@@ -12824,7 +12874,7 @@ haive hub pull # import into a project
|
|
|
12824
12874
|
`
|
|
12825
12875
|
Next steps:
|
|
12826
12876
|
1. Add hubPath to your project's .ai/haive.config.json:
|
|
12827
|
-
{ "hubPath": "${
|
|
12877
|
+
{ "hubPath": "${path41.relative(process.cwd(), absPath)}" }
|
|
12828
12878
|
2. Run \`haive hub push\` to publish your shared memories
|
|
12829
12879
|
3. Share ${absPath} with teammates (git remote, NFS, etc.)
|
|
12830
12880
|
`
|
|
@@ -12853,14 +12903,14 @@ Next steps:
|
|
|
12853
12903
|
process.exitCode = 1;
|
|
12854
12904
|
return;
|
|
12855
12905
|
}
|
|
12856
|
-
const hubRoot =
|
|
12906
|
+
const hubRoot = path41.resolve(root, config.hubPath);
|
|
12857
12907
|
if (!existsSync61(hubRoot)) {
|
|
12858
12908
|
ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
|
|
12859
12909
|
process.exitCode = 1;
|
|
12860
12910
|
return;
|
|
12861
12911
|
}
|
|
12862
|
-
const projectName =
|
|
12863
|
-
const destDir =
|
|
12912
|
+
const projectName = path41.basename(root);
|
|
12913
|
+
const destDir = path41.join(hubRoot, ".ai", "memories", "shared", projectName);
|
|
12864
12914
|
await mkdir17(destDir, { recursive: true });
|
|
12865
12915
|
const all = await loadMemoriesFromDir30(paths.memoriesDir);
|
|
12866
12916
|
const shared = all.filter(
|
|
@@ -12879,7 +12929,7 @@ Next steps:
|
|
|
12879
12929
|
for (const { memory: memory2 } of shared) {
|
|
12880
12930
|
const fm = memory2.frontmatter;
|
|
12881
12931
|
const fileName = `${fm.id}.md`;
|
|
12882
|
-
const destPath =
|
|
12932
|
+
const destPath = path41.join(destDir, fileName);
|
|
12883
12933
|
await writeFile29(destPath, serializeMemory26(memory2), "utf8");
|
|
12884
12934
|
pushed++;
|
|
12885
12935
|
}
|
|
@@ -12887,7 +12937,7 @@ Next steps:
|
|
|
12887
12937
|
console.log(ui.dim(` Location: ${destDir}`));
|
|
12888
12938
|
if (opts.commit) {
|
|
12889
12939
|
const message = opts.message ?? `haive: sync shared memories from ${projectName} (${pushed} memories)`;
|
|
12890
|
-
spawnSync5("git", ["add",
|
|
12940
|
+
spawnSync5("git", ["add", path41.join(".ai", "memories", "shared", projectName)], {
|
|
12891
12941
|
cwd: hubRoot
|
|
12892
12942
|
});
|
|
12893
12943
|
const commit = spawnSync5("git", ["commit", "-m", message], {
|
|
@@ -12922,13 +12972,13 @@ Next steps:
|
|
|
12922
12972
|
process.exitCode = 1;
|
|
12923
12973
|
return;
|
|
12924
12974
|
}
|
|
12925
|
-
const hubRoot =
|
|
12926
|
-
const hubSharedDir =
|
|
12975
|
+
const hubRoot = path41.resolve(root, config.hubPath);
|
|
12976
|
+
const hubSharedDir = path41.join(hubRoot, ".ai", "memories", "shared");
|
|
12927
12977
|
if (!existsSync61(hubSharedDir)) {
|
|
12928
12978
|
ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
|
|
12929
12979
|
return;
|
|
12930
12980
|
}
|
|
12931
|
-
const projectName =
|
|
12981
|
+
const projectName = path41.basename(root);
|
|
12932
12982
|
const { readdir: readdir7 } = await import("fs/promises");
|
|
12933
12983
|
const projectDirs = (await readdir7(hubSharedDir, { withFileTypes: true })).filter((d) => d.isDirectory() && d.name !== projectName).map((d) => d.name);
|
|
12934
12984
|
if (projectDirs.length === 0) {
|
|
@@ -12938,16 +12988,16 @@ Next steps:
|
|
|
12938
12988
|
let totalImported = 0;
|
|
12939
12989
|
let totalUpdated = 0;
|
|
12940
12990
|
for (const sourceName of projectDirs) {
|
|
12941
|
-
const sourceDir =
|
|
12942
|
-
const destDir =
|
|
12991
|
+
const sourceDir = path41.join(hubSharedDir, sourceName);
|
|
12992
|
+
const destDir = path41.join(paths.memoriesDir, "shared", sourceName);
|
|
12943
12993
|
await mkdir17(destDir, { recursive: true });
|
|
12944
12994
|
const sourceFiles = (await readdir7(sourceDir)).filter((f) => f.endsWith(".md"));
|
|
12945
12995
|
const { loadMemoriesFromDir: loadDir } = await import("@hiveai/core");
|
|
12946
12996
|
const existingInDest = await loadDir(destDir);
|
|
12947
12997
|
const existingIds = new Set(existingInDest.map(({ memory: memory2 }) => memory2.frontmatter.id));
|
|
12948
12998
|
for (const file of sourceFiles) {
|
|
12949
|
-
const srcPath =
|
|
12950
|
-
const destPath =
|
|
12999
|
+
const srcPath = path41.join(sourceDir, file);
|
|
13000
|
+
const destPath = path41.join(destDir, file);
|
|
12951
13001
|
const fileContent = await readFile19(srcPath, "utf8");
|
|
12952
13002
|
const alreadyTagged = fileContent.includes(`cross-repo:${sourceName}`);
|
|
12953
13003
|
if (!alreadyTagged) {
|
|
@@ -12978,14 +13028,14 @@ Next steps:
|
|
|
12978
13028
|
console.log(
|
|
12979
13029
|
` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
|
|
12980
13030
|
);
|
|
12981
|
-
const sharedDir =
|
|
13031
|
+
const sharedDir = path41.join(paths.memoriesDir, "shared");
|
|
12982
13032
|
if (existsSync61(sharedDir)) {
|
|
12983
13033
|
const { readdir: readdir7 } = await import("fs/promises");
|
|
12984
13034
|
const sources = (await readdir7(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
12985
13035
|
console.log(`
|
|
12986
13036
|
Imported from ${sources.length} source(s):`);
|
|
12987
13037
|
for (const src of sources) {
|
|
12988
|
-
const files = (await readdir7(
|
|
13038
|
+
const files = (await readdir7(path41.join(sharedDir, src))).filter((f) => f.endsWith(".md"));
|
|
12989
13039
|
console.log(` ${src}: ${files.length} memor${files.length === 1 ? "y" : "ies"}`);
|
|
12990
13040
|
}
|
|
12991
13041
|
} else {
|
|
@@ -13010,7 +13060,7 @@ Next steps:
|
|
|
13010
13060
|
import "commander";
|
|
13011
13061
|
import { existsSync as existsSync63 } from "fs";
|
|
13012
13062
|
import { mkdir as mkdir18, writeFile as writeFile30 } from "fs/promises";
|
|
13013
|
-
import
|
|
13063
|
+
import path43 from "path";
|
|
13014
13064
|
import {
|
|
13015
13065
|
aggregateUsage,
|
|
13016
13066
|
findProjectRoot as findProjectRoot39,
|
|
@@ -13082,7 +13132,7 @@ function registerStats(program2) {
|
|
|
13082
13132
|
});
|
|
13083
13133
|
}
|
|
13084
13134
|
async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
13085
|
-
const outAbs =
|
|
13135
|
+
const outAbs = path43.isAbsolute(outRelative) ? path43.resolve(outRelative) : path43.resolve(root, outRelative);
|
|
13086
13136
|
const size = await usageLogSize(paths);
|
|
13087
13137
|
let events = await readUsageEvents2(paths);
|
|
13088
13138
|
let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
|
|
@@ -13117,7 +13167,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
13117
13167
|
ui.warn("Usage log missing or empty \u2014 report still written with partial data.");
|
|
13118
13168
|
events = [];
|
|
13119
13169
|
}
|
|
13120
|
-
await mkdir18(
|
|
13170
|
+
await mkdir18(path43.dirname(outAbs), { recursive: true });
|
|
13121
13171
|
const payload = {
|
|
13122
13172
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13123
13173
|
project_root: root,
|
|
@@ -13308,7 +13358,7 @@ function summarize(name, t0, payload, notes) {
|
|
|
13308
13358
|
// src/commands/benchmark.ts
|
|
13309
13359
|
import { existsSync as existsSync64 } from "fs";
|
|
13310
13360
|
import { readdir as readdir5, readFile as readFile20, writeFile as writeFile31 } from "fs/promises";
|
|
13311
|
-
import
|
|
13361
|
+
import path44 from "path";
|
|
13312
13362
|
import "commander";
|
|
13313
13363
|
import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot41 } from "@hiveai/core";
|
|
13314
13364
|
function registerBenchmark(program2) {
|
|
@@ -13323,9 +13373,9 @@ function registerBenchmark(program2) {
|
|
|
13323
13373
|
}
|
|
13324
13374
|
const markdown = renderMarkdown(root, summary, rows);
|
|
13325
13375
|
if (opts.out) {
|
|
13326
|
-
const outFile =
|
|
13376
|
+
const outFile = path44.isAbsolute(opts.out) ? opts.out : path44.join(root, opts.out);
|
|
13327
13377
|
await writeFile31(outFile, markdown, "utf8");
|
|
13328
|
-
ui.success(`wrote ${
|
|
13378
|
+
ui.success(`wrote ${path44.relative(process.cwd(), outFile)}`);
|
|
13329
13379
|
return;
|
|
13330
13380
|
}
|
|
13331
13381
|
console.log(markdown);
|
|
@@ -13349,9 +13399,9 @@ function registerBenchmark(program2) {
|
|
|
13349
13399
|
}
|
|
13350
13400
|
function resolveBenchmarkRoot(dir) {
|
|
13351
13401
|
const candidate = dir ?? "benchmarks/agent-benchmark";
|
|
13352
|
-
if (
|
|
13402
|
+
if (path44.isAbsolute(candidate)) return candidate;
|
|
13353
13403
|
const projectRoot = findProjectRoot41(process.cwd());
|
|
13354
|
-
return
|
|
13404
|
+
return path44.join(projectRoot, candidate);
|
|
13355
13405
|
}
|
|
13356
13406
|
async function collectRows(root) {
|
|
13357
13407
|
if (!existsSync64(root)) throw new Error(`Benchmark directory not found: ${root}`);
|
|
@@ -13359,8 +13409,8 @@ async function collectRows(root) {
|
|
|
13359
13409
|
const rows = [];
|
|
13360
13410
|
for (const entry of entries) {
|
|
13361
13411
|
if (!entry.isDirectory()) continue;
|
|
13362
|
-
const fixtureDir =
|
|
13363
|
-
const reportFile =
|
|
13412
|
+
const fixtureDir = path44.join(root, entry.name);
|
|
13413
|
+
const reportFile = path44.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
|
|
13364
13414
|
if (!existsSync64(reportFile)) continue;
|
|
13365
13415
|
const report = await readFile20(reportFile, "utf8");
|
|
13366
13416
|
rows.push(parseAgentReport(entry.name, report));
|
|
@@ -13378,7 +13428,7 @@ function parseAgentReport(fixture, report) {
|
|
|
13378
13428
|
test_iterations: countMatches(section(report, "Test Iterations"), /Iteration\s+\d+|^- /gim),
|
|
13379
13429
|
terminal_failures: countMatches(section(report, "Terminal Errors"), /fail|error|not raised|exited with code 1/gi),
|
|
13380
13430
|
decision_mentions: sectionBulletCount(report, "Key Decisions"),
|
|
13381
|
-
|
|
13431
|
+
report_tokens_est: estimateTokens4(report),
|
|
13382
13432
|
haive_impact: /hAIve Memory Impact[\s\S]*?\b(yes|directly|changed|shaped|confirmed)\b/i.test(report)
|
|
13383
13433
|
};
|
|
13384
13434
|
}
|
|
@@ -13400,7 +13450,7 @@ function summarizeGroup(rows) {
|
|
|
13400
13450
|
test_iterations: sum("test_iterations"),
|
|
13401
13451
|
terminal_failures: sum("terminal_failures"),
|
|
13402
13452
|
decision_mentions: sum("decision_mentions"),
|
|
13403
|
-
|
|
13453
|
+
report_tokens_est: sum("report_tokens_est"),
|
|
13404
13454
|
haive_impact_count: rows.filter((r) => r.haive_impact).length
|
|
13405
13455
|
};
|
|
13406
13456
|
}
|
|
@@ -13412,29 +13462,31 @@ function renderMarkdown(root, summary, rows) {
|
|
|
13412
13462
|
"",
|
|
13413
13463
|
"## Summary",
|
|
13414
13464
|
"",
|
|
13415
|
-
"| Group | Fixtures | Commands | Files read | Files modified | Test iterations | Terminal failures | Decision mentions |
|
|
13465
|
+
"| Group | Fixtures | Commands | Files read | Files modified | Test iterations | Terminal failures | Decision mentions | Report tokens (est, report only) | hAIve impact |",
|
|
13416
13466
|
"| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |",
|
|
13417
13467
|
groupLine("hAIve", summary.haive),
|
|
13418
13468
|
groupLine("Plain", summary.plain),
|
|
13419
13469
|
"",
|
|
13420
13470
|
"## Fixtures",
|
|
13421
13471
|
"",
|
|
13422
|
-
"| Fixture | Group | Commands | Files read | Files modified | Test iterations | Terminal failures | Decisions |
|
|
13472
|
+
"| Fixture | Group | Commands | Files read | Files modified | Test iterations | Terminal failures | Decisions | Report tokens (est, report only) | hAIve impact |",
|
|
13423
13473
|
"| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- |",
|
|
13424
13474
|
...rows.map(
|
|
13425
|
-
(row) => `| \`${row.fixture}\` | ${row.group} | ${row.commands} | ${row.files_read} | ${row.files_modified} | ${row.test_iterations} | ${row.terminal_failures} | ${row.decision_mentions} | ${row.
|
|
13475
|
+
(row) => `| \`${row.fixture}\` | ${row.group} | ${row.commands} | ${row.files_read} | ${row.files_modified} | ${row.test_iterations} | ${row.terminal_failures} | ${row.decision_mentions} | ${row.report_tokens_est} | ${row.haive_impact ? "yes" : "no"} |`
|
|
13426
13476
|
),
|
|
13427
13477
|
"",
|
|
13428
13478
|
"## Reading",
|
|
13429
13479
|
"",
|
|
13430
|
-
"
|
|
13480
|
+
"`Report tokens (est)` estimates the size of the agent's WRITTEN REPORT only \u2014 a verbosity proxy, NOT",
|
|
13481
|
+
"the agent's total token consumption. For real per-agent token/latency, capture your runner's telemetry",
|
|
13482
|
+
"(e.g. subagent token counts) separately; this report can't see model billing.",
|
|
13431
13483
|
"Use this report to compare relative effort and decision quality, then pair it with final test results and a human review of the diffs.",
|
|
13432
13484
|
""
|
|
13433
13485
|
];
|
|
13434
13486
|
return lines.join("\n");
|
|
13435
13487
|
}
|
|
13436
13488
|
function groupLine(label, group) {
|
|
13437
|
-
return `| ${label} | ${group.fixtures} | ${group.commands} | ${group.files_read} | ${group.files_modified} | ${group.test_iterations} | ${group.terminal_failures} | ${group.decision_mentions} | ${group.
|
|
13489
|
+
return `| ${label} | ${group.fixtures} | ${group.commands} | ${group.files_read} | ${group.files_modified} | ${group.test_iterations} | ${group.terminal_failures} | ${group.decision_mentions} | ${group.report_tokens_est} | ${group.haive_impact_count} |`;
|
|
13438
13490
|
}
|
|
13439
13491
|
function sectionBulletCount(markdown, title) {
|
|
13440
13492
|
return countMatches(section(markdown, title), /^- |^\d+\.\s/gm);
|
|
@@ -13453,7 +13505,7 @@ function escapeRegExp(value) {
|
|
|
13453
13505
|
// src/commands/eval.ts
|
|
13454
13506
|
import { mkdir as mkdir19, readFile as readFile21, writeFile as writeFile33 } from "fs/promises";
|
|
13455
13507
|
import { existsSync as existsSync65 } from "fs";
|
|
13456
|
-
import
|
|
13508
|
+
import path45 from "path";
|
|
13457
13509
|
import "commander";
|
|
13458
13510
|
import {
|
|
13459
13511
|
aggregateRetrieval,
|
|
@@ -13549,7 +13601,7 @@ function registerEval(program2) {
|
|
|
13549
13601
|
});
|
|
13550
13602
|
if (!opts.json) ui.success(`Recorded eval score ${report.score}/100 to history.`);
|
|
13551
13603
|
}
|
|
13552
|
-
const baselineFile = opts.baselineFile ?
|
|
13604
|
+
const baselineFile = opts.baselineFile ? path45.isAbsolute(opts.baselineFile) ? opts.baselineFile : path45.join(root, opts.baselineFile) : path45.join(root, ".ai", "eval", "baseline.json");
|
|
13553
13605
|
if (opts.baseline) {
|
|
13554
13606
|
const snapshot = {
|
|
13555
13607
|
saved_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -13558,18 +13610,18 @@ function registerEval(program2) {
|
|
|
13558
13610
|
report,
|
|
13559
13611
|
gate_precision: gatePrecision
|
|
13560
13612
|
};
|
|
13561
|
-
await mkdir19(
|
|
13613
|
+
await mkdir19(path45.dirname(baselineFile), { recursive: true });
|
|
13562
13614
|
await writeFile33(baselineFile, JSON.stringify(snapshot, null, 2), "utf8");
|
|
13563
|
-
if (!opts.json) ui.success(`Saved baseline (score ${report.score}/100) \u2192 ${
|
|
13615
|
+
if (!opts.json) ui.success(`Saved baseline (score ${report.score}/100) \u2192 ${path45.relative(root, baselineFile)}`);
|
|
13564
13616
|
}
|
|
13565
13617
|
let delta = null;
|
|
13566
13618
|
let gateDelta = null;
|
|
13567
13619
|
if (opts.compare || opts.regressionGate) {
|
|
13568
13620
|
if (!existsSync65(baselineFile)) {
|
|
13569
13621
|
if (opts.regressionGate) {
|
|
13570
|
-
if (!opts.json) ui.info(`No baseline at ${
|
|
13622
|
+
if (!opts.json) ui.info(`No baseline at ${path45.relative(root, baselineFile)} \u2014 regression gate skipped. Run \`haive eval --baseline\` to enable it.`);
|
|
13571
13623
|
} else {
|
|
13572
|
-
ui.error(`No baseline at ${
|
|
13624
|
+
ui.error(`No baseline at ${path45.relative(root, baselineFile)}. Run \`haive eval --baseline\` first.`);
|
|
13573
13625
|
process.exitCode = 1;
|
|
13574
13626
|
return;
|
|
13575
13627
|
}
|
|
@@ -13586,6 +13638,7 @@ function registerEval(program2) {
|
|
|
13586
13638
|
root,
|
|
13587
13639
|
k,
|
|
13588
13640
|
spec_source: resolvedSpec.source,
|
|
13641
|
+
provenance: { synthesized: resolvedSpec.synthesized, authored: resolvedSpec.authored },
|
|
13589
13642
|
report,
|
|
13590
13643
|
gate_precision: gatePrecision,
|
|
13591
13644
|
...delta ? { delta } : {},
|
|
@@ -13594,17 +13647,22 @@ function registerEval(program2) {
|
|
|
13594
13647
|
applyExitGates(opts, report, delta, gatePrecision, gateDelta);
|
|
13595
13648
|
return;
|
|
13596
13649
|
}
|
|
13650
|
+
if (resolvedSpec.authored === 0 && resolvedSpec.synthesized > 0) {
|
|
13651
|
+
ui.warn(
|
|
13652
|
+
`All ${resolvedSpec.synthesized} case(s) are self-synthesized from your own memories (self-referential). Add hand-labeled cases in .ai/eval/spec.json, or run \`haive eval --spec <file>\`, for an independent score.`
|
|
13653
|
+
);
|
|
13654
|
+
}
|
|
13597
13655
|
if (delta) {
|
|
13598
13656
|
console.log(renderDelta(delta));
|
|
13599
13657
|
}
|
|
13600
13658
|
if (gateDelta) {
|
|
13601
13659
|
console.log(renderGateDelta(gateDelta));
|
|
13602
13660
|
}
|
|
13603
|
-
const md = renderMarkdown2(root, k, resolvedSpec
|
|
13661
|
+
const md = renderMarkdown2(root, k, resolvedSpec, report, gatePrecision);
|
|
13604
13662
|
if (opts.out) {
|
|
13605
|
-
const outFile =
|
|
13663
|
+
const outFile = path45.isAbsolute(opts.out) ? opts.out : path45.join(root, opts.out);
|
|
13606
13664
|
await writeFile33(outFile, md, "utf8");
|
|
13607
|
-
ui.success(`wrote ${
|
|
13665
|
+
ui.success(`wrote ${path45.relative(process.cwd(), outFile)}`);
|
|
13608
13666
|
} else {
|
|
13609
13667
|
console.log(md);
|
|
13610
13668
|
}
|
|
@@ -13683,30 +13741,39 @@ function renderGateDelta(delta) {
|
|
|
13683
13741
|
lines.push(` rejections ${delta.rejections.baseline ?? "n/a"} \u2192 ${delta.rejections.current ?? "n/a"} ${ui.dim(`(${rejectionDelta})`)}`);
|
|
13684
13742
|
return lines.join("\n");
|
|
13685
13743
|
}
|
|
13744
|
+
function countCases(spec) {
|
|
13745
|
+
return (spec.retrieval?.length ?? 0) + (spec.sensors?.length ?? 0);
|
|
13746
|
+
}
|
|
13686
13747
|
async function resolveSpec(opts, root, memoriesDir) {
|
|
13687
13748
|
if (opts.spec) {
|
|
13688
|
-
const file =
|
|
13749
|
+
const file = path45.resolve(opts.spec);
|
|
13689
13750
|
const raw = await readFile21(file, "utf8");
|
|
13690
|
-
|
|
13751
|
+
const spec = JSON.parse(raw);
|
|
13752
|
+
return { spec, source: file, synthesized: 0, authored: countCases(spec) };
|
|
13691
13753
|
}
|
|
13692
|
-
const defaultSpec =
|
|
13754
|
+
const defaultSpec = path45.join(root, ".ai", "eval", "spec.json");
|
|
13693
13755
|
if (existsSync65(defaultSpec)) {
|
|
13694
13756
|
const raw = await readFile21(defaultSpec, "utf8");
|
|
13695
13757
|
const explicit = JSON.parse(raw);
|
|
13696
13758
|
const memories2 = await loadMemoriesFromDir27(memoriesDir);
|
|
13697
|
-
const
|
|
13759
|
+
const synthesized2 = synthesizeSelfEvalCases(memories2, { includeFiles: !opts.semanticOnly });
|
|
13698
13760
|
return {
|
|
13699
13761
|
spec: {
|
|
13700
|
-
retrieval: [...
|
|
13762
|
+
retrieval: [...synthesized2, ...explicit.retrieval ?? []],
|
|
13701
13763
|
sensors: explicit.sensors ?? []
|
|
13702
13764
|
},
|
|
13703
|
-
source: ".ai/eval/spec.json + synthesized anchored retrieval"
|
|
13765
|
+
source: ".ai/eval/spec.json + synthesized anchored retrieval",
|
|
13766
|
+
synthesized: synthesized2.length,
|
|
13767
|
+
authored: countCases(explicit)
|
|
13704
13768
|
};
|
|
13705
13769
|
}
|
|
13706
13770
|
const memories = await loadMemoriesFromDir27(memoriesDir);
|
|
13771
|
+
const synthesized = synthesizeSelfEvalCases(memories, { includeFiles: !opts.semanticOnly });
|
|
13707
13772
|
return {
|
|
13708
|
-
spec: { retrieval:
|
|
13709
|
-
source: "synthesized anchored retrieval"
|
|
13773
|
+
spec: { retrieval: synthesized },
|
|
13774
|
+
source: "synthesized anchored retrieval",
|
|
13775
|
+
synthesized: synthesized.length,
|
|
13776
|
+
authored: 0
|
|
13710
13777
|
};
|
|
13711
13778
|
}
|
|
13712
13779
|
async function runRetrieval(c, k, ctx) {
|
|
@@ -13739,12 +13806,14 @@ async function runSensorCase(c, ctx) {
|
|
|
13739
13806
|
function pct(n) {
|
|
13740
13807
|
return `${Math.round(n * 100)}%`;
|
|
13741
13808
|
}
|
|
13742
|
-
function renderMarkdown2(root, k,
|
|
13809
|
+
function renderMarkdown2(root, k, resolved, report, gatePrecision) {
|
|
13810
|
+
const provenance = resolved.authored === 0 ? `${resolved.synthesized} synthesized (self-referential \u2014 sanity floor, not ground truth)` : resolved.synthesized === 0 ? `${resolved.authored} authored (independent ground truth)` : `${resolved.authored} authored (independent) + ${resolved.synthesized} synthesized (self-referential)`;
|
|
13743
13811
|
const lines = [
|
|
13744
13812
|
"# hAIve eval report",
|
|
13745
13813
|
"",
|
|
13746
13814
|
`Project: \`${root}\` \xB7 top-k: ${k}`,
|
|
13747
|
-
`Spec: ${source}`,
|
|
13815
|
+
`Spec: ${resolved.source}`,
|
|
13816
|
+
`Cases: ${provenance}`,
|
|
13748
13817
|
"",
|
|
13749
13818
|
`## Overall score: ${report.score}/100`,
|
|
13750
13819
|
""
|
|
@@ -13803,7 +13872,7 @@ function renderMarkdown2(root, k, source, report, gatePrecision) {
|
|
|
13803
13872
|
// src/commands/memory-suggest.ts
|
|
13804
13873
|
import { mkdir as mkdir20, writeFile as writeFile34 } from "fs/promises";
|
|
13805
13874
|
import { existsSync as existsSync66 } from "fs";
|
|
13806
|
-
import
|
|
13875
|
+
import path46 from "path";
|
|
13807
13876
|
import "commander";
|
|
13808
13877
|
import {
|
|
13809
13878
|
aggregateUsage as aggregateUsage2,
|
|
@@ -13904,13 +13973,13 @@ function registerMemorySuggest(memory2) {
|
|
|
13904
13973
|
});
|
|
13905
13974
|
const body = renderTemplate(s, fm.id, status);
|
|
13906
13975
|
const file = memoryFilePath11(paths, fm.scope, fm.id, fm.module);
|
|
13907
|
-
await mkdir20(
|
|
13976
|
+
await mkdir20(path46.dirname(file), { recursive: true });
|
|
13908
13977
|
if (existsSync66(file)) {
|
|
13909
|
-
skipped.push({ query: s.query, reason: `file already exists at ${
|
|
13978
|
+
skipped.push({ query: s.query, reason: `file already exists at ${path46.relative(root, file)}` });
|
|
13910
13979
|
continue;
|
|
13911
13980
|
}
|
|
13912
13981
|
await writeFile34(file, serializeMemory27({ frontmatter: fm, body }), "utf8");
|
|
13913
|
-
created.push({ id: fm.id, file:
|
|
13982
|
+
created.push({ id: fm.id, file: path46.relative(root, file), query: s.query });
|
|
13914
13983
|
}
|
|
13915
13984
|
if (opts.json) {
|
|
13916
13985
|
console.log(JSON.stringify({ created, skipped }, null, 2));
|
|
@@ -14010,7 +14079,7 @@ function truncate2(text, max) {
|
|
|
14010
14079
|
// src/commands/memory-archive.ts
|
|
14011
14080
|
import { existsSync as existsSync67 } from "fs";
|
|
14012
14081
|
import { writeFile as writeFile35 } from "fs/promises";
|
|
14013
|
-
import
|
|
14082
|
+
import path47 from "path";
|
|
14014
14083
|
import "commander";
|
|
14015
14084
|
import {
|
|
14016
14085
|
findProjectRoot as findProjectRoot44,
|
|
@@ -14054,7 +14123,7 @@ function registerMemoryArchive(memory2) {
|
|
|
14054
14123
|
if (fm.status === "deprecated" || fm.status === "rejected") continue;
|
|
14055
14124
|
const retired = retirementSignal2(fm, mem.body);
|
|
14056
14125
|
const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
|
|
14057
|
-
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync67(
|
|
14126
|
+
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync67(path47.join(paths.root, p)));
|
|
14058
14127
|
const isAnchorless = !hasAnyAnchor;
|
|
14059
14128
|
if (!retired.retired && !opts.unread && !isAnchorless && !allPathsGone) continue;
|
|
14060
14129
|
const u = getUsage21(usage, fm.id);
|
|
@@ -14131,7 +14200,7 @@ function parseDays(input) {
|
|
|
14131
14200
|
// src/commands/doctor.ts
|
|
14132
14201
|
import { existsSync as existsSync68, statSync as statSync2 } from "fs";
|
|
14133
14202
|
import { readFile as readFile23, stat, writeFile as writeFile36 } from "fs/promises";
|
|
14134
|
-
import
|
|
14203
|
+
import path48 from "path";
|
|
14135
14204
|
import { execFileSync, execSync as execSync3 } from "child_process";
|
|
14136
14205
|
import "commander";
|
|
14137
14206
|
import {
|
|
@@ -14185,8 +14254,8 @@ function registerDoctor(program2) {
|
|
|
14185
14254
|
fix: "haive init"
|
|
14186
14255
|
});
|
|
14187
14256
|
} else {
|
|
14188
|
-
const { readFile:
|
|
14189
|
-
const content = await
|
|
14257
|
+
const { readFile: readFile29 } = await import("fs/promises");
|
|
14258
|
+
const content = await readFile29(paths.projectContext, "utf8");
|
|
14190
14259
|
const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
|
|
14191
14260
|
if (isTemplate) {
|
|
14192
14261
|
findings.push({
|
|
@@ -14356,12 +14425,12 @@ function registerDoctor(program2) {
|
|
|
14356
14425
|
}
|
|
14357
14426
|
}
|
|
14358
14427
|
if (config.enforcement?.requireBriefingFirst) {
|
|
14359
|
-
const claudeSettings =
|
|
14428
|
+
const claudeSettings = path48.join(root, ".claude", "settings.local.json");
|
|
14360
14429
|
let hasClaudeEnforcement = false;
|
|
14361
14430
|
if (existsSync68(claudeSettings)) {
|
|
14362
14431
|
try {
|
|
14363
|
-
const { readFile:
|
|
14364
|
-
const raw = await
|
|
14432
|
+
const { readFile: readFile29 } = await import("fs/promises");
|
|
14433
|
+
const raw = await readFile29(claudeSettings, "utf8");
|
|
14365
14434
|
hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
|
|
14366
14435
|
} catch {
|
|
14367
14436
|
hasClaudeEnforcement = false;
|
|
@@ -14384,7 +14453,7 @@ function registerDoctor(program2) {
|
|
|
14384
14453
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
|
|
14385
14454
|
});
|
|
14386
14455
|
}
|
|
14387
|
-
findings.push(...await collectInstallFindings(root, "0.
|
|
14456
|
+
findings.push(...await collectInstallFindings(root, "0.24.0"));
|
|
14388
14457
|
findings.push(...await collectToolchainFindings(root));
|
|
14389
14458
|
try {
|
|
14390
14459
|
const legacyRaw = execSync3("haive-mcp --version", {
|
|
@@ -14392,7 +14461,7 @@ function registerDoctor(program2) {
|
|
|
14392
14461
|
timeout: 3e3,
|
|
14393
14462
|
stdio: ["ignore", "pipe", "ignore"]
|
|
14394
14463
|
}).trim();
|
|
14395
|
-
const cliVersion = "0.
|
|
14464
|
+
const cliVersion = "0.24.0";
|
|
14396
14465
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
14397
14466
|
findings.push({
|
|
14398
14467
|
severity: "warn",
|
|
@@ -14408,9 +14477,9 @@ npm uninstall -g @hiveai/mcp`
|
|
|
14408
14477
|
}
|
|
14409
14478
|
{
|
|
14410
14479
|
const configPaths = [
|
|
14411
|
-
|
|
14412
|
-
|
|
14413
|
-
|
|
14480
|
+
path48.join(root, ".mcp.json"),
|
|
14481
|
+
path48.join(root, ".cursor", "mcp.json"),
|
|
14482
|
+
path48.join(root, ".vscode", "mcp.json")
|
|
14414
14483
|
];
|
|
14415
14484
|
const staleConfigs = [];
|
|
14416
14485
|
for (const cfgPath of configPaths) {
|
|
@@ -14418,7 +14487,7 @@ npm uninstall -g @hiveai/mcp`
|
|
|
14418
14487
|
try {
|
|
14419
14488
|
const raw = await readFile23(cfgPath, "utf8");
|
|
14420
14489
|
if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
|
|
14421
|
-
staleConfigs.push(
|
|
14490
|
+
staleConfigs.push(path48.relative(root, cfgPath));
|
|
14422
14491
|
if (opts.fix && !opts.dryRun) {
|
|
14423
14492
|
const updated = raw.replace(/"command"\s*:\s*"haive-mcp"/g, '"command": "haive"').replace(/"args"\s*:\s*\[\]/g, '"args": ["mcp", "--stdio"]');
|
|
14424
14493
|
await writeFile36(cfgPath, updated, "utf8");
|
|
@@ -14709,7 +14778,7 @@ which -a haive`
|
|
|
14709
14778
|
".vscode/mcp.json"
|
|
14710
14779
|
];
|
|
14711
14780
|
for (const rel of integrationFiles) {
|
|
14712
|
-
const file =
|
|
14781
|
+
const file = path48.join(root, rel);
|
|
14713
14782
|
if (!existsSync68(file)) continue;
|
|
14714
14783
|
const text = await readFile23(file, "utf8").catch(() => "");
|
|
14715
14784
|
for (const bin of extractAbsoluteHaiveBins(text)) {
|
|
@@ -14736,7 +14805,7 @@ which -a haive`
|
|
|
14736
14805
|
async function collectToolchainFindings(root) {
|
|
14737
14806
|
const findings = [];
|
|
14738
14807
|
const pkg = await readJson(
|
|
14739
|
-
|
|
14808
|
+
path48.join(root, "package.json")
|
|
14740
14809
|
);
|
|
14741
14810
|
const wantsPnpm = pkg?.packageManager?.startsWith("pnpm@") || Object.values(pkg?.scripts ?? {}).some((script) => /\bpnpm\b/.test(script));
|
|
14742
14811
|
if (!wantsPnpm) return findings;
|
|
@@ -14755,9 +14824,9 @@ async function collectToolchainFindings(root) {
|
|
|
14755
14824
|
}
|
|
14756
14825
|
async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
14757
14826
|
const findings = [];
|
|
14758
|
-
const isHaiveWorkspace = (await readJson(
|
|
14827
|
+
const isHaiveWorkspace = (await readJson(path48.join(root, "package.json")))?.name === "haive-monorepo";
|
|
14759
14828
|
if (!isHaiveWorkspace) return findings;
|
|
14760
|
-
const cliDist =
|
|
14829
|
+
const cliDist = path48.join(root, "packages/cli/dist/index.js");
|
|
14761
14830
|
if (!existsSync68(cliDist)) {
|
|
14762
14831
|
findings.push({
|
|
14763
14832
|
severity: "warn",
|
|
@@ -14782,7 +14851,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
|
14782
14851
|
"packages/core/src/index.ts",
|
|
14783
14852
|
"packages/mcp/src/server.ts",
|
|
14784
14853
|
"packages/cli/src/index.ts"
|
|
14785
|
-
].map((rel) =>
|
|
14854
|
+
].map((rel) => path48.join(root, rel)).filter(existsSync68);
|
|
14786
14855
|
if (sourceFiles.length > 0) {
|
|
14787
14856
|
const distMtime = statSync2(cliDist).mtimeMs;
|
|
14788
14857
|
const newestSource = Math.max(...sourceFiles.map((file) => statSync2(file).mtimeMs));
|
|
@@ -14800,7 +14869,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
|
14800
14869
|
}
|
|
14801
14870
|
async function collectWorkspaceVersionFindings(root, expectedVersion) {
|
|
14802
14871
|
const findings = [];
|
|
14803
|
-
const rootPkg = await readJson(
|
|
14872
|
+
const rootPkg = await readJson(path48.join(root, "package.json"));
|
|
14804
14873
|
const workspacePackages = [
|
|
14805
14874
|
"packages/core/package.json",
|
|
14806
14875
|
"packages/embeddings/package.json",
|
|
@@ -14809,7 +14878,7 @@ async function collectWorkspaceVersionFindings(root, expectedVersion) {
|
|
|
14809
14878
|
];
|
|
14810
14879
|
const existing = (await Promise.all(workspacePackages.map(async (rel) => ({
|
|
14811
14880
|
rel,
|
|
14812
|
-
pkg: await readJson(
|
|
14881
|
+
pkg: await readJson(path48.join(root, rel))
|
|
14813
14882
|
})))).filter((item) => item.pkg);
|
|
14814
14883
|
const isHaiveWorkspace = rootPkg?.name === "haive-monorepo" || existing.some((item) => item.pkg?.name?.startsWith("@hiveai/"));
|
|
14815
14884
|
if (!isHaiveWorkspace) return findings;
|
|
@@ -15314,21 +15383,21 @@ function registerMemorySuggestTopic(memory2) {
|
|
|
15314
15383
|
}
|
|
15315
15384
|
|
|
15316
15385
|
// src/commands/resolve-project.ts
|
|
15317
|
-
import
|
|
15386
|
+
import path49 from "path";
|
|
15318
15387
|
import "commander";
|
|
15319
15388
|
import { resolveProjectInfo as resolveProjectInfo2 } from "@hiveai/core";
|
|
15320
15389
|
function registerResolveProject(program2) {
|
|
15321
15390
|
program2.command("resolve-project").description(
|
|
15322
15391
|
"Print JSON for hAIve project root resolution (HAIVE_PROJECT_ROOT, markers, .ai layout)."
|
|
15323
15392
|
).option("-d, --dir <dir>", "working directory", process.cwd()).action((opts) => {
|
|
15324
|
-
const info = resolveProjectInfo2({ cwd:
|
|
15393
|
+
const info = resolveProjectInfo2({ cwd: path49.resolve(opts.dir) });
|
|
15325
15394
|
console.log(JSON.stringify({ ok: true, info }, null, 2));
|
|
15326
15395
|
});
|
|
15327
15396
|
}
|
|
15328
15397
|
|
|
15329
15398
|
// src/commands/runtime-journal.ts
|
|
15330
15399
|
import { existsSync as existsSync71 } from "fs";
|
|
15331
|
-
import
|
|
15400
|
+
import path50 from "path";
|
|
15332
15401
|
import "commander";
|
|
15333
15402
|
import {
|
|
15334
15403
|
appendRuntimeJournalEntry as appendRuntimeJournalEntry3,
|
|
@@ -15342,15 +15411,15 @@ function registerRuntime(program2) {
|
|
|
15342
15411
|
);
|
|
15343
15412
|
const journal = runtime.command("journal").description("Append or read the machine-local session journal (NDJSON)");
|
|
15344
15413
|
journal.command("append").description("Append one JSON line to .ai/.runtime/session-journal.ndjson").argument("<message>", "short text to log").option("-k, --kind <kind>", "note | session_end | mcp", "note").option("-d, --dir <dir>", "project root", process.cwd()).action(async (message, opts) => {
|
|
15345
|
-
const root =
|
|
15414
|
+
const root = path50.resolve(opts.dir ?? process.cwd());
|
|
15346
15415
|
const paths = resolveHaivePaths45(findProjectRoot49(root));
|
|
15347
15416
|
const raw = opts.kind ?? "note";
|
|
15348
15417
|
const kind = ["note", "session_end", "mcp"].includes(raw) ? raw : "note";
|
|
15349
15418
|
await appendRuntimeJournalEntry3(paths, { kind, message });
|
|
15350
|
-
ui.success(`Appended to ${
|
|
15419
|
+
ui.success(`Appended to ${path50.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
|
|
15351
15420
|
});
|
|
15352
15421
|
journal.command("tail").description("Print the last N entries from the runtime session journal as JSON").option("-n, --limit <n>", "number of lines", "30").option("-d, --dir <dir>", "project root", process.cwd()).action(async (opts) => {
|
|
15353
|
-
const root =
|
|
15422
|
+
const root = path50.resolve(opts.dir ?? process.cwd());
|
|
15354
15423
|
const paths = resolveHaivePaths45(findProjectRoot49(root));
|
|
15355
15424
|
const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
|
|
15356
15425
|
if (!existsSync71(paths.haiveDir)) {
|
|
@@ -15369,7 +15438,7 @@ function registerRuntime(program2) {
|
|
|
15369
15438
|
|
|
15370
15439
|
// src/commands/memory-timeline.ts
|
|
15371
15440
|
import { existsSync as existsSync73 } from "fs";
|
|
15372
|
-
import
|
|
15441
|
+
import path51 from "path";
|
|
15373
15442
|
import "commander";
|
|
15374
15443
|
import {
|
|
15375
15444
|
collectTimelineEntries as collectTimelineEntries2,
|
|
@@ -15385,7 +15454,7 @@ function registerMemoryTimeline(memory2) {
|
|
|
15385
15454
|
process.exitCode = 1;
|
|
15386
15455
|
return;
|
|
15387
15456
|
}
|
|
15388
|
-
const root =
|
|
15457
|
+
const root = path51.resolve(opts.dir ?? process.cwd());
|
|
15389
15458
|
const paths = resolveHaivePaths46(findProjectRoot50(root));
|
|
15390
15459
|
if (!existsSync73(paths.memoriesDir)) {
|
|
15391
15460
|
ui.error("No memories \u2014 run `haive init`.");
|
|
@@ -15406,7 +15475,7 @@ function registerMemoryTimeline(memory2) {
|
|
|
15406
15475
|
|
|
15407
15476
|
// src/commands/memory-conflict-candidates.ts
|
|
15408
15477
|
import { existsSync as existsSync74 } from "fs";
|
|
15409
|
-
import
|
|
15478
|
+
import path53 from "path";
|
|
15410
15479
|
import "commander";
|
|
15411
15480
|
import {
|
|
15412
15481
|
findLexicalConflictPairs as findLexicalConflictPairs2,
|
|
@@ -15428,7 +15497,7 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
15428
15497
|
"decision,architecture,convention,gotcha (lexical scan)",
|
|
15429
15498
|
"decision,architecture"
|
|
15430
15499
|
).option("--min-jaccard <x>", "minimum Jaccard for lexical pairs", "0.45").option("--max-pairs <n>", "cap lexical pairs", "20").option("--max-scan <n>", "max memories scanned (lexical)", "500").option("--max-topic-pairs <n>", "cap topic/status pairs", "20").action(async (opts) => {
|
|
15431
|
-
const root =
|
|
15500
|
+
const root = path53.resolve(opts.dir ?? process.cwd());
|
|
15432
15501
|
const paths = resolveHaivePaths47(findProjectRoot51(root));
|
|
15433
15502
|
if (!existsSync74(paths.memoriesDir)) {
|
|
15434
15503
|
ui.error("No memories \u2014 run `haive init`.");
|
|
@@ -15465,10 +15534,11 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
15465
15534
|
}
|
|
15466
15535
|
|
|
15467
15536
|
// src/commands/enforce.ts
|
|
15468
|
-
import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
|
|
15537
|
+
import { execFile as execFile3, execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
|
|
15469
15538
|
import { existsSync as existsSync75, statSync as statSync3 } from "fs";
|
|
15470
15539
|
import { chmod as chmod2, mkdir as mkdir21, readFile as readFile24, readdir as readdir6, rm as rm3, writeFile as writeFile37 } from "fs/promises";
|
|
15471
|
-
import
|
|
15540
|
+
import path54 from "path";
|
|
15541
|
+
import { promisify as promisify3 } from "util";
|
|
15472
15542
|
import "commander";
|
|
15473
15543
|
import {
|
|
15474
15544
|
antiPatternGateParams as antiPatternGateParams2,
|
|
@@ -15482,15 +15552,18 @@ import {
|
|
|
15482
15552
|
loadMemoriesFromDir as loadMemoriesFromDir38,
|
|
15483
15553
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
|
|
15484
15554
|
readRecentBriefingMarker,
|
|
15555
|
+
recordPreventionHits as recordPreventionHits2,
|
|
15485
15556
|
resolveBriefingBudget as resolveBriefingBudget3,
|
|
15486
15557
|
resolveHaivePaths as resolveHaivePaths48,
|
|
15487
15558
|
runSensors as runSensors2,
|
|
15488
15559
|
saveConfig as saveConfig4,
|
|
15560
|
+
selectCommandSensors,
|
|
15489
15561
|
sensorTargetsFromDiff as sensorTargetsFromDiff2,
|
|
15490
15562
|
SESSION_RECAP_TTL_MS,
|
|
15491
15563
|
verifyAnchor as verifyAnchor4,
|
|
15492
15564
|
writeBriefingMarker as writeBriefingMarker3
|
|
15493
15565
|
} from "@hiveai/core";
|
|
15566
|
+
var execFileAsync2 = promisify3(execFile3);
|
|
15494
15567
|
var MAX_STDIN_BYTES2 = 256 * 1024;
|
|
15495
15568
|
var ENFORCE_HOOK_MARKER = "# hAIve enforcement hook";
|
|
15496
15569
|
function registerEnforce(program2) {
|
|
@@ -15524,7 +15597,7 @@ function registerEnforce(program2) {
|
|
|
15524
15597
|
if (opts.claude !== false) {
|
|
15525
15598
|
try {
|
|
15526
15599
|
const result = await installClaudeHooksAtPath(defaultClaudeSettingsPath("project", root));
|
|
15527
|
-
ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${
|
|
15600
|
+
ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path54.relative(root, result.settingsPath)})`);
|
|
15528
15601
|
} catch (err) {
|
|
15529
15602
|
ui.warn(`Claude Code hooks not installed: ${err instanceof Error ? err.message : String(err)}`);
|
|
15530
15603
|
}
|
|
@@ -15545,19 +15618,19 @@ function registerEnforce(program2) {
|
|
|
15545
15618
|
enforce.command("cleanup").description("Remove generated hAIve 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) => {
|
|
15546
15619
|
const root = findProjectRoot52(opts.dir);
|
|
15547
15620
|
const paths = resolveHaivePaths48(root);
|
|
15548
|
-
const cacheDir =
|
|
15621
|
+
const cacheDir = path54.join(paths.haiveDir, ".cache");
|
|
15549
15622
|
if (existsSync75(cacheDir)) {
|
|
15550
|
-
if (opts.dryRun) ui.info(`would clean ${
|
|
15623
|
+
if (opts.dryRun) ui.info(`would clean ${path54.relative(root, cacheDir)} (preserving .gitignore)`);
|
|
15551
15624
|
else {
|
|
15552
15625
|
const removed = await cleanupCacheDir(cacheDir);
|
|
15553
|
-
ui.success(`cleaned ${
|
|
15626
|
+
ui.success(`cleaned ${path54.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
|
|
15554
15627
|
}
|
|
15555
15628
|
}
|
|
15556
15629
|
if (existsSync75(paths.runtimeDir)) {
|
|
15557
|
-
if (opts.dryRun) ui.info(`would clean ${
|
|
15630
|
+
if (opts.dryRun) ui.info(`would clean ${path54.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
|
|
15558
15631
|
else {
|
|
15559
15632
|
const removed = await cleanupRuntimeDir(paths.runtimeDir);
|
|
15560
|
-
ui.success(`cleaned ${
|
|
15633
|
+
ui.success(`cleaned ${path54.relative(root, paths.runtimeDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
|
|
15561
15634
|
}
|
|
15562
15635
|
}
|
|
15563
15636
|
});
|
|
@@ -15910,7 +15983,7 @@ async function buildFinishReport(dir) {
|
|
|
15910
15983
|
async function checkFailureCapture(paths, config) {
|
|
15911
15984
|
const gate = config.enforcement?.failureCaptureGate ?? "warn";
|
|
15912
15985
|
if (gate === "off") return [];
|
|
15913
|
-
const obsFile =
|
|
15986
|
+
const obsFile = path54.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
15914
15987
|
if (!existsSync75(obsFile)) return [];
|
|
15915
15988
|
const failures = [];
|
|
15916
15989
|
try {
|
|
@@ -15982,7 +16055,7 @@ async function runWithEnforcement(command, args, opts) {
|
|
|
15982
16055
|
process.exit(2);
|
|
15983
16056
|
}
|
|
15984
16057
|
ui.info(`hAIve briefing marker created for wrapped agent session: ${sessionId}`);
|
|
15985
|
-
ui.info(`Briefing written to ${
|
|
16058
|
+
ui.info(`Briefing written to ${path54.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
|
|
15986
16059
|
const child = spawn6(command, args, {
|
|
15987
16060
|
cwd: root,
|
|
15988
16061
|
stdio: "inherit",
|
|
@@ -16032,9 +16105,9 @@ async function writeWrapperBriefing(paths, sessionId, task) {
|
|
|
16032
16105
|
source: "haive-run",
|
|
16033
16106
|
memoryIds: briefing.memories.map((m) => m.id)
|
|
16034
16107
|
});
|
|
16035
|
-
const dir =
|
|
16108
|
+
const dir = path54.join(paths.runtimeDir, "enforcement", "briefings");
|
|
16036
16109
|
await mkdir21(dir, { recursive: true });
|
|
16037
|
-
const file =
|
|
16110
|
+
const file = path54.join(dir, `${sessionId}.md`);
|
|
16038
16111
|
const parts = [
|
|
16039
16112
|
"# hAIve Briefing",
|
|
16040
16113
|
"",
|
|
@@ -16092,7 +16165,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
16092
16165
|
findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
|
|
16093
16166
|
});
|
|
16094
16167
|
}
|
|
16095
|
-
findings.push(...await inspectIntegrationVersions(root, "0.
|
|
16168
|
+
findings.push(...await inspectIntegrationVersions(root, "0.24.0"));
|
|
16096
16169
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
16097
16170
|
const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
|
|
16098
16171
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
|
|
@@ -16243,7 +16316,7 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
|
16243
16316
|
const consulted = new Set(marker?.memory_ids ?? []);
|
|
16244
16317
|
const missing = relevant.filter(({ memory: memory2, filePath }) => {
|
|
16245
16318
|
if (consulted.has(memory2.frontmatter.id)) return false;
|
|
16246
|
-
if (changedSet.has(
|
|
16319
|
+
if (changedSet.has(path54.relative(paths.root, filePath))) return false;
|
|
16247
16320
|
return true;
|
|
16248
16321
|
}).map(({ memory: memory2 }) => memory2);
|
|
16249
16322
|
if (missing.length === 0) {
|
|
@@ -16344,19 +16417,19 @@ async function runSensorGate(paths, diff) {
|
|
|
16344
16417
|
if (!diff || !existsSync75(paths.memoriesDir)) return [];
|
|
16345
16418
|
try {
|
|
16346
16419
|
const loaded = await loadMemoriesFromDir38(paths.memoriesDir);
|
|
16347
|
-
const
|
|
16348
|
-
|
|
16349
|
-
return Boolean(s) && s.kind === "regex" && !isRetiredMemory3(m.frontmatter, m.body);
|
|
16350
|
-
});
|
|
16351
|
-
if (sensorMemories.length === 0) return [];
|
|
16420
|
+
const scannable = loaded.map((l) => l.memory).filter((m) => Boolean(m.frontmatter.sensor) && !isRetiredMemory3(m.frontmatter, m.body));
|
|
16421
|
+
if (scannable.length === 0) return [];
|
|
16352
16422
|
const targets = sensorTargetsFromDiff2(diff).filter((t) => isSensorScannablePath(t.path));
|
|
16353
16423
|
if (targets.length === 0) return [];
|
|
16354
|
-
const hits = runSensors2(sensorMemories, targets);
|
|
16355
16424
|
const findings = [];
|
|
16356
16425
|
const seen = /* @__PURE__ */ new Set();
|
|
16426
|
+
const firedIds = /* @__PURE__ */ new Set();
|
|
16427
|
+
const regexSensorMemories = scannable.filter((m) => m.frontmatter.sensor.kind === "regex");
|
|
16428
|
+
const hits = regexSensorMemories.length > 0 ? runSensors2(regexSensorMemories, targets) : [];
|
|
16357
16429
|
for (const hit of hits) {
|
|
16358
16430
|
if (seen.has(hit.memory_id)) continue;
|
|
16359
16431
|
seen.add(hit.memory_id);
|
|
16432
|
+
firedIds.add(hit.memory_id);
|
|
16360
16433
|
const where = hit.file ? ` (${hit.file})` : "";
|
|
16361
16434
|
if (hit.severity === "block") {
|
|
16362
16435
|
findings.push({
|
|
@@ -16376,11 +16449,51 @@ async function runSensorGate(paths, diff) {
|
|
|
16376
16449
|
});
|
|
16377
16450
|
}
|
|
16378
16451
|
}
|
|
16452
|
+
const config = await loadConfig14(paths).catch(() => null);
|
|
16453
|
+
if (config?.enforcement?.runCommandSensors === true) {
|
|
16454
|
+
const changedPaths = targets.map((t) => t.path).filter(Boolean);
|
|
16455
|
+
for (const spec of selectCommandSensors(scannable, changedPaths)) {
|
|
16456
|
+
if (seen.has(spec.memory_id)) continue;
|
|
16457
|
+
const failed = await runGateCommandSensor(spec, paths.root);
|
|
16458
|
+
if (!failed) continue;
|
|
16459
|
+
seen.add(spec.memory_id);
|
|
16460
|
+
firedIds.add(spec.memory_id);
|
|
16461
|
+
if (spec.severity === "block") {
|
|
16462
|
+
findings.push({
|
|
16463
|
+
severity: "error",
|
|
16464
|
+
code: "sensor-block",
|
|
16465
|
+
message: `Block command sensor fired \u2014 ${spec.memory_id}: ${spec.message} (command: ${spec.command})`,
|
|
16466
|
+
fix: "Fix the condition the command checks, or run `haive sensors check --commands` to inspect it.",
|
|
16467
|
+
impact: 45
|
|
16468
|
+
});
|
|
16469
|
+
} else {
|
|
16470
|
+
findings.push({
|
|
16471
|
+
severity: "warn",
|
|
16472
|
+
code: "sensor-warn",
|
|
16473
|
+
message: `Command sensor flagged ${spec.memory_id}: ${spec.message}`,
|
|
16474
|
+
fix: "Review the failing command; `haive sensors check --commands` re-runs it.",
|
|
16475
|
+
impact: 5
|
|
16476
|
+
});
|
|
16477
|
+
}
|
|
16478
|
+
}
|
|
16479
|
+
}
|
|
16480
|
+
if (firedIds.size > 0) {
|
|
16481
|
+
await recordPreventionHits2(paths, [...firedIds], "sensor").catch(() => {
|
|
16482
|
+
});
|
|
16483
|
+
}
|
|
16379
16484
|
return findings;
|
|
16380
16485
|
} catch {
|
|
16381
16486
|
return [];
|
|
16382
16487
|
}
|
|
16383
16488
|
}
|
|
16489
|
+
async function runGateCommandSensor(spec, root) {
|
|
16490
|
+
try {
|
|
16491
|
+
await execFileAsync2("bash", ["-c", spec.command], { cwd: root, timeout: 12e4, maxBuffer: 8 * 1024 * 1024 });
|
|
16492
|
+
return false;
|
|
16493
|
+
} catch {
|
|
16494
|
+
return true;
|
|
16495
|
+
}
|
|
16496
|
+
}
|
|
16384
16497
|
async function findGeneratedArtifacts(paths) {
|
|
16385
16498
|
const dirty = await runCommand4("git", ["status", "--short", "--untracked-files=all"], paths.root).catch(() => "");
|
|
16386
16499
|
const generated = dirty.split("\n").map((line) => line.trim()).filter(Boolean).filter(
|
|
@@ -16411,16 +16524,16 @@ async function cleanupRuntimeDir(runtimeDir) {
|
|
|
16411
16524
|
for (const entry of entries) {
|
|
16412
16525
|
if (entry.name === ".gitignore" || entry.name === "README.md") continue;
|
|
16413
16526
|
if (entry.name === "enforcement") {
|
|
16414
|
-
removed += await cleanupEnforcementDir(
|
|
16527
|
+
removed += await cleanupEnforcementDir(path54.join(runtimeDir, entry.name));
|
|
16415
16528
|
continue;
|
|
16416
16529
|
}
|
|
16417
|
-
await rm3(
|
|
16530
|
+
await rm3(path54.join(runtimeDir, entry.name), { recursive: true, force: true });
|
|
16418
16531
|
removed++;
|
|
16419
16532
|
}
|
|
16420
|
-
await writeFile37(
|
|
16421
|
-
if (!existsSync75(
|
|
16533
|
+
await writeFile37(path54.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
|
|
16534
|
+
if (!existsSync75(path54.join(runtimeDir, "README.md"))) {
|
|
16422
16535
|
await writeFile37(
|
|
16423
|
-
|
|
16536
|
+
path54.join(runtimeDir, "README.md"),
|
|
16424
16537
|
"# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. hAIve cleanup preserves briefing markers so enforcement state remains valid.\n",
|
|
16425
16538
|
"utf8"
|
|
16426
16539
|
);
|
|
@@ -16433,10 +16546,10 @@ async function cleanupCacheDir(cacheDir) {
|
|
|
16433
16546
|
const entries = await readdir6(cacheDir, { withFileTypes: true }).catch(() => []);
|
|
16434
16547
|
for (const entry of entries) {
|
|
16435
16548
|
if (entry.name === ".gitignore") continue;
|
|
16436
|
-
await rm3(
|
|
16549
|
+
await rm3(path54.join(cacheDir, entry.name), { recursive: true, force: true });
|
|
16437
16550
|
removed++;
|
|
16438
16551
|
}
|
|
16439
|
-
await writeFile37(
|
|
16552
|
+
await writeFile37(path54.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
|
|
16440
16553
|
return removed;
|
|
16441
16554
|
}
|
|
16442
16555
|
async function cleanupEnforcementDir(enforcementDir) {
|
|
@@ -16444,7 +16557,7 @@ async function cleanupEnforcementDir(enforcementDir) {
|
|
|
16444
16557
|
const entries = await readdir6(enforcementDir, { withFileTypes: true }).catch(() => []);
|
|
16445
16558
|
for (const entry of entries) {
|
|
16446
16559
|
if (entry.name === "briefings") continue;
|
|
16447
|
-
await rm3(
|
|
16560
|
+
await rm3(path54.join(enforcementDir, entry.name), { recursive: true, force: true });
|
|
16448
16561
|
removed++;
|
|
16449
16562
|
}
|
|
16450
16563
|
return removed;
|
|
@@ -16460,7 +16573,7 @@ async function inspectIntegrationVersions(root, expectedVersion) {
|
|
|
16460
16573
|
];
|
|
16461
16574
|
const findings = [];
|
|
16462
16575
|
for (const rel of files) {
|
|
16463
|
-
const file =
|
|
16576
|
+
const file = path54.join(root, rel);
|
|
16464
16577
|
if (!existsSync75(file)) continue;
|
|
16465
16578
|
const text = await readFile24(file, "utf8").catch(() => "");
|
|
16466
16579
|
for (const bin of extractAbsoluteHaiveBins2(text)) {
|
|
@@ -16676,7 +16789,7 @@ function isShippablePath(file) {
|
|
|
16676
16789
|
}
|
|
16677
16790
|
var CI_SKIP_DIRECTIVE = /\[skip ci\]|\[ci skip\]|\[no ci\]|\[skip actions\]|\*\*\*NO_CI\*\*\*|skip-checks: *true/i;
|
|
16678
16791
|
async function checkCommitMessageSkipCi(root, msgfile) {
|
|
16679
|
-
const file =
|
|
16792
|
+
const file = path54.isAbsolute(msgfile) ? msgfile : path54.join(root, msgfile);
|
|
16680
16793
|
const raw = await readFile24(file, "utf8").catch(() => "");
|
|
16681
16794
|
const cleaned = raw.split("\n").filter((line) => !line.startsWith("#")).join("\n");
|
|
16682
16795
|
if (!CI_SKIP_DIRECTIVE.test(cleaned)) return { block: false, message: "" };
|
|
@@ -16705,7 +16818,7 @@ async function inspectReleaseVersionState(root, upstream) {
|
|
|
16705
16818
|
}
|
|
16706
16819
|
async function readPackageVersion(root, relPath) {
|
|
16707
16820
|
try {
|
|
16708
|
-
const data = JSON.parse(await readFile24(
|
|
16821
|
+
const data = JSON.parse(await readFile24(path54.join(root, relPath), "utf8"));
|
|
16709
16822
|
return typeof data.version === "string" ? data.version : void 0;
|
|
16710
16823
|
} catch {
|
|
16711
16824
|
return void 0;
|
|
@@ -16893,8 +17006,8 @@ function buildScore(findings, threshold = 80) {
|
|
|
16893
17006
|
};
|
|
16894
17007
|
}
|
|
16895
17008
|
async function installGitEnforcement(root) {
|
|
16896
|
-
const hooksDir =
|
|
16897
|
-
if (!existsSync75(
|
|
17009
|
+
const hooksDir = path54.join(root, ".git", "hooks");
|
|
17010
|
+
if (!existsSync75(path54.join(root, ".git"))) {
|
|
16898
17011
|
ui.warn("No .git directory found; git enforcement hooks skipped.");
|
|
16899
17012
|
return;
|
|
16900
17013
|
}
|
|
@@ -16923,7 +17036,7 @@ haive enforce commit-msg "$1" --dir . || exit $?
|
|
|
16923
17036
|
}
|
|
16924
17037
|
];
|
|
16925
17038
|
for (const hook of hooks) {
|
|
16926
|
-
const file =
|
|
17039
|
+
const file = path54.join(hooksDir, hook.name);
|
|
16927
17040
|
if (existsSync75(file)) {
|
|
16928
17041
|
const current = await readFile24(file, "utf8").catch(() => "");
|
|
16929
17042
|
if (current.includes(ENFORCE_HOOK_MARKER)) {
|
|
@@ -16941,8 +17054,8 @@ ${hook.body}`, "utf8");
|
|
|
16941
17054
|
ui.success("Installed blocking git enforcement hooks: pre-commit, pre-push, commit-msg");
|
|
16942
17055
|
}
|
|
16943
17056
|
async function installCiEnforcement(root) {
|
|
16944
|
-
const workflowPath =
|
|
16945
|
-
await mkdir21(
|
|
17057
|
+
const workflowPath = path54.join(root, ".github", "workflows", "haive-enforcement.yml");
|
|
17058
|
+
await mkdir21(path54.dirname(workflowPath), { recursive: true });
|
|
16946
17059
|
if (existsSync75(workflowPath)) {
|
|
16947
17060
|
ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
|
|
16948
17061
|
return;
|
|
@@ -16974,7 +17087,7 @@ jobs:
|
|
|
16974
17087
|
HAIVE_HEAD_SHA: \${{ github.event.pull_request.head.sha || github.sha }}
|
|
16975
17088
|
run: haive enforce ci
|
|
16976
17089
|
`, "utf8");
|
|
16977
|
-
ui.success(`Created ${
|
|
17090
|
+
ui.success(`Created ${path54.relative(root, workflowPath)}`);
|
|
16978
17091
|
}
|
|
16979
17092
|
function printReport(report, json, explain = false) {
|
|
16980
17093
|
if (json) {
|
|
@@ -17078,8 +17191,8 @@ function extractToolPaths(payload, root) {
|
|
|
17078
17191
|
}
|
|
17079
17192
|
function normalizeToolPath(file, root) {
|
|
17080
17193
|
const normalized = file.replace(/\\/g, "/");
|
|
17081
|
-
if (!
|
|
17082
|
-
return
|
|
17194
|
+
if (!path54.isAbsolute(normalized)) return normalized.replace(/^\.\//, "");
|
|
17195
|
+
return path54.relative(root, normalized).replace(/\\/g, "/");
|
|
17083
17196
|
}
|
|
17084
17197
|
async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
|
|
17085
17198
|
if (!existsSync75(paths.memoriesDir)) return [];
|
|
@@ -17127,7 +17240,7 @@ async function readStdin2(maxBytes) {
|
|
|
17127
17240
|
}
|
|
17128
17241
|
var ATOMIC_STAGE_EXCLUDE = ["/.usage/", "/.runtime/", "/.cache/"];
|
|
17129
17242
|
async function stageResyncedArtifacts(root, paths) {
|
|
17130
|
-
const aiRel =
|
|
17243
|
+
const aiRel = path54.relative(root, paths.haiveDir);
|
|
17131
17244
|
const out = await runCommand4("git", ["diff", "--name-only", "--", aiRel], root).catch(() => "");
|
|
17132
17245
|
const toStage = out.split("\n").map((line) => line.trim()).filter(Boolean).filter((file) => !ATOMIC_STAGE_EXCLUDE.some((excl) => `/${file}`.includes(excl)));
|
|
17133
17246
|
if (toStage.length === 0) return;
|
|
@@ -17166,28 +17279,25 @@ function registerRun(program2) {
|
|
|
17166
17279
|
}
|
|
17167
17280
|
|
|
17168
17281
|
// src/commands/sensors.ts
|
|
17169
|
-
import { execFile as
|
|
17282
|
+
import { execFile as execFile4 } from "child_process";
|
|
17170
17283
|
import { existsSync as existsSync76 } from "fs";
|
|
17171
17284
|
import { chmod as chmod3, mkdir as mkdir23, readFile as readFile25, writeFile as writeFile38 } from "fs/promises";
|
|
17172
|
-
import
|
|
17173
|
-
import { promisify as
|
|
17285
|
+
import path55 from "path";
|
|
17286
|
+
import { promisify as promisify4 } from "util";
|
|
17174
17287
|
import "commander";
|
|
17175
17288
|
import {
|
|
17176
|
-
appendPreventionEvent as appendPreventionEvent2,
|
|
17177
17289
|
findProjectRoot as findProjectRoot53,
|
|
17178
17290
|
isRetiredMemory as isRetiredMemory4,
|
|
17179
17291
|
loadConfig as loadConfig15,
|
|
17180
17292
|
loadMemoriesFromDir as loadMemoriesFromDir39,
|
|
17181
|
-
|
|
17182
|
-
recordPrevention as recordPrevention2,
|
|
17293
|
+
recordPreventionHits as recordPreventionHits3,
|
|
17183
17294
|
resolveHaivePaths as resolveHaivePaths49,
|
|
17184
17295
|
runSensors as runSensors3,
|
|
17185
|
-
|
|
17186
|
-
selectCommandSensors,
|
|
17296
|
+
selectCommandSensors as selectCommandSensors2,
|
|
17187
17297
|
sensorTargetsFromDiff as sensorTargetsFromDiff3,
|
|
17188
17298
|
serializeMemory as serializeMemory29
|
|
17189
17299
|
} from "@hiveai/core";
|
|
17190
|
-
var exec2 =
|
|
17300
|
+
var exec2 = promisify4(execFile4);
|
|
17191
17301
|
function registerSensors(program2) {
|
|
17192
17302
|
const sensors = program2.command("sensors").description("Operate executable sensors derived from hAIve memories");
|
|
17193
17303
|
sensors.command("list").description("List memories carrying executable sensors").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
@@ -17217,14 +17327,14 @@ function registerSensors(program2) {
|
|
|
17217
17327
|
const root = findProjectRoot53(opts.dir);
|
|
17218
17328
|
const paths = resolveHaivePaths49(root);
|
|
17219
17329
|
const memories = await runnableSensorMemories(paths);
|
|
17220
|
-
const diff = opts.diffFile ? await readFile25(
|
|
17330
|
+
const diff = opts.diffFile ? await readFile25(path55.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
|
|
17221
17331
|
const targets = sensorTargetsFromDiff3(diff);
|
|
17222
17332
|
const hits = runSensors3(memories, targets.length > 0 ? targets : [{ path: "", content: diff }]);
|
|
17223
17333
|
const config = await loadConfig15(paths);
|
|
17224
17334
|
const runCommands = opts.commands || config.enforcement?.runCommandSensors === true;
|
|
17225
17335
|
const changedPaths = targets.map((t) => t.path).filter(Boolean);
|
|
17226
17336
|
const allSensorMemories = await runnableSensorMemories(paths, false);
|
|
17227
|
-
const commandSpecs =
|
|
17337
|
+
const commandSpecs = selectCommandSensors2(allSensorMemories, changedPaths);
|
|
17228
17338
|
const commandHits = [];
|
|
17229
17339
|
const commandSkipped = [];
|
|
17230
17340
|
if (commandSpecs.length > 0 && runCommands) {
|
|
@@ -17243,20 +17353,7 @@ function registerSensors(program2) {
|
|
|
17243
17353
|
for (const spec of commandSpecs) commandSkipped.push(spec.memory_id);
|
|
17244
17354
|
}
|
|
17245
17355
|
const firedIds = [...new Set([...hits, ...commandHits].map((hit) => hit.memory_id))];
|
|
17246
|
-
|
|
17247
|
-
const usage = await loadUsageIndex31(paths);
|
|
17248
|
-
const recordedIds = [];
|
|
17249
|
-
for (const id of firedIds) if (recordPrevention2(usage, id)) recordedIds.push(id);
|
|
17250
|
-
if (recordedIds.length > 0) {
|
|
17251
|
-
await saveUsageIndex8(paths, usage).catch(() => {
|
|
17252
|
-
});
|
|
17253
|
-
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
17254
|
-
for (const id of recordedIds) {
|
|
17255
|
-
await appendPreventionEvent2(paths, { at, id, source: "sensor" }).catch(() => {
|
|
17256
|
-
});
|
|
17257
|
-
}
|
|
17258
|
-
}
|
|
17259
|
-
}
|
|
17356
|
+
await recordPreventionHits3(paths, firedIds, "sensor");
|
|
17260
17357
|
const output = {
|
|
17261
17358
|
scanned: memories.length,
|
|
17262
17359
|
hits: hits.map((hit) => ({
|
|
@@ -17342,13 +17439,13 @@ function registerSensors(program2) {
|
|
|
17342
17439
|
const root = findProjectRoot53(opts.dir);
|
|
17343
17440
|
const paths = resolveHaivePaths49(root);
|
|
17344
17441
|
const rows = await sensorRows(paths);
|
|
17345
|
-
const outDir =
|
|
17442
|
+
const outDir = path55.resolve(root, opts.outDir ?? ".ai/generated");
|
|
17346
17443
|
await mkdir23(outDir, { recursive: true });
|
|
17347
|
-
const outPath =
|
|
17444
|
+
const outPath = path55.join(outDir, format === "grep" ? "haive-sensors-grep.sh" : "haive-sensors-eslint.json");
|
|
17348
17445
|
const content = format === "grep" ? renderGrepScript(rows) : JSON.stringify({ sensors: rows }, null, 2) + "\n";
|
|
17349
17446
|
await writeFile38(outPath, content, "utf8");
|
|
17350
17447
|
if (format === "grep") await chmod3(outPath, 493);
|
|
17351
|
-
ui.success(`Exported ${rows.length} sensor(s): ${
|
|
17448
|
+
ui.success(`Exported ${rows.length} sensor(s): ${path55.relative(root, outPath)}`);
|
|
17352
17449
|
});
|
|
17353
17450
|
}
|
|
17354
17451
|
async function sensorRows(paths) {
|
|
@@ -17421,7 +17518,7 @@ function shellQuote(value) {
|
|
|
17421
17518
|
// src/commands/ingest.ts
|
|
17422
17519
|
import { existsSync as existsSync77 } from "fs";
|
|
17423
17520
|
import { mkdir as mkdir24, readFile as readFile26, writeFile as writeFile39 } from "fs/promises";
|
|
17424
|
-
import
|
|
17521
|
+
import path56 from "path";
|
|
17425
17522
|
import "commander";
|
|
17426
17523
|
import {
|
|
17427
17524
|
draftsFromFindings as draftsFromFindings2,
|
|
@@ -17478,7 +17575,7 @@ function registerIngest(program2) {
|
|
|
17478
17575
|
process.exitCode = 1;
|
|
17479
17576
|
return;
|
|
17480
17577
|
}
|
|
17481
|
-
const reportPath =
|
|
17578
|
+
const reportPath = path56.resolve(root, file);
|
|
17482
17579
|
if (!existsSync77(reportPath)) {
|
|
17483
17580
|
ui.error(`Report file not found: ${reportPath}`);
|
|
17484
17581
|
process.exitCode = 1;
|
|
@@ -17567,13 +17664,13 @@ function registerIngest(program2) {
|
|
|
17567
17664
|
await writeDraft2(paths, draft);
|
|
17568
17665
|
created++;
|
|
17569
17666
|
}
|
|
17570
|
-
ui.success(`Created ${created} proposed memory(ies) under ${
|
|
17667
|
+
ui.success(`Created ${created} proposed memory(ies) under ${path56.relative(root, paths.memoriesDir)}/`);
|
|
17571
17668
|
ui.info("Review with `haive memory pending`; promote sensors with `haive sensors promote <id> --yes`.");
|
|
17572
17669
|
});
|
|
17573
17670
|
}
|
|
17574
17671
|
async function writeDraft2(paths, draft) {
|
|
17575
17672
|
const file = memoryFilePath12(paths, draft.frontmatter.scope, draft.frontmatter.id, draft.frontmatter.module);
|
|
17576
|
-
await mkdir24(
|
|
17673
|
+
await mkdir24(path56.dirname(file), { recursive: true });
|
|
17577
17674
|
await writeFile39(file, serializeMemory30({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
|
|
17578
17675
|
return file;
|
|
17579
17676
|
}
|
|
@@ -17623,7 +17720,7 @@ import {
|
|
|
17623
17720
|
loadConfig as loadConfig16,
|
|
17624
17721
|
loadMemoriesFromDir as loadMemoriesFromDir41,
|
|
17625
17722
|
loadPreventionEvents as loadPreventionEvents4,
|
|
17626
|
-
loadUsageIndex as
|
|
17723
|
+
loadUsageIndex as loadUsageIndex31,
|
|
17627
17724
|
resolveHaivePaths as resolveHaivePaths51
|
|
17628
17725
|
} from "@hiveai/core";
|
|
17629
17726
|
function registerDashboard(program2) {
|
|
@@ -17638,7 +17735,7 @@ function registerDashboard(program2) {
|
|
|
17638
17735
|
return;
|
|
17639
17736
|
}
|
|
17640
17737
|
const memories = existsSync78(paths.memoriesDir) ? await loadMemoriesFromDir41(paths.memoriesDir) : [];
|
|
17641
|
-
const usage = await
|
|
17738
|
+
const usage = await loadUsageIndex31(paths);
|
|
17642
17739
|
const preventionEvents = await loadPreventionEvents4(paths);
|
|
17643
17740
|
const config = await loadConfig16(paths);
|
|
17644
17741
|
const top = Math.max(1, Number.parseInt(opts.top ?? "10", 10) || 10);
|
|
@@ -17665,6 +17762,18 @@ function renderDashboard(r) {
|
|
|
17665
17762
|
console.log(` ${ui.dim("scopes:")} ${formatCounts(inv.by_scope)}`);
|
|
17666
17763
|
console.log(` ${ui.dim("types: ")} ${formatCounts(inv.by_type)}`);
|
|
17667
17764
|
console.log();
|
|
17765
|
+
console.log(ui.bold("Value") + ui.dim(" (what hAIve demonstrably earned \u2014 vs its per-task cost)"));
|
|
17766
|
+
const blocked = prevention.trend.last_30d;
|
|
17767
|
+
const demonstrated = impact.high;
|
|
17768
|
+
console.log(
|
|
17769
|
+
` ${blocked > 0 ? ui.green(`${blocked} repeat${blocked === 1 ? "" : "s"} blocked (30d)`) : "0 repeats blocked (30d)"} \xB7 ${demonstrated} high-impact memor${demonstrated === 1 ? "y" : "ies"} (applied/prevented) \xB7 ${inv.active} active polic${inv.active === 1 ? "y" : "ies"} surfaceable`
|
|
17770
|
+
);
|
|
17771
|
+
console.log(
|
|
17772
|
+
ui.dim(
|
|
17773
|
+
" Cost is real: the briefing adds context to every task; the payoff is downstream (defects/incidents avoided), not the agent's token bill."
|
|
17774
|
+
)
|
|
17775
|
+
);
|
|
17776
|
+
console.log();
|
|
17668
17777
|
console.log(ui.bold("Prevention") + ui.dim(" (caught-for-you outcome)"));
|
|
17669
17778
|
console.log(
|
|
17670
17779
|
` ${prevention.trend.last_30d} catch${prevention.trend.last_30d === 1 ? "" : "es"} in 30d \xB7 ${prevention.recurrence.recurring_count} recurrence${prevention.recurrence.recurring_count === 1 ? "" : "s"} to review \xB7 ${prevention.trend.last_7d} in 7d`
|
|
@@ -17749,19 +17858,19 @@ function warnNum(n) {
|
|
|
17749
17858
|
}
|
|
17750
17859
|
|
|
17751
17860
|
// src/commands/dev-link.ts
|
|
17752
|
-
import { execFile as
|
|
17861
|
+
import { execFile as execFile5 } from "child_process";
|
|
17753
17862
|
import { cp, readFile as readFile27 } from "fs/promises";
|
|
17754
17863
|
import { existsSync as existsSync79 } from "fs";
|
|
17755
|
-
import
|
|
17756
|
-
import { promisify as
|
|
17864
|
+
import path57 from "path";
|
|
17865
|
+
import { promisify as promisify5 } from "util";
|
|
17757
17866
|
import "commander";
|
|
17758
17867
|
import { findProjectRoot as findProjectRoot56 } from "@hiveai/core";
|
|
17759
|
-
var exec3 =
|
|
17868
|
+
var exec3 = promisify5(execFile5);
|
|
17760
17869
|
function registerDevLink(program2) {
|
|
17761
17870
|
const dev = program2.commands.find((c) => c.name() === "dev") ?? program2.command("dev").description("Developer utilities for working on hAIve itself.");
|
|
17762
17871
|
dev.command("link").description("Hot-swap this repo's built dist into the global @hiveai install so `haive` 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) => {
|
|
17763
17872
|
const root = findProjectRoot56(opts.dir);
|
|
17764
|
-
if (!existsSync79(
|
|
17873
|
+
if (!existsSync79(path57.join(root, "packages", "cli", "dist", "index.js"))) {
|
|
17765
17874
|
ui.error(`Not the hAIve monorepo (no packages/cli/dist) at ${root}. Run \`pnpm -r build\` first, or pass --dir.`);
|
|
17766
17875
|
process.exitCode = 1;
|
|
17767
17876
|
return;
|
|
@@ -17770,9 +17879,9 @@ function registerDevLink(program2) {
|
|
|
17770
17879
|
try {
|
|
17771
17880
|
globalModules = (await exec3("npm", ["root", "-g"])).stdout.trim();
|
|
17772
17881
|
} catch {
|
|
17773
|
-
globalModules =
|
|
17882
|
+
globalModules = path57.join(path57.dirname(path57.dirname(process.execPath)), "lib", "node_modules");
|
|
17774
17883
|
}
|
|
17775
|
-
const globalHive =
|
|
17884
|
+
const globalHive = path57.join(globalModules, "@hiveai");
|
|
17776
17885
|
if (!existsSync79(globalHive)) {
|
|
17777
17886
|
ui.error(`No global @hiveai install at ${globalHive}. Install once with \`npm i -g @hiveai/cli\`, then re-run.`);
|
|
17778
17887
|
process.exitCode = 1;
|
|
@@ -17780,21 +17889,21 @@ function registerDevLink(program2) {
|
|
|
17780
17889
|
}
|
|
17781
17890
|
const linked = [];
|
|
17782
17891
|
const copyDist = async (fromPkg, toDistDir) => {
|
|
17783
|
-
const from =
|
|
17784
|
-
if (!existsSync79(from) || !existsSync79(
|
|
17892
|
+
const from = path57.join(root, "packages", fromPkg, "dist");
|
|
17893
|
+
if (!existsSync79(from) || !existsSync79(path57.dirname(toDistDir))) return;
|
|
17785
17894
|
await cp(from, toDistDir, { recursive: true });
|
|
17786
|
-
linked.push(
|
|
17895
|
+
linked.push(path57.relative(globalModules, toDistDir));
|
|
17787
17896
|
};
|
|
17788
17897
|
for (const pkg of ["cli", "mcp"]) {
|
|
17789
|
-
await copyDist(pkg,
|
|
17898
|
+
await copyDist(pkg, path57.join(globalHive, pkg, "dist"));
|
|
17790
17899
|
for (const nested of ["core", "embeddings"]) {
|
|
17791
|
-
await copyDist(nested,
|
|
17900
|
+
await copyDist(nested, path57.join(globalHive, pkg, "node_modules", "@hiveai", nested, "dist"));
|
|
17792
17901
|
}
|
|
17793
17902
|
}
|
|
17794
|
-
await copyDist("core",
|
|
17903
|
+
await copyDist("core", path57.join(globalHive, "core", "dist"));
|
|
17795
17904
|
let version = "unknown";
|
|
17796
17905
|
try {
|
|
17797
|
-
version = JSON.parse(await readFile27(
|
|
17906
|
+
version = JSON.parse(await readFile27(path57.join(root, "package.json"), "utf8")).version ?? "unknown";
|
|
17798
17907
|
} catch {
|
|
17799
17908
|
}
|
|
17800
17909
|
if (opts.json) {
|
|
@@ -17812,8 +17921,41 @@ function registerDevLink(program2) {
|
|
|
17812
17921
|
}
|
|
17813
17922
|
|
|
17814
17923
|
// src/commands/coverage.ts
|
|
17924
|
+
import { readFile as readFile28 } from "fs/promises";
|
|
17925
|
+
import { existsSync as existsSync80 } from "fs";
|
|
17926
|
+
import path58 from "path";
|
|
17815
17927
|
import "commander";
|
|
17816
|
-
import {
|
|
17928
|
+
import {
|
|
17929
|
+
findCoverageGaps,
|
|
17930
|
+
findProjectRoot as findProjectRoot57,
|
|
17931
|
+
mergeHotFiles,
|
|
17932
|
+
resolveHaivePaths as resolveHaivePaths52,
|
|
17933
|
+
tallyHotFiles
|
|
17934
|
+
} from "@hiveai/core";
|
|
17935
|
+
async function readAgentHotFiles(root, cacheFile, sinceMs) {
|
|
17936
|
+
if (!existsSync80(cacheFile)) return [];
|
|
17937
|
+
const raw = await readFile28(cacheFile, "utf8").catch(() => "");
|
|
17938
|
+
const files = [];
|
|
17939
|
+
for (const line of raw.split("\n")) {
|
|
17940
|
+
const trimmed = line.trim();
|
|
17941
|
+
if (!trimmed) continue;
|
|
17942
|
+
try {
|
|
17943
|
+
const obs = JSON.parse(trimmed);
|
|
17944
|
+
if (sinceMs > 0 && obs.ts) {
|
|
17945
|
+
const t = Date.parse(obs.ts);
|
|
17946
|
+
if (Number.isFinite(t) && t < sinceMs) continue;
|
|
17947
|
+
}
|
|
17948
|
+
for (const f of obs.files ?? []) {
|
|
17949
|
+
if (typeof f !== "string" || !f) continue;
|
|
17950
|
+
const rel = path58.isAbsolute(f) ? path58.relative(root, f) : f;
|
|
17951
|
+
if (rel.startsWith("..")) continue;
|
|
17952
|
+
files.push(rel);
|
|
17953
|
+
}
|
|
17954
|
+
} catch {
|
|
17955
|
+
}
|
|
17956
|
+
}
|
|
17957
|
+
return tallyHotFiles(files, "agent");
|
|
17958
|
+
}
|
|
17817
17959
|
function isNoisePath(p) {
|
|
17818
17960
|
if (/(^|\/)(node_modules|dist|build|coverage|\.next)\//.test(p)) return true;
|
|
17819
17961
|
if (p.startsWith(".ai/")) return true;
|
|
@@ -17825,29 +17967,47 @@ function isNoisePath(p) {
|
|
|
17825
17967
|
function registerCoverage(program2) {
|
|
17826
17968
|
program2.command("coverage").description(
|
|
17827
17969
|
"Coverage-gap report: frequently-edited files with no covering team memory (blind spots)."
|
|
17828
|
-
).option("--json", "emit JSON", false).option("--min-changes <n>", "minimum
|
|
17970
|
+
).option("--json", "emit JSON", false).option("--min-changes <n>", "minimum churn count to flag a file", "3").option("--limit <n>", "max gaps to report", "20").option("--days <n>", "lookback window in days (git history + agent edits)", "90").option("--source <which>", "heat source: git | agent | both", "both").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
17829
17971
|
const root = findProjectRoot57(opts.dir);
|
|
17830
17972
|
const paths = resolveHaivePaths52(root);
|
|
17831
17973
|
const minChanges = Math.max(1, parseInt(opts.minChanges ?? "3", 10) || 3);
|
|
17832
17974
|
const limit = Math.max(1, parseInt(opts.limit ?? "20", 10) || 20);
|
|
17833
17975
|
const days = Math.max(1, parseInt(opts.days ?? "90", 10) || 90);
|
|
17834
|
-
const
|
|
17976
|
+
const source = (opts.source ?? "both").toLowerCase();
|
|
17977
|
+
if (!["git", "agent", "both"].includes(source)) {
|
|
17978
|
+
ui.error("--source must be one of: git | agent | both");
|
|
17979
|
+
process.exitCode = 1;
|
|
17980
|
+
return;
|
|
17981
|
+
}
|
|
17982
|
+
const useGit = source === "git" || source === "both";
|
|
17983
|
+
const useAgent = source === "agent" || source === "both";
|
|
17984
|
+
const radar = useGit ? await buildRadar({
|
|
17835
17985
|
root,
|
|
17836
17986
|
taskTokens: null,
|
|
17837
17987
|
filePaths: [],
|
|
17838
17988
|
daysBack: Math.ceil(days / 6),
|
|
17839
17989
|
// getHotFiles multiplies daysBack by 6
|
|
17840
17990
|
maxHotFiles: 500
|
|
17841
|
-
});
|
|
17842
|
-
const
|
|
17991
|
+
}) : null;
|
|
17992
|
+
const gitHotFiles = (radar?.hotFiles ?? []).filter((h) => !isNoisePath(h.path)).map((h) => ({ path: h.path, changes: h.changes, source: "git" }));
|
|
17993
|
+
const sinceMs = Date.now() - days * 864e5;
|
|
17994
|
+
const agentHotFiles = useAgent ? (await readAgentHotFiles(root, path58.join(paths.haiveDir, ".cache", "observations.jsonl"), sinceMs)).filter((h) => !isNoisePath(h.path)) : [];
|
|
17995
|
+
const hotFiles = mergeHotFiles(gitHotFiles, agentHotFiles);
|
|
17843
17996
|
const memories = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
17844
17997
|
const gaps = findCoverageGaps(hotFiles, memories, { minChanges, limit });
|
|
17845
17998
|
if (opts.json) {
|
|
17846
|
-
console.log(JSON.stringify({
|
|
17999
|
+
console.log(JSON.stringify({
|
|
18000
|
+
root,
|
|
18001
|
+
source,
|
|
18002
|
+
scanned_hot_files: hotFiles.length,
|
|
18003
|
+
git_hot_files: gitHotFiles.length,
|
|
18004
|
+
agent_hot_files: agentHotFiles.length,
|
|
18005
|
+
gaps
|
|
18006
|
+
}, null, 2));
|
|
17847
18007
|
return;
|
|
17848
18008
|
}
|
|
17849
|
-
if (!radar.insideGitRepo) {
|
|
17850
|
-
ui.warn("Not a git repository
|
|
18009
|
+
if (useGit && radar && !radar.insideGitRepo && agentHotFiles.length === 0) {
|
|
18010
|
+
ui.warn("Not a git repository and no agent-edit history \u2014 nothing to cross-check.");
|
|
17851
18011
|
return;
|
|
17852
18012
|
}
|
|
17853
18013
|
if (gaps.length === 0) {
|
|
@@ -17856,7 +18016,8 @@ function registerCoverage(program2) {
|
|
|
17856
18016
|
}
|
|
17857
18017
|
console.log(ui.bold(`hAIve coverage \u2014 ${gaps.length} blind spot(s) (hot files with no covering memory)`));
|
|
17858
18018
|
for (const gap of gaps) {
|
|
17859
|
-
|
|
18019
|
+
const src = gap.source ? ui.dim(` [${gap.source}]`) : "";
|
|
18020
|
+
console.log(` ${ui.yellow("\u25CB")} ${gap.path} ${ui.dim(`(${gap.changes} change${gap.changes === 1 ? "" : "s"})`)}${src}`);
|
|
17860
18021
|
}
|
|
17861
18022
|
console.log(
|
|
17862
18023
|
ui.dim(
|
|
@@ -17868,8 +18029,8 @@ function registerCoverage(program2) {
|
|
|
17868
18029
|
|
|
17869
18030
|
// src/commands/merge-driver.ts
|
|
17870
18031
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
17871
|
-
import { readFileSync, writeFileSync, existsSync as
|
|
17872
|
-
import
|
|
18032
|
+
import { readFileSync, writeFileSync, existsSync as existsSync81 } from "fs";
|
|
18033
|
+
import path59 from "path";
|
|
17873
18034
|
import "commander";
|
|
17874
18035
|
import { findProjectRoot as findProjectRoot58, mergeMemoryVersions } from "@hiveai/core";
|
|
17875
18036
|
var GITATTRIBUTES_MARK = "# hAIve merge driver";
|
|
@@ -17901,8 +18062,8 @@ function registerMergeDriver(program2) {
|
|
|
17901
18062
|
process.exitCode = 1;
|
|
17902
18063
|
return;
|
|
17903
18064
|
}
|
|
17904
|
-
const gaPath =
|
|
17905
|
-
let content =
|
|
18065
|
+
const gaPath = path59.join(root, ".gitattributes");
|
|
18066
|
+
let content = existsSync81(gaPath) ? readFileSync(gaPath, "utf8") : "";
|
|
17906
18067
|
if (!content.includes(GITATTRIBUTES_MARK)) {
|
|
17907
18068
|
if (content.length > 0 && !content.endsWith("\n")) content += "\n";
|
|
17908
18069
|
content += GITATTRIBUTES_BLOCK + "\n";
|
|
@@ -17917,11 +18078,12 @@ function registerMergeDriver(program2) {
|
|
|
17917
18078
|
|
|
17918
18079
|
// src/commands/memory-resolve-conflict.ts
|
|
17919
18080
|
import { writeFile as writeFile40 } from "fs/promises";
|
|
17920
|
-
import { existsSync as
|
|
18081
|
+
import { existsSync as existsSync83 } from "fs";
|
|
17921
18082
|
import "commander";
|
|
17922
18083
|
import {
|
|
18084
|
+
applyConflictResolution,
|
|
17923
18085
|
findProjectRoot as findProjectRoot59,
|
|
17924
|
-
planConflictResolution,
|
|
18086
|
+
planConflictResolution as planConflictResolution2,
|
|
17925
18087
|
resolveHaivePaths as resolveHaivePaths53,
|
|
17926
18088
|
serializeMemory as serializeMemory31
|
|
17927
18089
|
} from "@hiveai/core";
|
|
@@ -17929,7 +18091,7 @@ function registerMemoryResolveConflict(memory2) {
|
|
|
17929
18091
|
memory2.command("resolve-conflict <id_a> <id_b>").description("Resolve a contradiction: keep the stronger memory, deprecate (supersede) the other").option("--yes", "apply the resolution (without this, only previews it)", false).option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (idA, idB, opts) => {
|
|
17930
18092
|
const root = findProjectRoot59(opts.dir);
|
|
17931
18093
|
const paths = resolveHaivePaths53(root);
|
|
17932
|
-
if (!
|
|
18094
|
+
if (!existsSync83(paths.memoriesDir)) {
|
|
17933
18095
|
ui.error(`No .ai/memories at ${root}.`);
|
|
17934
18096
|
process.exitCode = 1;
|
|
17935
18097
|
return;
|
|
@@ -17942,43 +18104,53 @@ function registerMemoryResolveConflict(memory2) {
|
|
|
17942
18104
|
process.exitCode = 1;
|
|
17943
18105
|
return;
|
|
17944
18106
|
}
|
|
17945
|
-
const plan =
|
|
18107
|
+
const plan = planConflictResolution2(a, b);
|
|
18108
|
+
const winner = plan.keep_id === idA ? a : b;
|
|
17946
18109
|
const loser = plan.supersede_id === idA ? a : b;
|
|
18110
|
+
const applied = applyConflictResolution(winner, loser, plan);
|
|
17947
18111
|
if (opts.json) {
|
|
17948
|
-
console.log(JSON.stringify({
|
|
18112
|
+
console.log(JSON.stringify({
|
|
18113
|
+
...plan,
|
|
18114
|
+
winner_revision_count: applied.winner.revision_count,
|
|
18115
|
+
topic: applied.topic,
|
|
18116
|
+
topic_adopted: applied.topic_adopted,
|
|
18117
|
+
applied: Boolean(opts.yes)
|
|
18118
|
+
}, null, 2));
|
|
17949
18119
|
} else {
|
|
17950
18120
|
console.log(ui.bold("Conflict resolution"));
|
|
17951
|
-
console.log(` keep: ${ui.green(plan.keep_id)}`);
|
|
18121
|
+
console.log(` keep: ${ui.green(plan.keep_id)} ${ui.dim(`(rev ${winner.memory.frontmatter.revision_count}\u2192${applied.winner.revision_count})`)}`);
|
|
17952
18122
|
console.log(` supersede: ${ui.red(plan.supersede_id)} ${ui.dim(`\u2192 deprecated`)}`);
|
|
17953
18123
|
console.log(` reason: ${plan.reason}`);
|
|
18124
|
+
if (applied.topic) {
|
|
18125
|
+
console.log(` topic: ${applied.topic}${applied.topic_adopted ? ui.dim(" (adopted from superseded \u2014 future captures upsert into the winner)") : ""}`);
|
|
18126
|
+
}
|
|
17954
18127
|
}
|
|
17955
18128
|
if (!opts.yes) {
|
|
17956
18129
|
if (!opts.json) ui.info("Preview only \u2014 re-run with --yes to apply.");
|
|
17957
18130
|
return;
|
|
17958
18131
|
}
|
|
18132
|
+
await writeFile40(
|
|
18133
|
+
winner.filePath,
|
|
18134
|
+
serializeMemory31({ frontmatter: applied.winner, body: winner.memory.body }),
|
|
18135
|
+
"utf8"
|
|
18136
|
+
);
|
|
17959
18137
|
await writeFile40(
|
|
17960
18138
|
loser.filePath,
|
|
17961
|
-
serializeMemory31({
|
|
17962
|
-
frontmatter: {
|
|
17963
|
-
...loser.memory.frontmatter,
|
|
17964
|
-
status: "deprecated",
|
|
17965
|
-
stale_reason: plan.stale_reason,
|
|
17966
|
-
related_ids: [.../* @__PURE__ */ new Set([...loser.memory.frontmatter.related_ids, plan.keep_id])]
|
|
17967
|
-
},
|
|
17968
|
-
body: loser.memory.body
|
|
17969
|
-
}),
|
|
18139
|
+
serializeMemory31({ frontmatter: applied.loser, body: loser.memory.body }),
|
|
17970
18140
|
"utf8"
|
|
17971
18141
|
);
|
|
17972
|
-
if (!opts.json)
|
|
18142
|
+
if (!opts.json) {
|
|
18143
|
+
ui.success(`Deprecated ${plan.supersede_id}; promoted ${plan.keep_id} (rev ${applied.winner.revision_count}${applied.topic ? `, topic=${applied.topic}` : ""}).`);
|
|
18144
|
+
}
|
|
17973
18145
|
});
|
|
17974
18146
|
}
|
|
17975
18147
|
|
|
17976
18148
|
// src/commands/memory-seed-git.ts
|
|
17977
|
-
import { execFile as
|
|
18149
|
+
import { execFile as execFile6 } from "child_process";
|
|
17978
18150
|
import { mkdir as mkdir25, writeFile as writeFile41 } from "fs/promises";
|
|
17979
|
-
import { existsSync as
|
|
17980
|
-
import
|
|
17981
|
-
import { promisify as
|
|
18151
|
+
import { existsSync as existsSync84 } from "fs";
|
|
18152
|
+
import path60 from "path";
|
|
18153
|
+
import { promisify as promisify6 } from "util";
|
|
17982
18154
|
import "commander";
|
|
17983
18155
|
import {
|
|
17984
18156
|
buildFrontmatter as buildFrontmatter12,
|
|
@@ -17988,12 +18160,12 @@ import {
|
|
|
17988
18160
|
resolveHaivePaths as resolveHaivePaths54,
|
|
17989
18161
|
serializeMemory as serializeMemory33
|
|
17990
18162
|
} from "@hiveai/core";
|
|
17991
|
-
var exec4 =
|
|
18163
|
+
var exec4 = promisify6(execFile6);
|
|
17992
18164
|
function registerMemorySeedGit(memory2) {
|
|
17993
18165
|
memory2.command("seed-git").description("Propose draft `attempt` seeds from revert/hotfix commits in git history (cold-start)").option("--apply", "write the proposed seeds as draft memories (default: preview only)", false).option("--limit <n>", "max seeds to propose", "20").option("--days <n>", "git-history lookback window in days", "365").option("--scope <scope>", "personal | team", "team").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
17994
18166
|
const root = findProjectRoot60(opts.dir);
|
|
17995
18167
|
const paths = resolveHaivePaths54(root);
|
|
17996
|
-
if (!
|
|
18168
|
+
if (!existsSync84(paths.haiveDir)) {
|
|
17997
18169
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
17998
18170
|
process.exitCode = 1;
|
|
17999
18171
|
return;
|
|
@@ -18038,8 +18210,8 @@ function registerMemorySeedGit(memory2) {
|
|
|
18038
18210
|
_Seeded from git ${p.kind} commit ${p.source_sha}. Review and validate (or delete) \u2014 not yet authoritative._
|
|
18039
18211
|
`;
|
|
18040
18212
|
const file = memoryFilePath13(paths, fm.scope, fm.id, fm.module);
|
|
18041
|
-
if (
|
|
18042
|
-
await mkdir25(
|
|
18213
|
+
if (existsSync84(file)) continue;
|
|
18214
|
+
await mkdir25(path60.dirname(file), { recursive: true });
|
|
18043
18215
|
await writeFile41(file, serializeMemory33({ frontmatter: fm, body }), "utf8");
|
|
18044
18216
|
written += 1;
|
|
18045
18217
|
}
|
|
@@ -18071,8 +18243,8 @@ async function readCommits(root, days) {
|
|
|
18071
18243
|
}
|
|
18072
18244
|
|
|
18073
18245
|
// src/commands/bridges.ts
|
|
18074
|
-
import { existsSync as
|
|
18075
|
-
import
|
|
18246
|
+
import { existsSync as existsSync85 } from "fs";
|
|
18247
|
+
import path61 from "path";
|
|
18076
18248
|
import "commander";
|
|
18077
18249
|
import {
|
|
18078
18250
|
findProjectRoot as findProjectRoot61,
|
|
@@ -18093,7 +18265,7 @@ function registerBridges(program2) {
|
|
|
18093
18265
|
const root = findProjectRoot61(opts.dir);
|
|
18094
18266
|
const paths = resolveHaivePaths55(root);
|
|
18095
18267
|
const dryRun = opts.dryRun === true;
|
|
18096
|
-
if (!
|
|
18268
|
+
if (!existsSync85(paths.memoriesDir)) {
|
|
18097
18269
|
ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
18098
18270
|
process.exitCode = 1;
|
|
18099
18271
|
return;
|
|
@@ -18112,7 +18284,7 @@ function registerBridges(program2) {
|
|
|
18112
18284
|
targets = BRIDGE_TARGETS3;
|
|
18113
18285
|
} else {
|
|
18114
18286
|
targets = BRIDGE_TARGETS3.filter(
|
|
18115
|
-
(t) =>
|
|
18287
|
+
(t) => existsSync85(path61.join(root, BRIDGE_TARGET_PATH3[t]))
|
|
18116
18288
|
);
|
|
18117
18289
|
if (targets.length === 0) {
|
|
18118
18290
|
ui.info(
|
|
@@ -18141,7 +18313,7 @@ function registerBridges(program2) {
|
|
|
18141
18313
|
console.log(ui.bold("hAIve bridge targets:"));
|
|
18142
18314
|
for (const target of BRIDGE_TARGETS3) {
|
|
18143
18315
|
const relPath = BRIDGE_TARGET_PATH3[target];
|
|
18144
|
-
const exists =
|
|
18316
|
+
const exists = existsSync85(path61.join(root, relPath));
|
|
18145
18317
|
const marker = exists ? ui.dim("\u2713") : ui.dim("\xB7");
|
|
18146
18318
|
console.log(` ${marker} ${target.padEnd(10)} ${relPath}${exists ? "" : " (not present)"}`);
|
|
18147
18319
|
}
|
|
@@ -18152,7 +18324,7 @@ function registerBridges(program2) {
|
|
|
18152
18324
|
|
|
18153
18325
|
// src/index.ts
|
|
18154
18326
|
var program = new Command64();
|
|
18155
|
-
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.
|
|
18327
|
+
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.24.0").option("--advanced", "show maintenance and experimental commands in help");
|
|
18156
18328
|
registerInit(program);
|
|
18157
18329
|
registerWelcome(program);
|
|
18158
18330
|
registerResolveProject(program);
|