@hiveai/cli 0.23.0 → 0.25.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 +396 -340
- 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,10 +2249,11 @@ 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,
|
|
2256
|
+
meetsSeedQualityFloor,
|
|
2252
2257
|
memoryFilePath,
|
|
2253
2258
|
serializeMemory as serializeMemory2,
|
|
2254
2259
|
STACK_PACK_TAG
|
|
@@ -2289,7 +2294,12 @@ app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: t
|
|
|
2289
2294
|
body: `Controllers must never import Prisma/TypeORM directly \u2014 that belongs in Services.
|
|
2290
2295
|
|
|
2291
2296
|
Controller \u2192 Service \u2192 Repository (or direct ORM) is the required layering.
|
|
2292
|
-
Direct ORM usage in controllers makes testing impossible and couples transport to persistence
|
|
2297
|
+
Direct ORM usage in controllers makes testing impossible and couples transport to persistence.`,
|
|
2298
|
+
sensor: {
|
|
2299
|
+
pattern: "@prisma/client|PrismaClient|getRepository\\(|createQueryBuilder\\(",
|
|
2300
|
+
paths: ["**/*.controller.ts"],
|
|
2301
|
+
message: "ORM/Prisma used directly in a controller \u2014 move persistence into a Service (Controller \u2192 Service \u2192 Repository)."
|
|
2302
|
+
}
|
|
2293
2303
|
},
|
|
2294
2304
|
{
|
|
2295
2305
|
slug: "nestjs-exception-filter-for-prisma",
|
|
@@ -2474,7 +2484,11 @@ Routes without schema accept any body and bypass Fastify's fast-json-stringify s
|
|
|
2474
2484
|
|
|
2475
2485
|
Calling $disconnect() after each request wastes the warm connection pool.
|
|
2476
2486
|
Create one PrismaClient per process (module-level singleton), not per request.
|
|
2477
|
-
Disconnecting is only needed when the process is shutting down
|
|
2487
|
+
Disconnecting is only needed when the process is shutting down.`,
|
|
2488
|
+
sensor: {
|
|
2489
|
+
pattern: "\\$disconnect\\(\\)",
|
|
2490
|
+
message: "prisma.$disconnect() per request drains the warm connection pool in serverless \u2014 use a module-level PrismaClient singleton and only disconnect on shutdown."
|
|
2491
|
+
}
|
|
2478
2492
|
},
|
|
2479
2493
|
{
|
|
2480
2494
|
slug: "prisma-migrations-never-modify",
|
|
@@ -2530,7 +2544,11 @@ const store = useStore();
|
|
|
2530
2544
|
const count = useStore((s) => s.count);
|
|
2531
2545
|
\`\`\`
|
|
2532
2546
|
|
|
2533
|
-
Subscribing to the whole store is the single most common Zustand performance mistake
|
|
2547
|
+
Subscribing to the whole store is the single most common Zustand performance mistake.`,
|
|
2548
|
+
sensor: {
|
|
2549
|
+
pattern: "use[A-Z]\\w*Store\\(\\s*\\)",
|
|
2550
|
+
message: "A Zustand store hook called with no selector subscribes to the WHOLE store (re-renders on any change) \u2014 pass a slice selector: useStore(s => s.field)."
|
|
2551
|
+
}
|
|
2534
2552
|
},
|
|
2535
2553
|
{
|
|
2536
2554
|
slug: "zustand-devtools-wrap-dev-only",
|
|
@@ -2751,7 +2769,8 @@ const users = await User.find({});
|
|
|
2751
2769
|
const users = await User.find({}).lean();
|
|
2752
2770
|
\`\`\`
|
|
2753
2771
|
|
|
2754
|
-
|
|
2772
|
+
\`.lean()\` skips hydration into a Mongoose \`Document\`, so getters, virtuals, \`toObject()\` and
|
|
2773
|
+
instance methods are gone. Never use \`.lean()\` when you then call \`.save()\` or instance methods.`
|
|
2755
2774
|
},
|
|
2756
2775
|
{
|
|
2757
2776
|
slug: "mongoose-index-frequently-queried-fields",
|
|
@@ -2970,7 +2989,11 @@ A committed key lets anyone forge sessions and CSRF tokens.`
|
|
|
2970
2989
|
db.execute(f"SELECT * FROM users WHERE id = {uid}")
|
|
2971
2990
|
# \u2705
|
|
2972
2991
|
db.execute("SELECT * FROM users WHERE id = %s", (uid,))
|
|
2973
|
-
|
|
2992
|
+
\`\`\``,
|
|
2993
|
+
sensor: {
|
|
2994
|
+
pattern: `execute\\(\\s*f["']`,
|
|
2995
|
+
message: 'SQL built with an f-string inside execute() \u2014 SQL injection risk; use a parameterized query: execute("\u2026 %s \u2026", (params,)).'
|
|
2996
|
+
}
|
|
2974
2997
|
}
|
|
2975
2998
|
],
|
|
2976
2999
|
vue: [
|
|
@@ -3338,6 +3361,7 @@ async function seedStackPack(haivePaths, stack) {
|
|
|
3338
3361
|
let memCount = 0;
|
|
3339
3362
|
let sensorCount = 0;
|
|
3340
3363
|
for (const mem of memories) {
|
|
3364
|
+
if (!meetsSeedQualityFloor(mem.body, Boolean(mem.sensor))) continue;
|
|
3341
3365
|
const sensor = mem.sensor ? {
|
|
3342
3366
|
kind: "regex",
|
|
3343
3367
|
pattern: mem.sensor.pattern,
|
|
@@ -3374,7 +3398,7 @@ ${mem.body}`;
|
|
|
3374
3398
|
const content = serializeMemory2({ frontmatter: fm, body: `${titledBody}
|
|
3375
3399
|
|
|
3376
3400
|
${SEED_FOOTER(stack)}` });
|
|
3377
|
-
await mkdir5(
|
|
3401
|
+
await mkdir5(path11.dirname(filePath), { recursive: true });
|
|
3378
3402
|
await writeFile6(filePath, content, "utf8");
|
|
3379
3403
|
existingTopics.add(topic);
|
|
3380
3404
|
existingSignatures.add(signature);
|
|
@@ -3386,7 +3410,7 @@ ${SEED_FOOTER(stack)}` });
|
|
|
3386
3410
|
|
|
3387
3411
|
// src/commands/init.ts
|
|
3388
3412
|
var execFileAsync = promisify2(execFile2);
|
|
3389
|
-
var HAIVE_GITHUB_ACTION_REF = `v${"0.
|
|
3413
|
+
var HAIVE_GITHUB_ACTION_REF = `v${"0.25.0"}`;
|
|
3390
3414
|
var PROJECT_CONTEXT_TEMPLATE = `# Project context
|
|
3391
3415
|
|
|
3392
3416
|
> Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
|
|
@@ -3614,7 +3638,7 @@ function registerInit(program2) {
|
|
|
3614
3638
|
"approve user-level AI client configuration prompts during agent setup",
|
|
3615
3639
|
false
|
|
3616
3640
|
).option("--json", "emit a machine-readable summary on stdout (human logs go to stderr)", false).action(async (opts) => {
|
|
3617
|
-
const root =
|
|
3641
|
+
const root = path12.resolve(opts.dir);
|
|
3618
3642
|
const paths = resolveHaivePaths6(root);
|
|
3619
3643
|
const autopilot = opts.manual !== true;
|
|
3620
3644
|
const json = opts.json === true;
|
|
@@ -3641,7 +3665,7 @@ function registerInit(program2) {
|
|
|
3641
3665
|
await mkdir6(paths.moduleDir, { recursive: true });
|
|
3642
3666
|
await mkdir6(paths.modulesContextDir, { recursive: true });
|
|
3643
3667
|
await ensureAiRuntimeLayout(paths.runtimeDir);
|
|
3644
|
-
await ensureAiCacheLayout(
|
|
3668
|
+
await ensureAiCacheLayout(path12.join(paths.haiveDir, ".cache"));
|
|
3645
3669
|
if (!existsSync11(paths.projectContext)) {
|
|
3646
3670
|
if (wantBootstrap) {
|
|
3647
3671
|
ui.info("Bootstrapping project context from local files\u2026");
|
|
@@ -3655,11 +3679,11 @@ function registerInit(program2) {
|
|
|
3655
3679
|
}
|
|
3656
3680
|
} else {
|
|
3657
3681
|
await writeFile7(paths.projectContext, PROJECT_CONTEXT_TEMPLATE, "utf8");
|
|
3658
|
-
ui.success(`Created ${
|
|
3682
|
+
ui.success(`Created ${path12.relative(root, paths.projectContext)}`);
|
|
3659
3683
|
}
|
|
3660
3684
|
}
|
|
3661
3685
|
const configExists = existsSync11(
|
|
3662
|
-
|
|
3686
|
+
path12.join(paths.haiveDir, "haive.config.json")
|
|
3663
3687
|
);
|
|
3664
3688
|
if (!configExists) {
|
|
3665
3689
|
await saveConfig2(paths, autopilot ? AUTOPILOT_DEFAULTS2 : { autopilot: false });
|
|
@@ -3726,13 +3750,13 @@ function registerInit(program2) {
|
|
|
3726
3750
|
}
|
|
3727
3751
|
const wantCi = opts.withCi || autopilot;
|
|
3728
3752
|
if (wantCi) {
|
|
3729
|
-
const ciPath =
|
|
3753
|
+
const ciPath = path12.join(root, ".github", "workflows", "haive-sync.yml");
|
|
3730
3754
|
if (existsSync11(ciPath)) {
|
|
3731
3755
|
ui.info("CI workflow already exists \u2014 skipped");
|
|
3732
3756
|
} else {
|
|
3733
|
-
await mkdir6(
|
|
3757
|
+
await mkdir6(path12.dirname(ciPath), { recursive: true });
|
|
3734
3758
|
await writeFile7(ciPath, CI_WORKFLOW, "utf8");
|
|
3735
|
-
ui.success(`Created ${
|
|
3759
|
+
ui.success(`Created ${path12.relative(root, ciPath)}`);
|
|
3736
3760
|
}
|
|
3737
3761
|
}
|
|
3738
3762
|
if (autopilot) {
|
|
@@ -3772,7 +3796,7 @@ function registerInit(program2) {
|
|
|
3772
3796
|
interactive: process.stdin.isTTY
|
|
3773
3797
|
});
|
|
3774
3798
|
for (const r of agentSetup.project_results) {
|
|
3775
|
-
if (r.status === "configured" && r.path) ui.success(`haive MCP project config written (${
|
|
3799
|
+
if (r.status === "configured" && r.path) ui.success(`haive MCP project config written (${path12.relative(root, r.path)})`);
|
|
3776
3800
|
else if (r.status === "error") ui.warn(`${r.client}: ${r.error}`);
|
|
3777
3801
|
}
|
|
3778
3802
|
for (const r of agentSetup.global_results) {
|
|
@@ -3871,7 +3895,7 @@ async function autoDetectStacksFromRoot(root) {
|
|
|
3871
3895
|
let requirementsTxt;
|
|
3872
3896
|
let goMod;
|
|
3873
3897
|
let pomXml;
|
|
3874
|
-
const pkgPath =
|
|
3898
|
+
const pkgPath = path12.join(root, "package.json");
|
|
3875
3899
|
if (existsSync11(pkgPath)) {
|
|
3876
3900
|
try {
|
|
3877
3901
|
const pkg = JSON.parse(await readFile6(pkgPath, "utf8"));
|
|
@@ -3880,7 +3904,7 @@ async function autoDetectStacksFromRoot(root) {
|
|
|
3880
3904
|
}
|
|
3881
3905
|
}
|
|
3882
3906
|
for (const name of ["requirements.txt", "requirements/base.txt", "requirements/prod.txt"]) {
|
|
3883
|
-
const reqPath =
|
|
3907
|
+
const reqPath = path12.join(root, name);
|
|
3884
3908
|
if (existsSync11(reqPath)) {
|
|
3885
3909
|
try {
|
|
3886
3910
|
requirementsTxt = await readFile6(reqPath, "utf8");
|
|
@@ -3889,14 +3913,14 @@ async function autoDetectStacksFromRoot(root) {
|
|
|
3889
3913
|
}
|
|
3890
3914
|
}
|
|
3891
3915
|
}
|
|
3892
|
-
const goModPath =
|
|
3916
|
+
const goModPath = path12.join(root, "go.mod");
|
|
3893
3917
|
if (existsSync11(goModPath)) {
|
|
3894
3918
|
try {
|
|
3895
3919
|
goMod = await readFile6(goModPath, "utf8");
|
|
3896
3920
|
} catch {
|
|
3897
3921
|
}
|
|
3898
3922
|
}
|
|
3899
|
-
const pomPath =
|
|
3923
|
+
const pomPath = path12.join(root, "pom.xml");
|
|
3900
3924
|
if (existsSync11(pomPath)) {
|
|
3901
3925
|
try {
|
|
3902
3926
|
pomXml = await readFile6(pomPath, "utf8");
|
|
@@ -3904,7 +3928,7 @@ async function autoDetectStacksFromRoot(root) {
|
|
|
3904
3928
|
}
|
|
3905
3929
|
}
|
|
3906
3930
|
let composerJson;
|
|
3907
|
-
const composerPath =
|
|
3931
|
+
const composerPath = path12.join(root, "composer.json");
|
|
3908
3932
|
if (existsSync11(composerPath)) {
|
|
3909
3933
|
try {
|
|
3910
3934
|
composerJson = await readFile6(composerPath, "utf8");
|
|
@@ -3912,16 +3936,16 @@ async function autoDetectStacksFromRoot(root) {
|
|
|
3912
3936
|
}
|
|
3913
3937
|
}
|
|
3914
3938
|
let gemfile;
|
|
3915
|
-
const gemfilePath =
|
|
3939
|
+
const gemfilePath = path12.join(root, "Gemfile");
|
|
3916
3940
|
if (existsSync11(gemfilePath)) {
|
|
3917
3941
|
try {
|
|
3918
3942
|
gemfile = await readFile6(gemfilePath, "utf8");
|
|
3919
3943
|
} catch {
|
|
3920
3944
|
}
|
|
3921
3945
|
}
|
|
3922
|
-
const hasDockerfile = existsSync11(
|
|
3923
|
-
const hasTurboJson = existsSync11(
|
|
3924
|
-
const hasNxJson = existsSync11(
|
|
3946
|
+
const hasDockerfile = existsSync11(path12.join(root, "Dockerfile"));
|
|
3947
|
+
const hasTurboJson = existsSync11(path12.join(root, "turbo.json"));
|
|
3948
|
+
const hasNxJson = existsSync11(path12.join(root, "nx.json"));
|
|
3925
3949
|
let hasCsproj = false;
|
|
3926
3950
|
try {
|
|
3927
3951
|
const entries = await readdir2(root);
|
|
@@ -3974,7 +3998,7 @@ _Seeded from git ${p.kind} commit ${p.source_sha}. Review and validate (or delet
|
|
|
3974
3998
|
`;
|
|
3975
3999
|
const file = memoryFilePath2(paths, fm.scope, fm.id, fm.module);
|
|
3976
4000
|
if (existsSync11(file)) continue;
|
|
3977
|
-
await mkdir6(
|
|
4001
|
+
await mkdir6(path12.dirname(file), { recursive: true });
|
|
3978
4002
|
await writeFile7(file, serializeMemory3({ frontmatter: fm, body }), "utf8");
|
|
3979
4003
|
written++;
|
|
3980
4004
|
}
|
|
@@ -4036,12 +4060,12 @@ function printInitReport(r) {
|
|
|
4036
4060
|
}
|
|
4037
4061
|
async function writeCursorHaiveRule(root) {
|
|
4038
4062
|
const relPath = ".cursor/rules/haive-mcp-required.mdc";
|
|
4039
|
-
const target =
|
|
4063
|
+
const target = path12.join(root, relPath);
|
|
4040
4064
|
if (existsSync11(target)) {
|
|
4041
4065
|
ui.info(`Cursor rule ${relPath} already exists \u2014 skipped`);
|
|
4042
4066
|
return;
|
|
4043
4067
|
}
|
|
4044
|
-
await mkdir6(
|
|
4068
|
+
await mkdir6(path12.dirname(target), { recursive: true });
|
|
4045
4069
|
await writeFile7(target, CURSOR_HAIVE_RULE_MDC, "utf8");
|
|
4046
4070
|
ui.success(`Created Cursor rule ${relPath}`);
|
|
4047
4071
|
}
|
|
@@ -4070,25 +4094,25 @@ var RUNTIME_GITIGNORE_BODY = `*
|
|
|
4070
4094
|
`;
|
|
4071
4095
|
async function ensureAiRuntimeLayout(runtimeDir) {
|
|
4072
4096
|
await mkdir6(runtimeDir, { recursive: true });
|
|
4073
|
-
const gi =
|
|
4097
|
+
const gi = path12.join(runtimeDir, ".gitignore");
|
|
4074
4098
|
if (!existsSync11(gi)) {
|
|
4075
4099
|
await writeFile7(gi, RUNTIME_GITIGNORE_BODY, "utf8");
|
|
4076
4100
|
}
|
|
4077
|
-
const readme =
|
|
4101
|
+
const readme = path12.join(runtimeDir, "README.md");
|
|
4078
4102
|
if (!existsSync11(readme)) {
|
|
4079
4103
|
await writeFile7(readme, RUNTIME_README_BODY, "utf8");
|
|
4080
4104
|
}
|
|
4081
4105
|
}
|
|
4082
4106
|
async function ensureAiCacheLayout(cacheDir) {
|
|
4083
4107
|
await mkdir6(cacheDir, { recursive: true });
|
|
4084
|
-
const gi =
|
|
4108
|
+
const gi = path12.join(cacheDir, ".gitignore");
|
|
4085
4109
|
if (!existsSync11(gi)) {
|
|
4086
4110
|
await writeFile7(gi, "*\n!.gitignore\n", "utf8");
|
|
4087
4111
|
}
|
|
4088
4112
|
}
|
|
4089
4113
|
async function ensureGitignoreEntries(root, patterns) {
|
|
4090
4114
|
try {
|
|
4091
|
-
const gitignorePath =
|
|
4115
|
+
const gitignorePath = path12.join(root, ".gitignore");
|
|
4092
4116
|
let existing = "";
|
|
4093
4117
|
if (existsSync11(gitignorePath)) {
|
|
4094
4118
|
existing = await readFile6(gitignorePath, "utf8");
|
|
@@ -4105,14 +4129,14 @@ async function ensureGitignoreEntries(root, patterns) {
|
|
|
4105
4129
|
// src/commands/install-hooks.ts
|
|
4106
4130
|
import { mkdir as mkdir8, writeFile as writeFile9, chmod, readFile as readFile8 } from "fs/promises";
|
|
4107
4131
|
import { existsSync as existsSync13 } from "fs";
|
|
4108
|
-
import
|
|
4132
|
+
import path14 from "path";
|
|
4109
4133
|
import "commander";
|
|
4110
4134
|
import { findProjectRoot as findProjectRoot8 } from "@hiveai/core";
|
|
4111
4135
|
|
|
4112
4136
|
// src/utils/claude-hooks.ts
|
|
4113
4137
|
import { existsSync as existsSync12 } from "fs";
|
|
4114
4138
|
import { mkdir as mkdir7, readFile as readFile7, writeFile as writeFile8 } from "fs/promises";
|
|
4115
|
-
import
|
|
4139
|
+
import path13 from "path";
|
|
4116
4140
|
var HAIVE_HOOK_TAG = "haive-enforcement";
|
|
4117
4141
|
var POST_TOOL_USE_GROUP = {
|
|
4118
4142
|
matcher: "Edit|Write|Bash",
|
|
@@ -4208,7 +4232,7 @@ async function installClaudeHooksAtPath(settingsPath) {
|
|
|
4208
4232
|
created = true;
|
|
4209
4233
|
}
|
|
4210
4234
|
const patched = patchClaudeSettings(raw);
|
|
4211
|
-
await mkdir7(
|
|
4235
|
+
await mkdir7(path13.dirname(settingsPath), { recursive: true });
|
|
4212
4236
|
await writeFile8(settingsPath, JSON.stringify(patched, null, 2) + "\n", "utf8");
|
|
4213
4237
|
return { settingsPath, created };
|
|
4214
4238
|
}
|
|
@@ -4224,9 +4248,9 @@ async function uninstallClaudeHooksAtPath(settingsPath) {
|
|
|
4224
4248
|
function defaultClaudeSettingsPath(scope, projectRoot) {
|
|
4225
4249
|
if (scope === "user") {
|
|
4226
4250
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
4227
|
-
return
|
|
4251
|
+
return path13.join(home, ".claude", "settings.json");
|
|
4228
4252
|
}
|
|
4229
|
-
return
|
|
4253
|
+
return path13.join(projectRoot, ".claude", "settings.local.json");
|
|
4230
4254
|
}
|
|
4231
4255
|
|
|
4232
4256
|
// src/commands/install-hooks.ts
|
|
@@ -4283,18 +4307,18 @@ fi
|
|
|
4283
4307
|
];
|
|
4284
4308
|
async function installGitHooks(opts) {
|
|
4285
4309
|
const root = findProjectRoot8(opts.dir);
|
|
4286
|
-
const gitDir =
|
|
4310
|
+
const gitDir = path14.join(root, ".git");
|
|
4287
4311
|
if (!existsSync13(gitDir)) {
|
|
4288
4312
|
ui.error(`No .git directory at ${root}.`);
|
|
4289
4313
|
process.exitCode = 1;
|
|
4290
4314
|
return;
|
|
4291
4315
|
}
|
|
4292
|
-
const hooksDir =
|
|
4316
|
+
const hooksDir = path14.join(gitDir, "hooks");
|
|
4293
4317
|
await mkdir8(hooksDir, { recursive: true });
|
|
4294
4318
|
let installed = 0;
|
|
4295
4319
|
let skipped = 0;
|
|
4296
4320
|
for (const { name, body } of HOOKS) {
|
|
4297
|
-
const file =
|
|
4321
|
+
const file = path14.join(hooksDir, name);
|
|
4298
4322
|
if (existsSync13(file) && !opts.force) {
|
|
4299
4323
|
const existing = await readFile8(file, "utf8");
|
|
4300
4324
|
if (!existing.includes(HOOK_MARKER)) {
|
|
@@ -4360,7 +4384,7 @@ function registerInstallHooks(program2) {
|
|
|
4360
4384
|
// src/commands/observe.ts
|
|
4361
4385
|
import { appendFile, mkdir as mkdir9 } from "fs/promises";
|
|
4362
4386
|
import { existsSync as existsSync14 } from "fs";
|
|
4363
|
-
import
|
|
4387
|
+
import path15 from "path";
|
|
4364
4388
|
import "commander";
|
|
4365
4389
|
import { findProjectRoot as findProjectRoot9, resolveHaivePaths as resolveHaivePaths7 } from "@hiveai/core";
|
|
4366
4390
|
var MAX_STDIN_BYTES = 256 * 1024;
|
|
@@ -4478,10 +4502,10 @@ function registerObserve(program2) {
|
|
|
4478
4502
|
files: extractFiles(payload),
|
|
4479
4503
|
...failureHint ? { failure_hint: true } : {}
|
|
4480
4504
|
};
|
|
4481
|
-
const cacheDir =
|
|
4505
|
+
const cacheDir = path15.join(paths.haiveDir, ".cache");
|
|
4482
4506
|
await mkdir9(cacheDir, { recursive: true });
|
|
4483
4507
|
await appendFile(
|
|
4484
|
-
|
|
4508
|
+
path15.join(cacheDir, "observations.jsonl"),
|
|
4485
4509
|
JSON.stringify(observation) + "\n",
|
|
4486
4510
|
"utf8"
|
|
4487
4511
|
);
|
|
@@ -4500,7 +4524,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
4500
4524
|
import { findProjectRoot as findProjectRoot10, resolveHaivePaths as resolveHaivePaths8 } from "@hiveai/core";
|
|
4501
4525
|
import { mkdir as mkdir10, writeFile as writeFile10 } from "fs/promises";
|
|
4502
4526
|
import { existsSync as existsSync15 } from "fs";
|
|
4503
|
-
import
|
|
4527
|
+
import path16 from "path";
|
|
4504
4528
|
import { z } from "zod";
|
|
4505
4529
|
import { readFile as readFile9, readdir as readdir3 } from "fs/promises";
|
|
4506
4530
|
import { existsSync as existsSync22 } from "fs";
|
|
@@ -4670,6 +4694,7 @@ import path82 from "path";
|
|
|
4670
4694
|
import { execSync } from "child_process";
|
|
4671
4695
|
import { readFile as readFile52, writeFile as writeFile13 } from "fs/promises";
|
|
4672
4696
|
import { existsSync as existsSync21 } from "fs";
|
|
4697
|
+
import path112 from "path";
|
|
4673
4698
|
import {
|
|
4674
4699
|
allocateBudget,
|
|
4675
4700
|
briefingProofLine,
|
|
@@ -4729,7 +4754,7 @@ import { z as z23 } from "zod";
|
|
|
4729
4754
|
import { z as z24 } from "zod";
|
|
4730
4755
|
import { existsSync as existsSync24 } from "fs";
|
|
4731
4756
|
import { spawn } from "child_process";
|
|
4732
|
-
import
|
|
4757
|
+
import path122 from "path";
|
|
4733
4758
|
import {
|
|
4734
4759
|
deriveConfidence as deriveConfidence5,
|
|
4735
4760
|
getUsage as getUsage7,
|
|
@@ -4788,7 +4813,7 @@ import { z as z29 } from "zod";
|
|
|
4788
4813
|
import { z as z30 } from "zod";
|
|
4789
4814
|
import { mkdir as mkdir82, writeFile as writeFile14 } from "fs/promises";
|
|
4790
4815
|
import { existsSync as existsSync29 } from "fs";
|
|
4791
|
-
import
|
|
4816
|
+
import path132 from "path";
|
|
4792
4817
|
import { execSync as execSync2 } from "child_process";
|
|
4793
4818
|
import {
|
|
4794
4819
|
buildFrontmatter as buildFrontmatter5,
|
|
@@ -4834,14 +4859,14 @@ var BootstrapProjectSaveInputSchema = {
|
|
|
4834
4859
|
overwrite: z.boolean().default(false).describe("Overwrite an existing file instead of failing")
|
|
4835
4860
|
};
|
|
4836
4861
|
async function bootstrapProjectSave(input, ctx) {
|
|
4837
|
-
const target = input.module ?
|
|
4862
|
+
const target = input.module ? path16.join(ctx.paths.modulesContextDir, input.module, "context.md") : ctx.paths.projectContext;
|
|
4838
4863
|
const exists = existsSync15(target);
|
|
4839
4864
|
if (exists && !input.overwrite) {
|
|
4840
4865
|
throw new Error(
|
|
4841
4866
|
`${target} already exists. Pass overwrite=true to replace it.`
|
|
4842
4867
|
);
|
|
4843
4868
|
}
|
|
4844
|
-
await mkdir10(
|
|
4869
|
+
await mkdir10(path16.dirname(target), { recursive: true });
|
|
4845
4870
|
await writeFile10(target, input.content, "utf8");
|
|
4846
4871
|
return {
|
|
4847
4872
|
file_path: target,
|
|
@@ -6579,6 +6604,7 @@ async function getBriefing(input, ctx) {
|
|
|
6579
6604
|
const topSymbols = Object.entries(codeMap.files).flatMap(
|
|
6580
6605
|
([fp, entry]) => entry.exports.slice(0, 3).map((e) => `${e.name} (${fp.split("/").slice(-2).join("/")})`)
|
|
6581
6606
|
).slice(0, 15).join(", ");
|
|
6607
|
+
const commands = await detectRunCommands(ctx.paths.root);
|
|
6582
6608
|
projectContext = `# Project context (auto-generated by hAIve)
|
|
6583
6609
|
|
|
6584
6610
|
> \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.
|
|
@@ -6588,7 +6614,10 @@ async function getBriefing(input, ctx) {
|
|
|
6588
6614
|
- **Main file types:** ${topExts}
|
|
6589
6615
|
- **Generated at:** ${codeMap.generated_at}
|
|
6590
6616
|
|
|
6591
|
-
|
|
6617
|
+
` + (commands ? `## Commands
|
|
6618
|
+
${commands}
|
|
6619
|
+
|
|
6620
|
+
` : "") + `## Key exports (sample)
|
|
6592
6621
|
` + topSymbols + "\n";
|
|
6593
6622
|
autoContextGenerated = true;
|
|
6594
6623
|
setupWarnings.push(
|
|
@@ -6868,6 +6897,19 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
6868
6897
|
}
|
|
6869
6898
|
};
|
|
6870
6899
|
}
|
|
6900
|
+
async function detectRunCommands(root) {
|
|
6901
|
+
const pkgPath = path112.join(root, "package.json");
|
|
6902
|
+
if (!existsSync21(pkgPath)) return null;
|
|
6903
|
+
try {
|
|
6904
|
+
const pkg = JSON.parse(await readFile52(pkgPath, "utf8"));
|
|
6905
|
+
const scripts = pkg.scripts ?? {};
|
|
6906
|
+
const order = ["test", "build", "lint", "typecheck", "type-check", "dev", "start"];
|
|
6907
|
+
const lines = order.filter((name) => typeof scripts[name] === "string" && scripts[name].trim() !== "").map((name) => `- \`${name}\`: \`${scripts[name]}\``);
|
|
6908
|
+
return lines.length > 0 ? lines.join("\n") : null;
|
|
6909
|
+
} catch {
|
|
6910
|
+
return null;
|
|
6911
|
+
}
|
|
6912
|
+
}
|
|
6871
6913
|
var CodeMapInputSchema = {
|
|
6872
6914
|
file: z20.string().optional().describe("Filter to files whose path contains this substring"),
|
|
6873
6915
|
symbol: z20.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
|
|
@@ -7091,7 +7133,7 @@ var WhyThisFileInputSchema = {
|
|
|
7091
7133
|
memory_limit: z25.number().int().positive().max(20).default(5).describe("Cap on memories anchored to this path.")
|
|
7092
7134
|
};
|
|
7093
7135
|
async function whyThisFile(input, ctx) {
|
|
7094
|
-
const fileExists = existsSync24(
|
|
7136
|
+
const fileExists = existsSync24(path122.join(ctx.paths.root, input.path));
|
|
7095
7137
|
const [commits, memories, codeMap] = await Promise.all([
|
|
7096
7138
|
runGitLog(ctx.paths.root, input.path, input.git_log_limit).catch(() => []),
|
|
7097
7139
|
collectAnchoredMemories(ctx, input.path, input.memory_limit),
|
|
@@ -8123,13 +8165,13 @@ async function patternDetect(input, ctx) {
|
|
|
8123
8165
|
try {
|
|
8124
8166
|
const changedFiles = gitChangedFiles(ctx.paths.root, input.since_days);
|
|
8125
8167
|
const configFiles = changedFiles.filter(
|
|
8126
|
-
(f) => CONFIG_PATTERNS.some((p) =>
|
|
8168
|
+
(f) => CONFIG_PATTERNS.some((p) => path132.basename(f.toLowerCase()).includes(p))
|
|
8127
8169
|
);
|
|
8128
8170
|
for (const file of configFiles.slice(0, 5)) {
|
|
8129
8171
|
const diff = gitFileDiff(ctx.paths.root, file, input.since_days);
|
|
8130
8172
|
if (!diff) continue;
|
|
8131
|
-
const parentDir =
|
|
8132
|
-
const baseName =
|
|
8173
|
+
const parentDir = path132.basename(path132.dirname(file));
|
|
8174
|
+
const baseName = path132.basename(file).replace(/\.[^.]+$/, "");
|
|
8133
8175
|
const slug = `${parentDir}-${baseName}`.replace(/[^a-z0-9]/gi, "-").toLowerCase().slice(0, 40);
|
|
8134
8176
|
matches.push({
|
|
8135
8177
|
kind: "config_change",
|
|
@@ -8193,7 +8235,7 @@ async function patternDetect(input, ctx) {
|
|
|
8193
8235
|
for (const [p, { count, tools }] of pathCounts) {
|
|
8194
8236
|
if (count < HOT_FILE_MIN) continue;
|
|
8195
8237
|
if (tools.has("mem_tried") || tools.has("mem_observe")) continue;
|
|
8196
|
-
if (CONFIG_PATTERNS.some((cp2) =>
|
|
8238
|
+
if (CONFIG_PATTERNS.some((cp2) => path132.basename(p).includes(cp2))) continue;
|
|
8197
8239
|
const slug = p.replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").slice(0, 40);
|
|
8198
8240
|
matches.push({
|
|
8199
8241
|
kind: "hot_file",
|
|
@@ -8240,7 +8282,7 @@ async function patternDetect(input, ctx) {
|
|
|
8240
8282
|
void 0
|
|
8241
8283
|
);
|
|
8242
8284
|
if (existsSync29(file)) continue;
|
|
8243
|
-
await mkdir82(
|
|
8285
|
+
await mkdir82(path132.dirname(file), { recursive: true });
|
|
8244
8286
|
await writeFile14(
|
|
8245
8287
|
file,
|
|
8246
8288
|
serializeMemory12({ frontmatter: fm, body: match.proposed_body }),
|
|
@@ -8654,7 +8696,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
8654
8696
|
};
|
|
8655
8697
|
}
|
|
8656
8698
|
var SERVER_NAME = "haive";
|
|
8657
|
-
var SERVER_VERSION = "0.
|
|
8699
|
+
var SERVER_VERSION = "0.25.0";
|
|
8658
8700
|
function jsonResult(data) {
|
|
8659
8701
|
return {
|
|
8660
8702
|
content: [
|
|
@@ -9688,7 +9730,7 @@ function registerMcp(program2) {
|
|
|
9688
9730
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
9689
9731
|
import { readFile as readFile10, writeFile as writeFile15, mkdir as mkdir11 } from "fs/promises";
|
|
9690
9732
|
import { existsSync as existsSync33 } from "fs";
|
|
9691
|
-
import
|
|
9733
|
+
import path17 from "path";
|
|
9692
9734
|
import "commander";
|
|
9693
9735
|
import {
|
|
9694
9736
|
DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE2,
|
|
@@ -9882,10 +9924,10 @@ function registerSync(program2) {
|
|
|
9882
9924
|
const maxInject = Math.max(1, Number(opts.bridgeMaxMemories ?? 5));
|
|
9883
9925
|
let bridgeTargets;
|
|
9884
9926
|
if (opts.bridgeFile) {
|
|
9885
|
-
bridgeTargets = [
|
|
9927
|
+
bridgeTargets = [path17.resolve(opts.bridgeFile)];
|
|
9886
9928
|
} else {
|
|
9887
|
-
const agentsMd =
|
|
9888
|
-
bridgeTargets = [
|
|
9929
|
+
const agentsMd = path17.join(root, "AGENTS.md");
|
|
9930
|
+
bridgeTargets = [path17.join(root, "CLAUDE.md")];
|
|
9889
9931
|
if (existsSync33(agentsMd)) bridgeTargets.push(agentsMd);
|
|
9890
9932
|
}
|
|
9891
9933
|
for (const bridgeFile of bridgeTargets) {
|
|
@@ -10017,10 +10059,10 @@ Wait for **explicit confirmation** before acting.
|
|
|
10017
10059
|
topic: `dep-bump-${slugParts}`
|
|
10018
10060
|
});
|
|
10019
10061
|
if (!dryRun) {
|
|
10020
|
-
const teamDir =
|
|
10062
|
+
const teamDir = path17.join(paths.memoriesDir, "team");
|
|
10021
10063
|
await mkdir11(teamDir, { recursive: true });
|
|
10022
10064
|
await writeFile15(
|
|
10023
|
-
|
|
10065
|
+
path17.join(teamDir, `${fm.id}.md`),
|
|
10024
10066
|
serializeMemory13({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
10025
10067
|
"utf8"
|
|
10026
10068
|
);
|
|
@@ -10086,10 +10128,10 @@ Wait for **explicit confirmation** before acting.
|
|
|
10086
10128
|
topic: `contract-breaking-${diff.contract}`
|
|
10087
10129
|
});
|
|
10088
10130
|
if (!dryRun) {
|
|
10089
|
-
const teamDir =
|
|
10131
|
+
const teamDir = path17.join(paths.memoriesDir, "team");
|
|
10090
10132
|
await mkdir11(teamDir, { recursive: true });
|
|
10091
10133
|
await writeFile15(
|
|
10092
|
-
|
|
10134
|
+
path17.join(teamDir, `${fm.id}.md`),
|
|
10093
10135
|
serializeMemory13({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
10094
10136
|
"utf8"
|
|
10095
10137
|
);
|
|
@@ -10212,11 +10254,11 @@ ${BRIDGE_END}`;
|
|
|
10212
10254
|
const startIdx = existing.indexOf(BRIDGE_START);
|
|
10213
10255
|
const endIdx = existing.indexOf(BRIDGE_END);
|
|
10214
10256
|
if (startIdx !== -1 && endIdx === -1) {
|
|
10215
|
-
ui.warn(`${
|
|
10257
|
+
ui.warn(`${path17.relative(root, bridgeFile)}: found ${BRIDGE_START} without ${BRIDGE_END}. Fix the file manually before running --inject-bridge.`);
|
|
10216
10258
|
return;
|
|
10217
10259
|
}
|
|
10218
10260
|
if (startIdx === -1 && endIdx !== -1) {
|
|
10219
|
-
ui.warn(`${
|
|
10261
|
+
ui.warn(`${path17.relative(root, bridgeFile)}: found ${BRIDGE_END} without ${BRIDGE_START}. Fix the file manually before running --inject-bridge.`);
|
|
10220
10262
|
return;
|
|
10221
10263
|
}
|
|
10222
10264
|
let updated;
|
|
@@ -10224,14 +10266,14 @@ ${BRIDGE_END}`;
|
|
|
10224
10266
|
updated = existing.slice(0, startIdx) + injected + existing.slice(endIdx + BRIDGE_END.length);
|
|
10225
10267
|
} else {
|
|
10226
10268
|
if (!fileExists && !quiet) {
|
|
10227
|
-
ui.info(`Creating ${
|
|
10269
|
+
ui.info(`Creating ${path17.relative(root, bridgeFile)} with haive memory block.`);
|
|
10228
10270
|
}
|
|
10229
10271
|
updated = existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + injected + "\n";
|
|
10230
10272
|
}
|
|
10231
10273
|
await writeFile15(bridgeFile, updated, "utf8");
|
|
10232
10274
|
if (!quiet) {
|
|
10233
10275
|
console.log(
|
|
10234
|
-
ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${
|
|
10276
|
+
ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path17.relative(root, bridgeFile)}`)
|
|
10235
10277
|
);
|
|
10236
10278
|
}
|
|
10237
10279
|
}
|
|
@@ -10258,7 +10300,7 @@ function collectSinceChanges(root, ref) {
|
|
|
10258
10300
|
import { createHash as createHash2 } from "crypto";
|
|
10259
10301
|
import { mkdir as mkdir12, readFile as readFile11, writeFile as writeFile16 } from "fs/promises";
|
|
10260
10302
|
import { existsSync as existsSync34 } from "fs";
|
|
10261
|
-
import
|
|
10303
|
+
import path18 from "path";
|
|
10262
10304
|
import "commander";
|
|
10263
10305
|
import {
|
|
10264
10306
|
buildFrontmatter as buildFrontmatter7,
|
|
@@ -10316,7 +10358,7 @@ function registerMemoryAdd(memory2) {
|
|
|
10316
10358
|
const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
|
|
10317
10359
|
const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
|
|
10318
10360
|
if (anchorPaths.length > 0) {
|
|
10319
|
-
const missing = anchorPaths.filter((p) => !existsSync34(
|
|
10361
|
+
const missing = anchorPaths.filter((p) => !existsSync34(path18.resolve(root, p)));
|
|
10320
10362
|
if (missing.length > 0) {
|
|
10321
10363
|
ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
|
|
10322
10364
|
for (const p of missing) ui.warn(` \u2717 ${p}`);
|
|
@@ -10380,7 +10422,7 @@ TODO \u2014 write the memory body.
|
|
|
10380
10422
|
const suggestedSensor = !newFrontmatter.sensor ? suggestSensorForCliMemory(opts.type, body, newFrontmatter.anchor.paths) : null;
|
|
10381
10423
|
if (suggestedSensor) newFrontmatter.sensor = suggestedSensor;
|
|
10382
10424
|
await writeFile16(topicMatch.filePath, serializeMemory14({ frontmatter: newFrontmatter, body }), "utf8");
|
|
10383
|
-
ui.success(`Updated (topic upsert) ${
|
|
10425
|
+
ui.success(`Updated (topic upsert) ${path18.relative(root, topicMatch.filePath)}`);
|
|
10384
10426
|
ui.info(`id=${fm.id} revision=${revisionCount}`);
|
|
10385
10427
|
if (suggestedSensor) ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(suggestedSensor.pattern)}`);
|
|
10386
10428
|
await runPostMemoryAutopilot(root, paths, config);
|
|
@@ -10404,7 +10446,7 @@ TODO \u2014 write the memory body.
|
|
|
10404
10446
|
activation
|
|
10405
10447
|
});
|
|
10406
10448
|
const file = memoryFilePath7(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
10407
|
-
await mkdir12(
|
|
10449
|
+
await mkdir12(path18.dirname(file), { recursive: true });
|
|
10408
10450
|
if (existsSync34(file)) {
|
|
10409
10451
|
ui.error(`Memory already exists at ${file}`);
|
|
10410
10452
|
process.exitCode = 1;
|
|
@@ -10423,7 +10465,7 @@ TODO \u2014 write the memory body.
|
|
|
10423
10465
|
}
|
|
10424
10466
|
}
|
|
10425
10467
|
await writeFile16(file, serializeMemory14({ frontmatter, body }), "utf8");
|
|
10426
|
-
ui.success(`Created ${
|
|
10468
|
+
ui.success(`Created ${path18.relative(root, file)}`);
|
|
10427
10469
|
ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
|
|
10428
10470
|
if (frontmatter.sensor?.autogen) {
|
|
10429
10471
|
ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(frontmatter.sensor.pattern)}`);
|
|
@@ -10503,7 +10545,7 @@ function slugify(value) {
|
|
|
10503
10545
|
|
|
10504
10546
|
// src/commands/memory-list.ts
|
|
10505
10547
|
import { existsSync as existsSync35 } from "fs";
|
|
10506
|
-
import
|
|
10548
|
+
import path19 from "path";
|
|
10507
10549
|
import "commander";
|
|
10508
10550
|
import { findProjectRoot as findProjectRoot14, resolveHaivePaths as resolveHaivePaths11 } from "@hiveai/core";
|
|
10509
10551
|
|
|
@@ -10556,7 +10598,7 @@ function registerMemoryList(memory2) {
|
|
|
10556
10598
|
);
|
|
10557
10599
|
const title = mem.body.match(/^#\s+(.+)$/m)?.[1]?.trim();
|
|
10558
10600
|
if (title && title !== fm.id) console.log(` ${title}`);
|
|
10559
|
-
console.log(` ${ui.dim(
|
|
10601
|
+
console.log(` ${ui.dim(path19.relative(root, filePath))}`);
|
|
10560
10602
|
}
|
|
10561
10603
|
const totalLabel = clipped > 0 ? `
|
|
10562
10604
|
${displayed.length} of ${filtered.length} memories shown (use --limit to adjust)` : `
|
|
@@ -10595,7 +10637,7 @@ function matchesFilters(loaded, opts) {
|
|
|
10595
10637
|
// src/commands/memory-promote.ts
|
|
10596
10638
|
import { mkdir as mkdir13, unlink as unlink2, writeFile as writeFile17 } from "fs/promises";
|
|
10597
10639
|
import { existsSync as existsSync36 } from "fs";
|
|
10598
|
-
import
|
|
10640
|
+
import path20 from "path";
|
|
10599
10641
|
import "commander";
|
|
10600
10642
|
import {
|
|
10601
10643
|
findProjectRoot as findProjectRoot15,
|
|
@@ -10642,11 +10684,11 @@ function registerMemoryPromote(memory2) {
|
|
|
10642
10684
|
body: found.memory.body
|
|
10643
10685
|
};
|
|
10644
10686
|
const newPath = memoryFilePath8(paths, "team", updated.frontmatter.id);
|
|
10645
|
-
await mkdir13(
|
|
10687
|
+
await mkdir13(path20.dirname(newPath), { recursive: true });
|
|
10646
10688
|
await writeFile17(newPath, serializeMemory15(updated), "utf8");
|
|
10647
10689
|
await unlink2(found.filePath);
|
|
10648
10690
|
ui.success(`Promoted ${id} to team scope (status=proposed)`);
|
|
10649
|
-
ui.info(`Now at ${
|
|
10691
|
+
ui.info(`Now at ${path20.relative(root, newPath)}`);
|
|
10650
10692
|
console.log(ui.dim(`\u2192 next: haive memory approve ${id} (validate for team use)`));
|
|
10651
10693
|
});
|
|
10652
10694
|
}
|
|
@@ -10654,7 +10696,7 @@ function registerMemoryPromote(memory2) {
|
|
|
10654
10696
|
// src/commands/memory-approve.ts
|
|
10655
10697
|
import { existsSync as existsSync37 } from "fs";
|
|
10656
10698
|
import { writeFile as writeFile18 } from "fs/promises";
|
|
10657
|
-
import
|
|
10699
|
+
import path21 from "path";
|
|
10658
10700
|
import "commander";
|
|
10659
10701
|
import {
|
|
10660
10702
|
findProjectRoot as findProjectRoot16,
|
|
@@ -10718,14 +10760,14 @@ function registerMemoryApprove(memory2) {
|
|
|
10718
10760
|
};
|
|
10719
10761
|
await writeFile18(found.filePath, serializeMemory16(next), "utf8");
|
|
10720
10762
|
ui.success(`Approved ${id} (status=validated)`);
|
|
10721
|
-
ui.info(
|
|
10763
|
+
ui.info(path21.relative(root, found.filePath));
|
|
10722
10764
|
});
|
|
10723
10765
|
}
|
|
10724
10766
|
|
|
10725
10767
|
// src/commands/memory-update.ts
|
|
10726
10768
|
import { readFile as readFile12, writeFile as writeFile19 } from "fs/promises";
|
|
10727
10769
|
import { existsSync as existsSync38 } from "fs";
|
|
10728
|
-
import
|
|
10770
|
+
import path23 from "path";
|
|
10729
10771
|
import "commander";
|
|
10730
10772
|
import {
|
|
10731
10773
|
findProjectRoot as findProjectRoot17,
|
|
@@ -10804,7 +10846,7 @@ function registerMemoryUpdate(memory2) {
|
|
|
10804
10846
|
serializeMemory17({ frontmatter: newFrontmatter, body: newBody }),
|
|
10805
10847
|
"utf8"
|
|
10806
10848
|
);
|
|
10807
|
-
ui.success(`Updated ${
|
|
10849
|
+
ui.success(`Updated ${path23.relative(root, loaded.filePath)}`);
|
|
10808
10850
|
ui.info(`fields: ${updated.join(", ")}`);
|
|
10809
10851
|
});
|
|
10810
10852
|
}
|
|
@@ -10825,7 +10867,7 @@ function parseCsv3(value) {
|
|
|
10825
10867
|
// src/commands/memory-auto-promote.ts
|
|
10826
10868
|
import { writeFile as writeFile20 } from "fs/promises";
|
|
10827
10869
|
import { existsSync as existsSync39 } from "fs";
|
|
10828
|
-
import
|
|
10870
|
+
import path24 from "path";
|
|
10829
10871
|
import "commander";
|
|
10830
10872
|
import {
|
|
10831
10873
|
DEFAULT_AUTO_PROMOTE_RULE as DEFAULT_AUTO_PROMOTE_RULE3,
|
|
@@ -10870,7 +10912,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
10870
10912
|
console.log(
|
|
10871
10913
|
`${ui.bold(opts.apply ? "PROMOTE" : "would promote")} ${mem.frontmatter.id} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
|
|
10872
10914
|
);
|
|
10873
|
-
console.log(` ${ui.dim(
|
|
10915
|
+
console.log(` ${ui.dim(path24.relative(root, filePath))}`);
|
|
10874
10916
|
if (opts.apply) {
|
|
10875
10917
|
const next = {
|
|
10876
10918
|
frontmatter: { ...mem.frontmatter, status: "validated" },
|
|
@@ -10889,7 +10931,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
10889
10931
|
import { spawn as spawn3 } from "child_process";
|
|
10890
10932
|
import { existsSync as existsSync40 } from "fs";
|
|
10891
10933
|
import { readFile as readFile13 } from "fs/promises";
|
|
10892
|
-
import
|
|
10934
|
+
import path25 from "path";
|
|
10893
10935
|
import "commander";
|
|
10894
10936
|
import {
|
|
10895
10937
|
findProjectRoot as findProjectRoot19,
|
|
@@ -10913,7 +10955,7 @@ function registerMemoryEdit(memory2) {
|
|
|
10913
10955
|
return;
|
|
10914
10956
|
}
|
|
10915
10957
|
const editor = opts.editor ?? process.env.EDITOR ?? process.env.VISUAL ?? "vi";
|
|
10916
|
-
ui.info(`Opening ${
|
|
10958
|
+
ui.info(`Opening ${path25.relative(root, found.filePath)} with ${editor}\u2026`);
|
|
10917
10959
|
const code = await runEditor(editor, found.filePath);
|
|
10918
10960
|
if (code !== 0) {
|
|
10919
10961
|
ui.warn(`Editor exited with status ${code}.`);
|
|
@@ -10941,7 +10983,7 @@ function runEditor(editor, file) {
|
|
|
10941
10983
|
|
|
10942
10984
|
// src/commands/memory-for-files.ts
|
|
10943
10985
|
import { existsSync as existsSync41 } from "fs";
|
|
10944
|
-
import
|
|
10986
|
+
import path26 from "path";
|
|
10945
10987
|
import "commander";
|
|
10946
10988
|
import {
|
|
10947
10989
|
deriveConfidence as deriveConfidence9,
|
|
@@ -11063,13 +11105,13 @@ function printGroup(root, label, loaded, usage) {
|
|
|
11063
11105
|
const u = getUsage13(usage, fm.id);
|
|
11064
11106
|
const conf = deriveConfidence9(fm, u);
|
|
11065
11107
|
console.log(`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(conf)}`);
|
|
11066
|
-
console.log(` ${ui.dim(
|
|
11108
|
+
console.log(` ${ui.dim(path26.relative(root, filePath))}`);
|
|
11067
11109
|
}
|
|
11068
11110
|
}
|
|
11069
11111
|
|
|
11070
11112
|
// src/commands/memory-hot.ts
|
|
11071
11113
|
import { existsSync as existsSync43 } from "fs";
|
|
11072
|
-
import
|
|
11114
|
+
import path27 from "path";
|
|
11073
11115
|
import "commander";
|
|
11074
11116
|
import {
|
|
11075
11117
|
findProjectRoot as findProjectRoot21,
|
|
@@ -11111,7 +11153,7 @@ function registerMemoryHot(memory2) {
|
|
|
11111
11153
|
console.log(
|
|
11112
11154
|
`${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.bold(fm.status)} ${ui.dim(`reads=${u.read_count} rejections=${u.rejected_count}`)}`
|
|
11113
11155
|
);
|
|
11114
|
-
console.log(` ${ui.dim(
|
|
11156
|
+
console.log(` ${ui.dim(path27.relative(root, filePath))}`);
|
|
11115
11157
|
}
|
|
11116
11158
|
ui.info(
|
|
11117
11159
|
`${candidates.length} hot (read \u2265${threshold}\xD7) \u2014 agents rely on these; promote with \`haive memory promote <id>\`.
|
|
@@ -11123,7 +11165,7 @@ function registerMemoryHot(memory2) {
|
|
|
11123
11165
|
// src/commands/memory-tried.ts
|
|
11124
11166
|
import { mkdir as mkdir14, writeFile as writeFile21 } from "fs/promises";
|
|
11125
11167
|
import { existsSync as existsSync44 } from "fs";
|
|
11126
|
-
import
|
|
11168
|
+
import path28 from "path";
|
|
11127
11169
|
import "commander";
|
|
11128
11170
|
import {
|
|
11129
11171
|
buildFrontmatter as buildFrontmatter8,
|
|
@@ -11179,14 +11221,14 @@ function registerMemoryTried(memory2) {
|
|
|
11179
11221
|
frontmatter.sensor = sensor;
|
|
11180
11222
|
}
|
|
11181
11223
|
const file = memoryFilePath9(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
11182
|
-
await mkdir14(
|
|
11224
|
+
await mkdir14(path28.dirname(file), { recursive: true });
|
|
11183
11225
|
if (existsSync44(file)) {
|
|
11184
11226
|
ui.error(`Memory already exists at ${file}`);
|
|
11185
11227
|
process.exitCode = 1;
|
|
11186
11228
|
return;
|
|
11187
11229
|
}
|
|
11188
11230
|
await writeFile21(file, serializeMemory19({ frontmatter, body }), "utf8");
|
|
11189
|
-
ui.success(`Recorded: ${
|
|
11231
|
+
ui.success(`Recorded: ${path28.relative(root, file)}`);
|
|
11190
11232
|
ui.info(`id=${frontmatter.id} type=attempt status=validated (auto-approved)`);
|
|
11191
11233
|
if (sensor) {
|
|
11192
11234
|
ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(sensor.pattern)}`);
|
|
@@ -11205,7 +11247,7 @@ function parseCsv4(value) {
|
|
|
11205
11247
|
// src/commands/memory-seed.ts
|
|
11206
11248
|
import { readFile as readFile14 } from "fs/promises";
|
|
11207
11249
|
import { existsSync as existsSync45 } from "fs";
|
|
11208
|
-
import
|
|
11250
|
+
import path29 from "path";
|
|
11209
11251
|
import "commander";
|
|
11210
11252
|
import {
|
|
11211
11253
|
findProjectRoot as findProjectRoot23,
|
|
@@ -11214,7 +11256,7 @@ import {
|
|
|
11214
11256
|
} from "@hiveai/core";
|
|
11215
11257
|
async function readDependencyMap(root) {
|
|
11216
11258
|
try {
|
|
11217
|
-
const raw = await readFile14(
|
|
11259
|
+
const raw = await readFile14(path29.join(root, "package.json"), "utf8");
|
|
11218
11260
|
const pkg = JSON.parse(raw);
|
|
11219
11261
|
return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
11220
11262
|
} catch {
|
|
@@ -11301,7 +11343,7 @@ function registerMemorySeed(memory2) {
|
|
|
11301
11343
|
|
|
11302
11344
|
// src/commands/memory-pending.ts
|
|
11303
11345
|
import { existsSync as existsSync46 } from "fs";
|
|
11304
|
-
import
|
|
11346
|
+
import path30 from "path";
|
|
11305
11347
|
import "commander";
|
|
11306
11348
|
import {
|
|
11307
11349
|
findProjectRoot as findProjectRoot24,
|
|
@@ -11347,7 +11389,7 @@ function registerMemoryPending(memory2) {
|
|
|
11347
11389
|
console.log(
|
|
11348
11390
|
` ${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count}`)}`
|
|
11349
11391
|
);
|
|
11350
|
-
console.log(` ${ui.dim(
|
|
11392
|
+
console.log(` ${ui.dim(path30.relative(root, filePath))}`);
|
|
11351
11393
|
}
|
|
11352
11394
|
if (proposed.length > 0) console.log(ui.dim(` \u2192 haive memory approve <id> or haive memory auto-promote`));
|
|
11353
11395
|
console.log();
|
|
@@ -11362,7 +11404,7 @@ function registerMemoryPending(memory2) {
|
|
|
11362
11404
|
console.log(
|
|
11363
11405
|
` ${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count}`)}`
|
|
11364
11406
|
);
|
|
11365
|
-
console.log(` ${ui.dim(
|
|
11407
|
+
console.log(` ${ui.dim(path30.relative(root, filePath))}`);
|
|
11366
11408
|
}
|
|
11367
11409
|
console.log(ui.dim(` \u2192 haive memory approve <id> (activate) | haive memory promote <id> (share with team)`));
|
|
11368
11410
|
}
|
|
@@ -11372,7 +11414,7 @@ function registerMemoryPending(memory2) {
|
|
|
11372
11414
|
|
|
11373
11415
|
// src/commands/memory-query.ts
|
|
11374
11416
|
import { existsSync as existsSync47 } from "fs";
|
|
11375
|
-
import
|
|
11417
|
+
import path31 from "path";
|
|
11376
11418
|
import "commander";
|
|
11377
11419
|
import {
|
|
11378
11420
|
extractSnippet as extractSnippet2,
|
|
@@ -11429,7 +11471,7 @@ function registerMemoryQuery(memory2) {
|
|
|
11429
11471
|
const fm = mem.frontmatter;
|
|
11430
11472
|
const statusBadge = ui.statusBadge(fm.status);
|
|
11431
11473
|
console.log(`${ui.bold(fm.id)} ${ui.dim(fm.scope)} ${statusBadge}`);
|
|
11432
|
-
console.log(` ${ui.dim(
|
|
11474
|
+
console.log(` ${ui.dim(path31.relative(root, filePath))}`);
|
|
11433
11475
|
const snippet = extractSnippet2(mem.body, snippetNeedle);
|
|
11434
11476
|
if (snippet) console.log(` ${snippet}`);
|
|
11435
11477
|
}
|
|
@@ -11499,7 +11541,7 @@ function registerMemoryReject(memory2) {
|
|
|
11499
11541
|
// src/commands/memory-rm.ts
|
|
11500
11542
|
import { existsSync as existsSync49 } from "fs";
|
|
11501
11543
|
import { unlink as unlink3 } from "fs/promises";
|
|
11502
|
-
import
|
|
11544
|
+
import path33 from "path";
|
|
11503
11545
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
11504
11546
|
import "commander";
|
|
11505
11547
|
import {
|
|
@@ -11524,7 +11566,7 @@ function registerMemoryRm(memory2) {
|
|
|
11524
11566
|
process.exitCode = 1;
|
|
11525
11567
|
return;
|
|
11526
11568
|
}
|
|
11527
|
-
const rel =
|
|
11569
|
+
const rel = path33.relative(root, found.filePath);
|
|
11528
11570
|
if (!opts.yes) {
|
|
11529
11571
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
11530
11572
|
const answer = (await rl.question(`Delete ${rel}? [y/N] `)).trim().toLowerCase();
|
|
@@ -11550,7 +11592,7 @@ function registerMemoryRm(memory2) {
|
|
|
11550
11592
|
// src/commands/memory-show.ts
|
|
11551
11593
|
import { existsSync as existsSync50 } from "fs";
|
|
11552
11594
|
import { readFile as readFile15 } from "fs/promises";
|
|
11553
|
-
import
|
|
11595
|
+
import path34 from "path";
|
|
11554
11596
|
import "commander";
|
|
11555
11597
|
import {
|
|
11556
11598
|
deriveConfidence as deriveConfidence10,
|
|
@@ -11592,7 +11634,7 @@ function registerMemoryShow(memory2) {
|
|
|
11592
11634
|
if (fm.verified_at) console.log(`${ui.dim("verified:")} ${fm.verified_at}`);
|
|
11593
11635
|
if (fm.stale_reason) console.log(`${ui.dim("stale:")} ${fm.stale_reason}`);
|
|
11594
11636
|
console.log(`${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`);
|
|
11595
|
-
console.log(`${ui.dim("file:")} ${
|
|
11637
|
+
console.log(`${ui.dim("file:")} ${path34.relative(root, found.filePath)}`);
|
|
11596
11638
|
if (fm.anchor.paths.length || fm.anchor.symbols.length) {
|
|
11597
11639
|
console.log(ui.dim("anchor:"));
|
|
11598
11640
|
if (fm.anchor.commit) console.log(` ${ui.dim("commit:")} ${fm.anchor.commit}`);
|
|
@@ -11608,7 +11650,7 @@ function registerMemoryShow(memory2) {
|
|
|
11608
11650
|
|
|
11609
11651
|
// src/commands/memory-stats.ts
|
|
11610
11652
|
import { existsSync as existsSync51 } from "fs";
|
|
11611
|
-
import
|
|
11653
|
+
import path35 from "path";
|
|
11612
11654
|
import "commander";
|
|
11613
11655
|
import {
|
|
11614
11656
|
deriveConfidence as deriveConfidence11,
|
|
@@ -11646,7 +11688,7 @@ function registerMemoryStats(memory2) {
|
|
|
11646
11688
|
console.log(
|
|
11647
11689
|
` ${ui.dim("status:")} ${fm.status} ${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`
|
|
11648
11690
|
);
|
|
11649
|
-
console.log(` ${ui.dim(
|
|
11691
|
+
console.log(` ${ui.dim(path35.relative(root, filePath))}`);
|
|
11650
11692
|
}
|
|
11651
11693
|
});
|
|
11652
11694
|
}
|
|
@@ -11809,7 +11851,7 @@ function registerMemoryFeedback(memory2) {
|
|
|
11809
11851
|
// src/commands/memory-verify.ts
|
|
11810
11852
|
import { writeFile as writeFile25 } from "fs/promises";
|
|
11811
11853
|
import { existsSync as existsSync55 } from "fs";
|
|
11812
|
-
import
|
|
11854
|
+
import path36 from "path";
|
|
11813
11855
|
import "commander";
|
|
11814
11856
|
import {
|
|
11815
11857
|
findProjectRoot as findProjectRoot32,
|
|
@@ -11851,7 +11893,7 @@ function registerMemoryVerify(memory2) {
|
|
|
11851
11893
|
for (const { memory: mem, filePath } of targets) {
|
|
11852
11894
|
const result = await verifyAnchor3(mem, { projectRoot: root });
|
|
11853
11895
|
const isAnchored = mem.frontmatter.anchor.paths.length > 0 || mem.frontmatter.anchor.symbols.length > 0;
|
|
11854
|
-
const rel =
|
|
11896
|
+
const rel = path36.relative(root, filePath);
|
|
11855
11897
|
if (!isAnchored) {
|
|
11856
11898
|
anchorlessIds.push(mem.frontmatter.id);
|
|
11857
11899
|
entries.push({ id: mem.frontmatter.id, status: "anchorless", path: rel });
|
|
@@ -11996,7 +12038,7 @@ function registerMemoryImport(memory2) {
|
|
|
11996
12038
|
// src/commands/memory-import-changelog.ts
|
|
11997
12039
|
import { existsSync as existsSync57 } from "fs";
|
|
11998
12040
|
import { readFile as readFile17, mkdir as mkdir15, writeFile as writeFile26 } from "fs/promises";
|
|
11999
|
-
import
|
|
12041
|
+
import path37 from "path";
|
|
12000
12042
|
import "commander";
|
|
12001
12043
|
import {
|
|
12002
12044
|
buildFrontmatter as buildFrontmatter9,
|
|
@@ -12068,7 +12110,7 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
12068
12110
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
12069
12111
|
const root = findProjectRoot34(opts.dir);
|
|
12070
12112
|
const paths = resolveHaivePaths31(root);
|
|
12071
|
-
const changelogPath =
|
|
12113
|
+
const changelogPath = path37.resolve(root, opts.fromChangelog);
|
|
12072
12114
|
if (!existsSync57(changelogPath)) {
|
|
12073
12115
|
ui.error(`CHANGELOG not found: ${changelogPath}`);
|
|
12074
12116
|
process.exitCode = 1;
|
|
@@ -12089,9 +12131,9 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
12089
12131
|
entries = entries.filter((e) => requested.includes(e.version));
|
|
12090
12132
|
}
|
|
12091
12133
|
}
|
|
12092
|
-
const pkgName = opts.package ??
|
|
12134
|
+
const pkgName = opts.package ?? path37.basename(path37.dirname(changelogPath));
|
|
12093
12135
|
const scope = opts.scope ?? "team";
|
|
12094
|
-
const teamDir =
|
|
12136
|
+
const teamDir = path37.join(paths.memoriesDir, scope);
|
|
12095
12137
|
await mkdir15(teamDir, { recursive: true });
|
|
12096
12138
|
let saved = 0;
|
|
12097
12139
|
for (const entry of entries) {
|
|
@@ -12114,7 +12156,7 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
12114
12156
|
lines.push("");
|
|
12115
12157
|
}
|
|
12116
12158
|
lines.push(
|
|
12117
|
-
`**Source:** \`${
|
|
12159
|
+
`**Source:** \`${path37.relative(root, changelogPath)}\`
|
|
12118
12160
|
**Action:** Update all usages of ${pkgName} if they rely on any of the above.`
|
|
12119
12161
|
);
|
|
12120
12162
|
const slug = `changelog-${pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase()}-v${entry.version.replace(/\./g, "-")}`;
|
|
@@ -12129,11 +12171,11 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
12129
12171
|
pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase(),
|
|
12130
12172
|
`v${entry.version}`
|
|
12131
12173
|
],
|
|
12132
|
-
paths: [
|
|
12174
|
+
paths: [path37.relative(root, changelogPath)],
|
|
12133
12175
|
topic: `changelog-${pkgName}-${entry.version}`
|
|
12134
12176
|
});
|
|
12135
12177
|
await writeFile26(
|
|
12136
|
-
|
|
12178
|
+
path37.join(teamDir, `${fm.id}.md`),
|
|
12137
12179
|
serializeMemory24({ frontmatter: fm, body: lines.join("\n") }),
|
|
12138
12180
|
"utf8"
|
|
12139
12181
|
);
|
|
@@ -12158,7 +12200,7 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
|
|
|
12158
12200
|
// src/commands/memory-digest.ts
|
|
12159
12201
|
import { existsSync as existsSync58 } from "fs";
|
|
12160
12202
|
import { writeFile as writeFile27 } from "fs/promises";
|
|
12161
|
-
import
|
|
12203
|
+
import path38 from "path";
|
|
12162
12204
|
import "commander";
|
|
12163
12205
|
import {
|
|
12164
12206
|
deriveConfidence as deriveConfidence12,
|
|
@@ -12253,7 +12295,7 @@ function registerMemoryDigest(program2) {
|
|
|
12253
12295
|
);
|
|
12254
12296
|
const digest = lines.join("\n");
|
|
12255
12297
|
if (opts.out) {
|
|
12256
|
-
const outPath =
|
|
12298
|
+
const outPath = path38.resolve(process.cwd(), opts.out);
|
|
12257
12299
|
await writeFile27(outPath, digest, "utf8");
|
|
12258
12300
|
ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
|
|
12259
12301
|
} else {
|
|
@@ -12266,7 +12308,7 @@ function registerMemoryDigest(program2) {
|
|
|
12266
12308
|
import { writeFile as writeFile28, mkdir as mkdir16, readFile as readFile18, rm as rm2 } from "fs/promises";
|
|
12267
12309
|
import { existsSync as existsSync59 } from "fs";
|
|
12268
12310
|
import { spawn as spawn4 } from "child_process";
|
|
12269
|
-
import
|
|
12311
|
+
import path39 from "path";
|
|
12270
12312
|
import "commander";
|
|
12271
12313
|
import {
|
|
12272
12314
|
buildFrontmatter as buildFrontmatter10,
|
|
@@ -12281,7 +12323,7 @@ import {
|
|
|
12281
12323
|
summarizeCaughtForYou
|
|
12282
12324
|
} from "@hiveai/core";
|
|
12283
12325
|
async function buildAutoRecap(paths) {
|
|
12284
|
-
const obsFile =
|
|
12326
|
+
const obsFile = path39.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
12285
12327
|
if (!existsSync59(obsFile)) return await buildGitAutoRecap(paths);
|
|
12286
12328
|
const raw = await readFile18(obsFile, "utf8").catch(() => "");
|
|
12287
12329
|
if (!raw.trim()) return await buildGitAutoRecap(paths);
|
|
@@ -12450,7 +12492,7 @@ function runGit(cwd, args) {
|
|
|
12450
12492
|
});
|
|
12451
12493
|
}
|
|
12452
12494
|
async function observationStart(paths) {
|
|
12453
|
-
const obsFile =
|
|
12495
|
+
const obsFile = path39.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
12454
12496
|
if (!existsSync59(obsFile)) return null;
|
|
12455
12497
|
const raw = await readFile18(obsFile, "utf8").catch(() => "");
|
|
12456
12498
|
let first = null;
|
|
@@ -12539,14 +12581,14 @@ function registerSessionEnd(session2) {
|
|
|
12539
12581
|
});
|
|
12540
12582
|
const topic = recapTopic2(scope, opts.module);
|
|
12541
12583
|
const filesTouched = parseCsv5(resolvedFiles).map((p) => normalizeAnchorPath(root, p));
|
|
12542
|
-
const missingPaths = filesTouched.filter((p) => !existsSync59(
|
|
12584
|
+
const missingPaths = filesTouched.filter((p) => !existsSync59(path39.resolve(root, p)));
|
|
12543
12585
|
if (missingPaths.length > 0 && !opts.quiet) {
|
|
12544
12586
|
ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
|
|
12545
12587
|
for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
|
|
12546
12588
|
}
|
|
12547
12589
|
const cleanupObservations = async () => {
|
|
12548
12590
|
if (!opts.auto) return;
|
|
12549
|
-
const obsFile =
|
|
12591
|
+
const obsFile = path39.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
12550
12592
|
if (existsSync59(obsFile)) await rm2(obsFile).catch(() => {
|
|
12551
12593
|
});
|
|
12552
12594
|
};
|
|
@@ -12571,7 +12613,7 @@ function registerSessionEnd(session2) {
|
|
|
12571
12613
|
await cleanupObservations();
|
|
12572
12614
|
if (!opts.quiet) {
|
|
12573
12615
|
ui.success(`Session recap updated (revision #${revisionCount})`);
|
|
12574
|
-
ui.info(`id=${fm.id} file=${
|
|
12616
|
+
ui.info(`id=${fm.id} file=${path39.relative(root, topicMatch.filePath)}`);
|
|
12575
12617
|
await printCaughtForYou(paths, caughtSince, opts.quiet);
|
|
12576
12618
|
ui.info("Tip: `haive stats --export-report` generates a usage JSON suitable for dashboards.");
|
|
12577
12619
|
}
|
|
@@ -12589,12 +12631,12 @@ function registerSessionEnd(session2) {
|
|
|
12589
12631
|
status: "validated"
|
|
12590
12632
|
});
|
|
12591
12633
|
const file = memoryFilePath10(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
12592
|
-
await mkdir16(
|
|
12634
|
+
await mkdir16(path39.dirname(file), { recursive: true });
|
|
12593
12635
|
await writeFile28(file, serializeMemory25({ frontmatter, body }), "utf8");
|
|
12594
12636
|
await cleanupObservations();
|
|
12595
12637
|
if (!opts.quiet) {
|
|
12596
12638
|
ui.success(`Session recap created`);
|
|
12597
|
-
ui.info(`id=${frontmatter.id} scope=${scope} file=${
|
|
12639
|
+
ui.info(`id=${frontmatter.id} scope=${scope} file=${path39.relative(root, file)}`);
|
|
12598
12640
|
await printCaughtForYou(paths, caughtSince, opts.quiet);
|
|
12599
12641
|
ui.info("Next session: call `get_briefing` \u2014 the recap will be surfaced automatically.");
|
|
12600
12642
|
ui.info("Tip: export a local MCP usage rollup with `haive stats --export-report .ai/tool-usage-roi-report.json`.");
|
|
@@ -12607,8 +12649,8 @@ function parseCsv5(value) {
|
|
|
12607
12649
|
}
|
|
12608
12650
|
function normalizeAnchorPath(root, filePath) {
|
|
12609
12651
|
if (!filePath) return filePath;
|
|
12610
|
-
if (!
|
|
12611
|
-
const rel =
|
|
12652
|
+
if (!path39.isAbsolute(filePath)) return filePath;
|
|
12653
|
+
const rel = path39.relative(root, filePath);
|
|
12612
12654
|
if (rel.startsWith("..")) return filePath;
|
|
12613
12655
|
return rel;
|
|
12614
12656
|
}
|
|
@@ -12616,7 +12658,7 @@ function normalizeAnchorPath(root, filePath) {
|
|
|
12616
12658
|
// src/commands/snapshot.ts
|
|
12617
12659
|
import { existsSync as existsSync60 } from "fs";
|
|
12618
12660
|
import { readdir as readdir4 } from "fs/promises";
|
|
12619
|
-
import
|
|
12661
|
+
import path40 from "path";
|
|
12620
12662
|
import "commander";
|
|
12621
12663
|
import {
|
|
12622
12664
|
diffContract,
|
|
@@ -12655,7 +12697,7 @@ function registerSnapshot(program2) {
|
|
|
12655
12697
|
return;
|
|
12656
12698
|
}
|
|
12657
12699
|
if (opts.list) {
|
|
12658
|
-
const contractsDir =
|
|
12700
|
+
const contractsDir = path40.join(paths.haiveDir, "contracts");
|
|
12659
12701
|
if (!existsSync60(contractsDir)) {
|
|
12660
12702
|
console.log(ui.dim("No contract snapshots found."));
|
|
12661
12703
|
return;
|
|
@@ -12711,7 +12753,7 @@ function registerSnapshot(program2) {
|
|
|
12711
12753
|
return;
|
|
12712
12754
|
}
|
|
12713
12755
|
const contractPath = opts.contract;
|
|
12714
|
-
const name = opts.name ??
|
|
12756
|
+
const name = opts.name ?? path40.basename(contractPath, path40.extname(contractPath));
|
|
12715
12757
|
const format = opts.format ?? detectFormat(contractPath) ?? "openapi";
|
|
12716
12758
|
const contract = { name, path: contractPath, format };
|
|
12717
12759
|
try {
|
|
@@ -12766,8 +12808,8 @@ async function runDiff(root, haiveDir, contract) {
|
|
|
12766
12808
|
}
|
|
12767
12809
|
}
|
|
12768
12810
|
function detectFormat(filePath) {
|
|
12769
|
-
const ext =
|
|
12770
|
-
const base =
|
|
12811
|
+
const ext = path40.extname(filePath).toLowerCase();
|
|
12812
|
+
const base = path40.basename(filePath).toLowerCase();
|
|
12771
12813
|
if (ext === ".yaml" || ext === ".yml" || ext === ".json") {
|
|
12772
12814
|
if (base.includes("openapi") || base.includes("swagger")) return "openapi";
|
|
12773
12815
|
if (base.includes("schema") || base.includes("graphql")) return "graphql";
|
|
@@ -12782,7 +12824,7 @@ function detectFormat(filePath) {
|
|
|
12782
12824
|
// src/commands/hub.ts
|
|
12783
12825
|
import { existsSync as existsSync61 } from "fs";
|
|
12784
12826
|
import { mkdir as mkdir17, readFile as readFile19, writeFile as writeFile29, copyFile } from "fs/promises";
|
|
12785
|
-
import
|
|
12827
|
+
import path41 from "path";
|
|
12786
12828
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
12787
12829
|
import "commander";
|
|
12788
12830
|
import {
|
|
@@ -12801,7 +12843,7 @@ function registerHub(program2) {
|
|
|
12801
12843
|
hub.command("init <hubPath>").description(
|
|
12802
12844
|
"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"
|
|
12803
12845
|
).action(async (hubPath) => {
|
|
12804
|
-
const absPath =
|
|
12846
|
+
const absPath = path41.resolve(hubPath);
|
|
12805
12847
|
await mkdir17(absPath, { recursive: true });
|
|
12806
12848
|
const gitCheck = spawnSync5("git", ["rev-parse", "--git-dir"], { cwd: absPath });
|
|
12807
12849
|
if (gitCheck.status !== 0) {
|
|
@@ -12812,10 +12854,10 @@ function registerHub(program2) {
|
|
|
12812
12854
|
return;
|
|
12813
12855
|
}
|
|
12814
12856
|
}
|
|
12815
|
-
const sharedDir =
|
|
12857
|
+
const sharedDir = path41.join(absPath, ".ai", "memories", "shared");
|
|
12816
12858
|
await mkdir17(sharedDir, { recursive: true });
|
|
12817
12859
|
await writeFile29(
|
|
12818
|
-
|
|
12860
|
+
path41.join(absPath, ".ai", "README.md"),
|
|
12819
12861
|
`# hAIve Team Knowledge Hub
|
|
12820
12862
|
|
|
12821
12863
|
This repo is a shared knowledge hub for hAIve.
|
|
@@ -12837,7 +12879,7 @@ haive hub pull # import into a project
|
|
|
12837
12879
|
"utf8"
|
|
12838
12880
|
);
|
|
12839
12881
|
await writeFile29(
|
|
12840
|
-
|
|
12882
|
+
path41.join(absPath, ".gitignore"),
|
|
12841
12883
|
".ai/.cache/\n.ai/memories/personal/\n",
|
|
12842
12884
|
"utf8"
|
|
12843
12885
|
);
|
|
@@ -12852,7 +12894,7 @@ haive hub pull # import into a project
|
|
|
12852
12894
|
`
|
|
12853
12895
|
Next steps:
|
|
12854
12896
|
1. Add hubPath to your project's .ai/haive.config.json:
|
|
12855
|
-
{ "hubPath": "${
|
|
12897
|
+
{ "hubPath": "${path41.relative(process.cwd(), absPath)}" }
|
|
12856
12898
|
2. Run \`haive hub push\` to publish your shared memories
|
|
12857
12899
|
3. Share ${absPath} with teammates (git remote, NFS, etc.)
|
|
12858
12900
|
`
|
|
@@ -12881,14 +12923,14 @@ Next steps:
|
|
|
12881
12923
|
process.exitCode = 1;
|
|
12882
12924
|
return;
|
|
12883
12925
|
}
|
|
12884
|
-
const hubRoot =
|
|
12926
|
+
const hubRoot = path41.resolve(root, config.hubPath);
|
|
12885
12927
|
if (!existsSync61(hubRoot)) {
|
|
12886
12928
|
ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
|
|
12887
12929
|
process.exitCode = 1;
|
|
12888
12930
|
return;
|
|
12889
12931
|
}
|
|
12890
|
-
const projectName =
|
|
12891
|
-
const destDir =
|
|
12932
|
+
const projectName = path41.basename(root);
|
|
12933
|
+
const destDir = path41.join(hubRoot, ".ai", "memories", "shared", projectName);
|
|
12892
12934
|
await mkdir17(destDir, { recursive: true });
|
|
12893
12935
|
const all = await loadMemoriesFromDir30(paths.memoriesDir);
|
|
12894
12936
|
const shared = all.filter(
|
|
@@ -12907,7 +12949,7 @@ Next steps:
|
|
|
12907
12949
|
for (const { memory: memory2 } of shared) {
|
|
12908
12950
|
const fm = memory2.frontmatter;
|
|
12909
12951
|
const fileName = `${fm.id}.md`;
|
|
12910
|
-
const destPath =
|
|
12952
|
+
const destPath = path41.join(destDir, fileName);
|
|
12911
12953
|
await writeFile29(destPath, serializeMemory26(memory2), "utf8");
|
|
12912
12954
|
pushed++;
|
|
12913
12955
|
}
|
|
@@ -12915,7 +12957,7 @@ Next steps:
|
|
|
12915
12957
|
console.log(ui.dim(` Location: ${destDir}`));
|
|
12916
12958
|
if (opts.commit) {
|
|
12917
12959
|
const message = opts.message ?? `haive: sync shared memories from ${projectName} (${pushed} memories)`;
|
|
12918
|
-
spawnSync5("git", ["add",
|
|
12960
|
+
spawnSync5("git", ["add", path41.join(".ai", "memories", "shared", projectName)], {
|
|
12919
12961
|
cwd: hubRoot
|
|
12920
12962
|
});
|
|
12921
12963
|
const commit = spawnSync5("git", ["commit", "-m", message], {
|
|
@@ -12950,13 +12992,13 @@ Next steps:
|
|
|
12950
12992
|
process.exitCode = 1;
|
|
12951
12993
|
return;
|
|
12952
12994
|
}
|
|
12953
|
-
const hubRoot =
|
|
12954
|
-
const hubSharedDir =
|
|
12995
|
+
const hubRoot = path41.resolve(root, config.hubPath);
|
|
12996
|
+
const hubSharedDir = path41.join(hubRoot, ".ai", "memories", "shared");
|
|
12955
12997
|
if (!existsSync61(hubSharedDir)) {
|
|
12956
12998
|
ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
|
|
12957
12999
|
return;
|
|
12958
13000
|
}
|
|
12959
|
-
const projectName =
|
|
13001
|
+
const projectName = path41.basename(root);
|
|
12960
13002
|
const { readdir: readdir7 } = await import("fs/promises");
|
|
12961
13003
|
const projectDirs = (await readdir7(hubSharedDir, { withFileTypes: true })).filter((d) => d.isDirectory() && d.name !== projectName).map((d) => d.name);
|
|
12962
13004
|
if (projectDirs.length === 0) {
|
|
@@ -12966,16 +13008,16 @@ Next steps:
|
|
|
12966
13008
|
let totalImported = 0;
|
|
12967
13009
|
let totalUpdated = 0;
|
|
12968
13010
|
for (const sourceName of projectDirs) {
|
|
12969
|
-
const sourceDir =
|
|
12970
|
-
const destDir =
|
|
13011
|
+
const sourceDir = path41.join(hubSharedDir, sourceName);
|
|
13012
|
+
const destDir = path41.join(paths.memoriesDir, "shared", sourceName);
|
|
12971
13013
|
await mkdir17(destDir, { recursive: true });
|
|
12972
13014
|
const sourceFiles = (await readdir7(sourceDir)).filter((f) => f.endsWith(".md"));
|
|
12973
13015
|
const { loadMemoriesFromDir: loadDir } = await import("@hiveai/core");
|
|
12974
13016
|
const existingInDest = await loadDir(destDir);
|
|
12975
13017
|
const existingIds = new Set(existingInDest.map(({ memory: memory2 }) => memory2.frontmatter.id));
|
|
12976
13018
|
for (const file of sourceFiles) {
|
|
12977
|
-
const srcPath =
|
|
12978
|
-
const destPath =
|
|
13019
|
+
const srcPath = path41.join(sourceDir, file);
|
|
13020
|
+
const destPath = path41.join(destDir, file);
|
|
12979
13021
|
const fileContent = await readFile19(srcPath, "utf8");
|
|
12980
13022
|
const alreadyTagged = fileContent.includes(`cross-repo:${sourceName}`);
|
|
12981
13023
|
if (!alreadyTagged) {
|
|
@@ -13006,14 +13048,14 @@ Next steps:
|
|
|
13006
13048
|
console.log(
|
|
13007
13049
|
` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
|
|
13008
13050
|
);
|
|
13009
|
-
const sharedDir =
|
|
13051
|
+
const sharedDir = path41.join(paths.memoriesDir, "shared");
|
|
13010
13052
|
if (existsSync61(sharedDir)) {
|
|
13011
13053
|
const { readdir: readdir7 } = await import("fs/promises");
|
|
13012
13054
|
const sources = (await readdir7(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
13013
13055
|
console.log(`
|
|
13014
13056
|
Imported from ${sources.length} source(s):`);
|
|
13015
13057
|
for (const src of sources) {
|
|
13016
|
-
const files = (await readdir7(
|
|
13058
|
+
const files = (await readdir7(path41.join(sharedDir, src))).filter((f) => f.endsWith(".md"));
|
|
13017
13059
|
console.log(` ${src}: ${files.length} memor${files.length === 1 ? "y" : "ies"}`);
|
|
13018
13060
|
}
|
|
13019
13061
|
} else {
|
|
@@ -13038,7 +13080,7 @@ Next steps:
|
|
|
13038
13080
|
import "commander";
|
|
13039
13081
|
import { existsSync as existsSync63 } from "fs";
|
|
13040
13082
|
import { mkdir as mkdir18, writeFile as writeFile30 } from "fs/promises";
|
|
13041
|
-
import
|
|
13083
|
+
import path43 from "path";
|
|
13042
13084
|
import {
|
|
13043
13085
|
aggregateUsage,
|
|
13044
13086
|
findProjectRoot as findProjectRoot39,
|
|
@@ -13110,7 +13152,7 @@ function registerStats(program2) {
|
|
|
13110
13152
|
});
|
|
13111
13153
|
}
|
|
13112
13154
|
async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
13113
|
-
const outAbs =
|
|
13155
|
+
const outAbs = path43.isAbsolute(outRelative) ? path43.resolve(outRelative) : path43.resolve(root, outRelative);
|
|
13114
13156
|
const size = await usageLogSize(paths);
|
|
13115
13157
|
let events = await readUsageEvents2(paths);
|
|
13116
13158
|
let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
|
|
@@ -13145,7 +13187,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
13145
13187
|
ui.warn("Usage log missing or empty \u2014 report still written with partial data.");
|
|
13146
13188
|
events = [];
|
|
13147
13189
|
}
|
|
13148
|
-
await mkdir18(
|
|
13190
|
+
await mkdir18(path43.dirname(outAbs), { recursive: true });
|
|
13149
13191
|
const payload = {
|
|
13150
13192
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13151
13193
|
project_root: root,
|
|
@@ -13336,7 +13378,7 @@ function summarize(name, t0, payload, notes) {
|
|
|
13336
13378
|
// src/commands/benchmark.ts
|
|
13337
13379
|
import { existsSync as existsSync64 } from "fs";
|
|
13338
13380
|
import { readdir as readdir5, readFile as readFile20, writeFile as writeFile31 } from "fs/promises";
|
|
13339
|
-
import
|
|
13381
|
+
import path44 from "path";
|
|
13340
13382
|
import "commander";
|
|
13341
13383
|
import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot41 } from "@hiveai/core";
|
|
13342
13384
|
function registerBenchmark(program2) {
|
|
@@ -13351,9 +13393,9 @@ function registerBenchmark(program2) {
|
|
|
13351
13393
|
}
|
|
13352
13394
|
const markdown = renderMarkdown(root, summary, rows);
|
|
13353
13395
|
if (opts.out) {
|
|
13354
|
-
const outFile =
|
|
13396
|
+
const outFile = path44.isAbsolute(opts.out) ? opts.out : path44.join(root, opts.out);
|
|
13355
13397
|
await writeFile31(outFile, markdown, "utf8");
|
|
13356
|
-
ui.success(`wrote ${
|
|
13398
|
+
ui.success(`wrote ${path44.relative(process.cwd(), outFile)}`);
|
|
13357
13399
|
return;
|
|
13358
13400
|
}
|
|
13359
13401
|
console.log(markdown);
|
|
@@ -13377,9 +13419,9 @@ function registerBenchmark(program2) {
|
|
|
13377
13419
|
}
|
|
13378
13420
|
function resolveBenchmarkRoot(dir) {
|
|
13379
13421
|
const candidate = dir ?? "benchmarks/agent-benchmark";
|
|
13380
|
-
if (
|
|
13422
|
+
if (path44.isAbsolute(candidate)) return candidate;
|
|
13381
13423
|
const projectRoot = findProjectRoot41(process.cwd());
|
|
13382
|
-
return
|
|
13424
|
+
return path44.join(projectRoot, candidate);
|
|
13383
13425
|
}
|
|
13384
13426
|
async function collectRows(root) {
|
|
13385
13427
|
if (!existsSync64(root)) throw new Error(`Benchmark directory not found: ${root}`);
|
|
@@ -13387,8 +13429,8 @@ async function collectRows(root) {
|
|
|
13387
13429
|
const rows = [];
|
|
13388
13430
|
for (const entry of entries) {
|
|
13389
13431
|
if (!entry.isDirectory()) continue;
|
|
13390
|
-
const fixtureDir =
|
|
13391
|
-
const reportFile =
|
|
13432
|
+
const fixtureDir = path44.join(root, entry.name);
|
|
13433
|
+
const reportFile = path44.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
|
|
13392
13434
|
if (!existsSync64(reportFile)) continue;
|
|
13393
13435
|
const report = await readFile20(reportFile, "utf8");
|
|
13394
13436
|
rows.push(parseAgentReport(entry.name, report));
|
|
@@ -13406,7 +13448,7 @@ function parseAgentReport(fixture, report) {
|
|
|
13406
13448
|
test_iterations: countMatches(section(report, "Test Iterations"), /Iteration\s+\d+|^- /gim),
|
|
13407
13449
|
terminal_failures: countMatches(section(report, "Terminal Errors"), /fail|error|not raised|exited with code 1/gi),
|
|
13408
13450
|
decision_mentions: sectionBulletCount(report, "Key Decisions"),
|
|
13409
|
-
|
|
13451
|
+
report_tokens_est: estimateTokens4(report),
|
|
13410
13452
|
haive_impact: /hAIve Memory Impact[\s\S]*?\b(yes|directly|changed|shaped|confirmed)\b/i.test(report)
|
|
13411
13453
|
};
|
|
13412
13454
|
}
|
|
@@ -13428,7 +13470,7 @@ function summarizeGroup(rows) {
|
|
|
13428
13470
|
test_iterations: sum("test_iterations"),
|
|
13429
13471
|
terminal_failures: sum("terminal_failures"),
|
|
13430
13472
|
decision_mentions: sum("decision_mentions"),
|
|
13431
|
-
|
|
13473
|
+
report_tokens_est: sum("report_tokens_est"),
|
|
13432
13474
|
haive_impact_count: rows.filter((r) => r.haive_impact).length
|
|
13433
13475
|
};
|
|
13434
13476
|
}
|
|
@@ -13440,29 +13482,31 @@ function renderMarkdown(root, summary, rows) {
|
|
|
13440
13482
|
"",
|
|
13441
13483
|
"## Summary",
|
|
13442
13484
|
"",
|
|
13443
|
-
"| Group | Fixtures | Commands | Files read | Files modified | Test iterations | Terminal failures | Decision mentions |
|
|
13485
|
+
"| Group | Fixtures | Commands | Files read | Files modified | Test iterations | Terminal failures | Decision mentions | Report tokens (est, report only) | hAIve impact |",
|
|
13444
13486
|
"| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |",
|
|
13445
13487
|
groupLine("hAIve", summary.haive),
|
|
13446
13488
|
groupLine("Plain", summary.plain),
|
|
13447
13489
|
"",
|
|
13448
13490
|
"## Fixtures",
|
|
13449
13491
|
"",
|
|
13450
|
-
"| Fixture | Group | Commands | Files read | Files modified | Test iterations | Terminal failures | Decisions |
|
|
13492
|
+
"| Fixture | Group | Commands | Files read | Files modified | Test iterations | Terminal failures | Decisions | Report tokens (est, report only) | hAIve impact |",
|
|
13451
13493
|
"| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- |",
|
|
13452
13494
|
...rows.map(
|
|
13453
|
-
(row) => `| \`${row.fixture}\` | ${row.group} | ${row.commands} | ${row.files_read} | ${row.files_modified} | ${row.test_iterations} | ${row.terminal_failures} | ${row.decision_mentions} | ${row.
|
|
13495
|
+
(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"} |`
|
|
13454
13496
|
),
|
|
13455
13497
|
"",
|
|
13456
13498
|
"## Reading",
|
|
13457
13499
|
"",
|
|
13458
|
-
"
|
|
13500
|
+
"`Report tokens (est)` estimates the size of the agent's WRITTEN REPORT only \u2014 a verbosity proxy, NOT",
|
|
13501
|
+
"the agent's total token consumption. For real per-agent token/latency, capture your runner's telemetry",
|
|
13502
|
+
"(e.g. subagent token counts) separately; this report can't see model billing.",
|
|
13459
13503
|
"Use this report to compare relative effort and decision quality, then pair it with final test results and a human review of the diffs.",
|
|
13460
13504
|
""
|
|
13461
13505
|
];
|
|
13462
13506
|
return lines.join("\n");
|
|
13463
13507
|
}
|
|
13464
13508
|
function groupLine(label, group) {
|
|
13465
|
-
return `| ${label} | ${group.fixtures} | ${group.commands} | ${group.files_read} | ${group.files_modified} | ${group.test_iterations} | ${group.terminal_failures} | ${group.decision_mentions} | ${group.
|
|
13509
|
+
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} |`;
|
|
13466
13510
|
}
|
|
13467
13511
|
function sectionBulletCount(markdown, title) {
|
|
13468
13512
|
return countMatches(section(markdown, title), /^- |^\d+\.\s/gm);
|
|
@@ -13481,7 +13525,7 @@ function escapeRegExp(value) {
|
|
|
13481
13525
|
// src/commands/eval.ts
|
|
13482
13526
|
import { mkdir as mkdir19, readFile as readFile21, writeFile as writeFile33 } from "fs/promises";
|
|
13483
13527
|
import { existsSync as existsSync65 } from "fs";
|
|
13484
|
-
import
|
|
13528
|
+
import path45 from "path";
|
|
13485
13529
|
import "commander";
|
|
13486
13530
|
import {
|
|
13487
13531
|
aggregateRetrieval,
|
|
@@ -13577,7 +13621,7 @@ function registerEval(program2) {
|
|
|
13577
13621
|
});
|
|
13578
13622
|
if (!opts.json) ui.success(`Recorded eval score ${report.score}/100 to history.`);
|
|
13579
13623
|
}
|
|
13580
|
-
const baselineFile = opts.baselineFile ?
|
|
13624
|
+
const baselineFile = opts.baselineFile ? path45.isAbsolute(opts.baselineFile) ? opts.baselineFile : path45.join(root, opts.baselineFile) : path45.join(root, ".ai", "eval", "baseline.json");
|
|
13581
13625
|
if (opts.baseline) {
|
|
13582
13626
|
const snapshot = {
|
|
13583
13627
|
saved_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -13586,18 +13630,18 @@ function registerEval(program2) {
|
|
|
13586
13630
|
report,
|
|
13587
13631
|
gate_precision: gatePrecision
|
|
13588
13632
|
};
|
|
13589
|
-
await mkdir19(
|
|
13633
|
+
await mkdir19(path45.dirname(baselineFile), { recursive: true });
|
|
13590
13634
|
await writeFile33(baselineFile, JSON.stringify(snapshot, null, 2), "utf8");
|
|
13591
|
-
if (!opts.json) ui.success(`Saved baseline (score ${report.score}/100) \u2192 ${
|
|
13635
|
+
if (!opts.json) ui.success(`Saved baseline (score ${report.score}/100) \u2192 ${path45.relative(root, baselineFile)}`);
|
|
13592
13636
|
}
|
|
13593
13637
|
let delta = null;
|
|
13594
13638
|
let gateDelta = null;
|
|
13595
13639
|
if (opts.compare || opts.regressionGate) {
|
|
13596
13640
|
if (!existsSync65(baselineFile)) {
|
|
13597
13641
|
if (opts.regressionGate) {
|
|
13598
|
-
if (!opts.json) ui.info(`No baseline at ${
|
|
13642
|
+
if (!opts.json) ui.info(`No baseline at ${path45.relative(root, baselineFile)} \u2014 regression gate skipped. Run \`haive eval --baseline\` to enable it.`);
|
|
13599
13643
|
} else {
|
|
13600
|
-
ui.error(`No baseline at ${
|
|
13644
|
+
ui.error(`No baseline at ${path45.relative(root, baselineFile)}. Run \`haive eval --baseline\` first.`);
|
|
13601
13645
|
process.exitCode = 1;
|
|
13602
13646
|
return;
|
|
13603
13647
|
}
|
|
@@ -13636,9 +13680,9 @@ function registerEval(program2) {
|
|
|
13636
13680
|
}
|
|
13637
13681
|
const md = renderMarkdown2(root, k, resolvedSpec, report, gatePrecision);
|
|
13638
13682
|
if (opts.out) {
|
|
13639
|
-
const outFile =
|
|
13683
|
+
const outFile = path45.isAbsolute(opts.out) ? opts.out : path45.join(root, opts.out);
|
|
13640
13684
|
await writeFile33(outFile, md, "utf8");
|
|
13641
|
-
ui.success(`wrote ${
|
|
13685
|
+
ui.success(`wrote ${path45.relative(process.cwd(), outFile)}`);
|
|
13642
13686
|
} else {
|
|
13643
13687
|
console.log(md);
|
|
13644
13688
|
}
|
|
@@ -13722,12 +13766,12 @@ function countCases(spec) {
|
|
|
13722
13766
|
}
|
|
13723
13767
|
async function resolveSpec(opts, root, memoriesDir) {
|
|
13724
13768
|
if (opts.spec) {
|
|
13725
|
-
const file =
|
|
13769
|
+
const file = path45.resolve(opts.spec);
|
|
13726
13770
|
const raw = await readFile21(file, "utf8");
|
|
13727
13771
|
const spec = JSON.parse(raw);
|
|
13728
13772
|
return { spec, source: file, synthesized: 0, authored: countCases(spec) };
|
|
13729
13773
|
}
|
|
13730
|
-
const defaultSpec =
|
|
13774
|
+
const defaultSpec = path45.join(root, ".ai", "eval", "spec.json");
|
|
13731
13775
|
if (existsSync65(defaultSpec)) {
|
|
13732
13776
|
const raw = await readFile21(defaultSpec, "utf8");
|
|
13733
13777
|
const explicit = JSON.parse(raw);
|
|
@@ -13848,7 +13892,7 @@ function renderMarkdown2(root, k, resolved, report, gatePrecision) {
|
|
|
13848
13892
|
// src/commands/memory-suggest.ts
|
|
13849
13893
|
import { mkdir as mkdir20, writeFile as writeFile34 } from "fs/promises";
|
|
13850
13894
|
import { existsSync as existsSync66 } from "fs";
|
|
13851
|
-
import
|
|
13895
|
+
import path46 from "path";
|
|
13852
13896
|
import "commander";
|
|
13853
13897
|
import {
|
|
13854
13898
|
aggregateUsage as aggregateUsage2,
|
|
@@ -13949,13 +13993,13 @@ function registerMemorySuggest(memory2) {
|
|
|
13949
13993
|
});
|
|
13950
13994
|
const body = renderTemplate(s, fm.id, status);
|
|
13951
13995
|
const file = memoryFilePath11(paths, fm.scope, fm.id, fm.module);
|
|
13952
|
-
await mkdir20(
|
|
13996
|
+
await mkdir20(path46.dirname(file), { recursive: true });
|
|
13953
13997
|
if (existsSync66(file)) {
|
|
13954
|
-
skipped.push({ query: s.query, reason: `file already exists at ${
|
|
13998
|
+
skipped.push({ query: s.query, reason: `file already exists at ${path46.relative(root, file)}` });
|
|
13955
13999
|
continue;
|
|
13956
14000
|
}
|
|
13957
14001
|
await writeFile34(file, serializeMemory27({ frontmatter: fm, body }), "utf8");
|
|
13958
|
-
created.push({ id: fm.id, file:
|
|
14002
|
+
created.push({ id: fm.id, file: path46.relative(root, file), query: s.query });
|
|
13959
14003
|
}
|
|
13960
14004
|
if (opts.json) {
|
|
13961
14005
|
console.log(JSON.stringify({ created, skipped }, null, 2));
|
|
@@ -14055,7 +14099,7 @@ function truncate2(text, max) {
|
|
|
14055
14099
|
// src/commands/memory-archive.ts
|
|
14056
14100
|
import { existsSync as existsSync67 } from "fs";
|
|
14057
14101
|
import { writeFile as writeFile35 } from "fs/promises";
|
|
14058
|
-
import
|
|
14102
|
+
import path47 from "path";
|
|
14059
14103
|
import "commander";
|
|
14060
14104
|
import {
|
|
14061
14105
|
findProjectRoot as findProjectRoot44,
|
|
@@ -14099,7 +14143,7 @@ function registerMemoryArchive(memory2) {
|
|
|
14099
14143
|
if (fm.status === "deprecated" || fm.status === "rejected") continue;
|
|
14100
14144
|
const retired = retirementSignal2(fm, mem.body);
|
|
14101
14145
|
const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
|
|
14102
|
-
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync67(
|
|
14146
|
+
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync67(path47.join(paths.root, p)));
|
|
14103
14147
|
const isAnchorless = !hasAnyAnchor;
|
|
14104
14148
|
if (!retired.retired && !opts.unread && !isAnchorless && !allPathsGone) continue;
|
|
14105
14149
|
const u = getUsage21(usage, fm.id);
|
|
@@ -14176,7 +14220,7 @@ function parseDays(input) {
|
|
|
14176
14220
|
// src/commands/doctor.ts
|
|
14177
14221
|
import { existsSync as existsSync68, statSync as statSync2 } from "fs";
|
|
14178
14222
|
import { readFile as readFile23, stat, writeFile as writeFile36 } from "fs/promises";
|
|
14179
|
-
import
|
|
14223
|
+
import path48 from "path";
|
|
14180
14224
|
import { execFileSync, execSync as execSync3 } from "child_process";
|
|
14181
14225
|
import "commander";
|
|
14182
14226
|
import {
|
|
@@ -14401,7 +14445,7 @@ function registerDoctor(program2) {
|
|
|
14401
14445
|
}
|
|
14402
14446
|
}
|
|
14403
14447
|
if (config.enforcement?.requireBriefingFirst) {
|
|
14404
|
-
const claudeSettings =
|
|
14448
|
+
const claudeSettings = path48.join(root, ".claude", "settings.local.json");
|
|
14405
14449
|
let hasClaudeEnforcement = false;
|
|
14406
14450
|
if (existsSync68(claudeSettings)) {
|
|
14407
14451
|
try {
|
|
@@ -14429,7 +14473,7 @@ function registerDoctor(program2) {
|
|
|
14429
14473
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
|
|
14430
14474
|
});
|
|
14431
14475
|
}
|
|
14432
|
-
findings.push(...await collectInstallFindings(root, "0.
|
|
14476
|
+
findings.push(...await collectInstallFindings(root, "0.25.0"));
|
|
14433
14477
|
findings.push(...await collectToolchainFindings(root));
|
|
14434
14478
|
try {
|
|
14435
14479
|
const legacyRaw = execSync3("haive-mcp --version", {
|
|
@@ -14437,7 +14481,7 @@ function registerDoctor(program2) {
|
|
|
14437
14481
|
timeout: 3e3,
|
|
14438
14482
|
stdio: ["ignore", "pipe", "ignore"]
|
|
14439
14483
|
}).trim();
|
|
14440
|
-
const cliVersion = "0.
|
|
14484
|
+
const cliVersion = "0.25.0";
|
|
14441
14485
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
14442
14486
|
findings.push({
|
|
14443
14487
|
severity: "warn",
|
|
@@ -14453,9 +14497,9 @@ npm uninstall -g @hiveai/mcp`
|
|
|
14453
14497
|
}
|
|
14454
14498
|
{
|
|
14455
14499
|
const configPaths = [
|
|
14456
|
-
|
|
14457
|
-
|
|
14458
|
-
|
|
14500
|
+
path48.join(root, ".mcp.json"),
|
|
14501
|
+
path48.join(root, ".cursor", "mcp.json"),
|
|
14502
|
+
path48.join(root, ".vscode", "mcp.json")
|
|
14459
14503
|
];
|
|
14460
14504
|
const staleConfigs = [];
|
|
14461
14505
|
for (const cfgPath of configPaths) {
|
|
@@ -14463,7 +14507,7 @@ npm uninstall -g @hiveai/mcp`
|
|
|
14463
14507
|
try {
|
|
14464
14508
|
const raw = await readFile23(cfgPath, "utf8");
|
|
14465
14509
|
if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
|
|
14466
|
-
staleConfigs.push(
|
|
14510
|
+
staleConfigs.push(path48.relative(root, cfgPath));
|
|
14467
14511
|
if (opts.fix && !opts.dryRun) {
|
|
14468
14512
|
const updated = raw.replace(/"command"\s*:\s*"haive-mcp"/g, '"command": "haive"').replace(/"args"\s*:\s*\[\]/g, '"args": ["mcp", "--stdio"]');
|
|
14469
14513
|
await writeFile36(cfgPath, updated, "utf8");
|
|
@@ -14754,7 +14798,7 @@ which -a haive`
|
|
|
14754
14798
|
".vscode/mcp.json"
|
|
14755
14799
|
];
|
|
14756
14800
|
for (const rel of integrationFiles) {
|
|
14757
|
-
const file =
|
|
14801
|
+
const file = path48.join(root, rel);
|
|
14758
14802
|
if (!existsSync68(file)) continue;
|
|
14759
14803
|
const text = await readFile23(file, "utf8").catch(() => "");
|
|
14760
14804
|
for (const bin of extractAbsoluteHaiveBins(text)) {
|
|
@@ -14781,7 +14825,7 @@ which -a haive`
|
|
|
14781
14825
|
async function collectToolchainFindings(root) {
|
|
14782
14826
|
const findings = [];
|
|
14783
14827
|
const pkg = await readJson(
|
|
14784
|
-
|
|
14828
|
+
path48.join(root, "package.json")
|
|
14785
14829
|
);
|
|
14786
14830
|
const wantsPnpm = pkg?.packageManager?.startsWith("pnpm@") || Object.values(pkg?.scripts ?? {}).some((script) => /\bpnpm\b/.test(script));
|
|
14787
14831
|
if (!wantsPnpm) return findings;
|
|
@@ -14800,9 +14844,9 @@ async function collectToolchainFindings(root) {
|
|
|
14800
14844
|
}
|
|
14801
14845
|
async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
14802
14846
|
const findings = [];
|
|
14803
|
-
const isHaiveWorkspace = (await readJson(
|
|
14847
|
+
const isHaiveWorkspace = (await readJson(path48.join(root, "package.json")))?.name === "haive-monorepo";
|
|
14804
14848
|
if (!isHaiveWorkspace) return findings;
|
|
14805
|
-
const cliDist =
|
|
14849
|
+
const cliDist = path48.join(root, "packages/cli/dist/index.js");
|
|
14806
14850
|
if (!existsSync68(cliDist)) {
|
|
14807
14851
|
findings.push({
|
|
14808
14852
|
severity: "warn",
|
|
@@ -14827,7 +14871,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
|
14827
14871
|
"packages/core/src/index.ts",
|
|
14828
14872
|
"packages/mcp/src/server.ts",
|
|
14829
14873
|
"packages/cli/src/index.ts"
|
|
14830
|
-
].map((rel) =>
|
|
14874
|
+
].map((rel) => path48.join(root, rel)).filter(existsSync68);
|
|
14831
14875
|
if (sourceFiles.length > 0) {
|
|
14832
14876
|
const distMtime = statSync2(cliDist).mtimeMs;
|
|
14833
14877
|
const newestSource = Math.max(...sourceFiles.map((file) => statSync2(file).mtimeMs));
|
|
@@ -14845,7 +14889,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
|
14845
14889
|
}
|
|
14846
14890
|
async function collectWorkspaceVersionFindings(root, expectedVersion) {
|
|
14847
14891
|
const findings = [];
|
|
14848
|
-
const rootPkg = await readJson(
|
|
14892
|
+
const rootPkg = await readJson(path48.join(root, "package.json"));
|
|
14849
14893
|
const workspacePackages = [
|
|
14850
14894
|
"packages/core/package.json",
|
|
14851
14895
|
"packages/embeddings/package.json",
|
|
@@ -14854,7 +14898,7 @@ async function collectWorkspaceVersionFindings(root, expectedVersion) {
|
|
|
14854
14898
|
];
|
|
14855
14899
|
const existing = (await Promise.all(workspacePackages.map(async (rel) => ({
|
|
14856
14900
|
rel,
|
|
14857
|
-
pkg: await readJson(
|
|
14901
|
+
pkg: await readJson(path48.join(root, rel))
|
|
14858
14902
|
})))).filter((item) => item.pkg);
|
|
14859
14903
|
const isHaiveWorkspace = rootPkg?.name === "haive-monorepo" || existing.some((item) => item.pkg?.name?.startsWith("@hiveai/"));
|
|
14860
14904
|
if (!isHaiveWorkspace) return findings;
|
|
@@ -15359,21 +15403,21 @@ function registerMemorySuggestTopic(memory2) {
|
|
|
15359
15403
|
}
|
|
15360
15404
|
|
|
15361
15405
|
// src/commands/resolve-project.ts
|
|
15362
|
-
import
|
|
15406
|
+
import path49 from "path";
|
|
15363
15407
|
import "commander";
|
|
15364
15408
|
import { resolveProjectInfo as resolveProjectInfo2 } from "@hiveai/core";
|
|
15365
15409
|
function registerResolveProject(program2) {
|
|
15366
15410
|
program2.command("resolve-project").description(
|
|
15367
15411
|
"Print JSON for hAIve project root resolution (HAIVE_PROJECT_ROOT, markers, .ai layout)."
|
|
15368
15412
|
).option("-d, --dir <dir>", "working directory", process.cwd()).action((opts) => {
|
|
15369
|
-
const info = resolveProjectInfo2({ cwd:
|
|
15413
|
+
const info = resolveProjectInfo2({ cwd: path49.resolve(opts.dir) });
|
|
15370
15414
|
console.log(JSON.stringify({ ok: true, info }, null, 2));
|
|
15371
15415
|
});
|
|
15372
15416
|
}
|
|
15373
15417
|
|
|
15374
15418
|
// src/commands/runtime-journal.ts
|
|
15375
15419
|
import { existsSync as existsSync71 } from "fs";
|
|
15376
|
-
import
|
|
15420
|
+
import path50 from "path";
|
|
15377
15421
|
import "commander";
|
|
15378
15422
|
import {
|
|
15379
15423
|
appendRuntimeJournalEntry as appendRuntimeJournalEntry3,
|
|
@@ -15387,15 +15431,15 @@ function registerRuntime(program2) {
|
|
|
15387
15431
|
);
|
|
15388
15432
|
const journal = runtime.command("journal").description("Append or read the machine-local session journal (NDJSON)");
|
|
15389
15433
|
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) => {
|
|
15390
|
-
const root =
|
|
15434
|
+
const root = path50.resolve(opts.dir ?? process.cwd());
|
|
15391
15435
|
const paths = resolveHaivePaths45(findProjectRoot49(root));
|
|
15392
15436
|
const raw = opts.kind ?? "note";
|
|
15393
15437
|
const kind = ["note", "session_end", "mcp"].includes(raw) ? raw : "note";
|
|
15394
15438
|
await appendRuntimeJournalEntry3(paths, { kind, message });
|
|
15395
|
-
ui.success(`Appended to ${
|
|
15439
|
+
ui.success(`Appended to ${path50.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
|
|
15396
15440
|
});
|
|
15397
15441
|
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) => {
|
|
15398
|
-
const root =
|
|
15442
|
+
const root = path50.resolve(opts.dir ?? process.cwd());
|
|
15399
15443
|
const paths = resolveHaivePaths45(findProjectRoot49(root));
|
|
15400
15444
|
const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
|
|
15401
15445
|
if (!existsSync71(paths.haiveDir)) {
|
|
@@ -15414,7 +15458,7 @@ function registerRuntime(program2) {
|
|
|
15414
15458
|
|
|
15415
15459
|
// src/commands/memory-timeline.ts
|
|
15416
15460
|
import { existsSync as existsSync73 } from "fs";
|
|
15417
|
-
import
|
|
15461
|
+
import path51 from "path";
|
|
15418
15462
|
import "commander";
|
|
15419
15463
|
import {
|
|
15420
15464
|
collectTimelineEntries as collectTimelineEntries2,
|
|
@@ -15430,7 +15474,7 @@ function registerMemoryTimeline(memory2) {
|
|
|
15430
15474
|
process.exitCode = 1;
|
|
15431
15475
|
return;
|
|
15432
15476
|
}
|
|
15433
|
-
const root =
|
|
15477
|
+
const root = path51.resolve(opts.dir ?? process.cwd());
|
|
15434
15478
|
const paths = resolveHaivePaths46(findProjectRoot50(root));
|
|
15435
15479
|
if (!existsSync73(paths.memoriesDir)) {
|
|
15436
15480
|
ui.error("No memories \u2014 run `haive init`.");
|
|
@@ -15451,7 +15495,7 @@ function registerMemoryTimeline(memory2) {
|
|
|
15451
15495
|
|
|
15452
15496
|
// src/commands/memory-conflict-candidates.ts
|
|
15453
15497
|
import { existsSync as existsSync74 } from "fs";
|
|
15454
|
-
import
|
|
15498
|
+
import path53 from "path";
|
|
15455
15499
|
import "commander";
|
|
15456
15500
|
import {
|
|
15457
15501
|
findLexicalConflictPairs as findLexicalConflictPairs2,
|
|
@@ -15473,7 +15517,7 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
15473
15517
|
"decision,architecture,convention,gotcha (lexical scan)",
|
|
15474
15518
|
"decision,architecture"
|
|
15475
15519
|
).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) => {
|
|
15476
|
-
const root =
|
|
15520
|
+
const root = path53.resolve(opts.dir ?? process.cwd());
|
|
15477
15521
|
const paths = resolveHaivePaths47(findProjectRoot51(root));
|
|
15478
15522
|
if (!existsSync74(paths.memoriesDir)) {
|
|
15479
15523
|
ui.error("No memories \u2014 run `haive init`.");
|
|
@@ -15513,7 +15557,7 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
15513
15557
|
import { execFile as execFile3, execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
|
|
15514
15558
|
import { existsSync as existsSync75, statSync as statSync3 } from "fs";
|
|
15515
15559
|
import { chmod as chmod2, mkdir as mkdir21, readFile as readFile24, readdir as readdir6, rm as rm3, writeFile as writeFile37 } from "fs/promises";
|
|
15516
|
-
import
|
|
15560
|
+
import path54 from "path";
|
|
15517
15561
|
import { promisify as promisify3 } from "util";
|
|
15518
15562
|
import "commander";
|
|
15519
15563
|
import {
|
|
@@ -15573,7 +15617,7 @@ function registerEnforce(program2) {
|
|
|
15573
15617
|
if (opts.claude !== false) {
|
|
15574
15618
|
try {
|
|
15575
15619
|
const result = await installClaudeHooksAtPath(defaultClaudeSettingsPath("project", root));
|
|
15576
|
-
ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${
|
|
15620
|
+
ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path54.relative(root, result.settingsPath)})`);
|
|
15577
15621
|
} catch (err) {
|
|
15578
15622
|
ui.warn(`Claude Code hooks not installed: ${err instanceof Error ? err.message : String(err)}`);
|
|
15579
15623
|
}
|
|
@@ -15594,19 +15638,19 @@ function registerEnforce(program2) {
|
|
|
15594
15638
|
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) => {
|
|
15595
15639
|
const root = findProjectRoot52(opts.dir);
|
|
15596
15640
|
const paths = resolveHaivePaths48(root);
|
|
15597
|
-
const cacheDir =
|
|
15641
|
+
const cacheDir = path54.join(paths.haiveDir, ".cache");
|
|
15598
15642
|
if (existsSync75(cacheDir)) {
|
|
15599
|
-
if (opts.dryRun) ui.info(`would clean ${
|
|
15643
|
+
if (opts.dryRun) ui.info(`would clean ${path54.relative(root, cacheDir)} (preserving .gitignore)`);
|
|
15600
15644
|
else {
|
|
15601
15645
|
const removed = await cleanupCacheDir(cacheDir);
|
|
15602
|
-
ui.success(`cleaned ${
|
|
15646
|
+
ui.success(`cleaned ${path54.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
|
|
15603
15647
|
}
|
|
15604
15648
|
}
|
|
15605
15649
|
if (existsSync75(paths.runtimeDir)) {
|
|
15606
|
-
if (opts.dryRun) ui.info(`would clean ${
|
|
15650
|
+
if (opts.dryRun) ui.info(`would clean ${path54.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
|
|
15607
15651
|
else {
|
|
15608
15652
|
const removed = await cleanupRuntimeDir(paths.runtimeDir);
|
|
15609
|
-
ui.success(`cleaned ${
|
|
15653
|
+
ui.success(`cleaned ${path54.relative(root, paths.runtimeDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
|
|
15610
15654
|
}
|
|
15611
15655
|
}
|
|
15612
15656
|
});
|
|
@@ -15959,7 +16003,7 @@ async function buildFinishReport(dir) {
|
|
|
15959
16003
|
async function checkFailureCapture(paths, config) {
|
|
15960
16004
|
const gate = config.enforcement?.failureCaptureGate ?? "warn";
|
|
15961
16005
|
if (gate === "off") return [];
|
|
15962
|
-
const obsFile =
|
|
16006
|
+
const obsFile = path54.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
15963
16007
|
if (!existsSync75(obsFile)) return [];
|
|
15964
16008
|
const failures = [];
|
|
15965
16009
|
try {
|
|
@@ -16031,7 +16075,7 @@ async function runWithEnforcement(command, args, opts) {
|
|
|
16031
16075
|
process.exit(2);
|
|
16032
16076
|
}
|
|
16033
16077
|
ui.info(`hAIve briefing marker created for wrapped agent session: ${sessionId}`);
|
|
16034
|
-
ui.info(`Briefing written to ${
|
|
16078
|
+
ui.info(`Briefing written to ${path54.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
|
|
16035
16079
|
const child = spawn6(command, args, {
|
|
16036
16080
|
cwd: root,
|
|
16037
16081
|
stdio: "inherit",
|
|
@@ -16081,9 +16125,9 @@ async function writeWrapperBriefing(paths, sessionId, task) {
|
|
|
16081
16125
|
source: "haive-run",
|
|
16082
16126
|
memoryIds: briefing.memories.map((m) => m.id)
|
|
16083
16127
|
});
|
|
16084
|
-
const dir =
|
|
16128
|
+
const dir = path54.join(paths.runtimeDir, "enforcement", "briefings");
|
|
16085
16129
|
await mkdir21(dir, { recursive: true });
|
|
16086
|
-
const file =
|
|
16130
|
+
const file = path54.join(dir, `${sessionId}.md`);
|
|
16087
16131
|
const parts = [
|
|
16088
16132
|
"# hAIve Briefing",
|
|
16089
16133
|
"",
|
|
@@ -16141,7 +16185,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
16141
16185
|
findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
|
|
16142
16186
|
});
|
|
16143
16187
|
}
|
|
16144
|
-
findings.push(...await inspectIntegrationVersions(root, "0.
|
|
16188
|
+
findings.push(...await inspectIntegrationVersions(root, "0.25.0"));
|
|
16145
16189
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
16146
16190
|
const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
|
|
16147
16191
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
|
|
@@ -16292,7 +16336,7 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
|
16292
16336
|
const consulted = new Set(marker?.memory_ids ?? []);
|
|
16293
16337
|
const missing = relevant.filter(({ memory: memory2, filePath }) => {
|
|
16294
16338
|
if (consulted.has(memory2.frontmatter.id)) return false;
|
|
16295
|
-
if (changedSet.has(
|
|
16339
|
+
if (changedSet.has(path54.relative(paths.root, filePath))) return false;
|
|
16296
16340
|
return true;
|
|
16297
16341
|
}).map(({ memory: memory2 }) => memory2);
|
|
16298
16342
|
if (missing.length === 0) {
|
|
@@ -16500,16 +16544,16 @@ async function cleanupRuntimeDir(runtimeDir) {
|
|
|
16500
16544
|
for (const entry of entries) {
|
|
16501
16545
|
if (entry.name === ".gitignore" || entry.name === "README.md") continue;
|
|
16502
16546
|
if (entry.name === "enforcement") {
|
|
16503
|
-
removed += await cleanupEnforcementDir(
|
|
16547
|
+
removed += await cleanupEnforcementDir(path54.join(runtimeDir, entry.name));
|
|
16504
16548
|
continue;
|
|
16505
16549
|
}
|
|
16506
|
-
await rm3(
|
|
16550
|
+
await rm3(path54.join(runtimeDir, entry.name), { recursive: true, force: true });
|
|
16507
16551
|
removed++;
|
|
16508
16552
|
}
|
|
16509
|
-
await writeFile37(
|
|
16510
|
-
if (!existsSync75(
|
|
16553
|
+
await writeFile37(path54.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
|
|
16554
|
+
if (!existsSync75(path54.join(runtimeDir, "README.md"))) {
|
|
16511
16555
|
await writeFile37(
|
|
16512
|
-
|
|
16556
|
+
path54.join(runtimeDir, "README.md"),
|
|
16513
16557
|
"# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. hAIve cleanup preserves briefing markers so enforcement state remains valid.\n",
|
|
16514
16558
|
"utf8"
|
|
16515
16559
|
);
|
|
@@ -16522,10 +16566,10 @@ async function cleanupCacheDir(cacheDir) {
|
|
|
16522
16566
|
const entries = await readdir6(cacheDir, { withFileTypes: true }).catch(() => []);
|
|
16523
16567
|
for (const entry of entries) {
|
|
16524
16568
|
if (entry.name === ".gitignore") continue;
|
|
16525
|
-
await rm3(
|
|
16569
|
+
await rm3(path54.join(cacheDir, entry.name), { recursive: true, force: true });
|
|
16526
16570
|
removed++;
|
|
16527
16571
|
}
|
|
16528
|
-
await writeFile37(
|
|
16572
|
+
await writeFile37(path54.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
|
|
16529
16573
|
return removed;
|
|
16530
16574
|
}
|
|
16531
16575
|
async function cleanupEnforcementDir(enforcementDir) {
|
|
@@ -16533,7 +16577,7 @@ async function cleanupEnforcementDir(enforcementDir) {
|
|
|
16533
16577
|
const entries = await readdir6(enforcementDir, { withFileTypes: true }).catch(() => []);
|
|
16534
16578
|
for (const entry of entries) {
|
|
16535
16579
|
if (entry.name === "briefings") continue;
|
|
16536
|
-
await rm3(
|
|
16580
|
+
await rm3(path54.join(enforcementDir, entry.name), { recursive: true, force: true });
|
|
16537
16581
|
removed++;
|
|
16538
16582
|
}
|
|
16539
16583
|
return removed;
|
|
@@ -16549,7 +16593,7 @@ async function inspectIntegrationVersions(root, expectedVersion) {
|
|
|
16549
16593
|
];
|
|
16550
16594
|
const findings = [];
|
|
16551
16595
|
for (const rel of files) {
|
|
16552
|
-
const file =
|
|
16596
|
+
const file = path54.join(root, rel);
|
|
16553
16597
|
if (!existsSync75(file)) continue;
|
|
16554
16598
|
const text = await readFile24(file, "utf8").catch(() => "");
|
|
16555
16599
|
for (const bin of extractAbsoluteHaiveBins2(text)) {
|
|
@@ -16765,7 +16809,7 @@ function isShippablePath(file) {
|
|
|
16765
16809
|
}
|
|
16766
16810
|
var CI_SKIP_DIRECTIVE = /\[skip ci\]|\[ci skip\]|\[no ci\]|\[skip actions\]|\*\*\*NO_CI\*\*\*|skip-checks: *true/i;
|
|
16767
16811
|
async function checkCommitMessageSkipCi(root, msgfile) {
|
|
16768
|
-
const file =
|
|
16812
|
+
const file = path54.isAbsolute(msgfile) ? msgfile : path54.join(root, msgfile);
|
|
16769
16813
|
const raw = await readFile24(file, "utf8").catch(() => "");
|
|
16770
16814
|
const cleaned = raw.split("\n").filter((line) => !line.startsWith("#")).join("\n");
|
|
16771
16815
|
if (!CI_SKIP_DIRECTIVE.test(cleaned)) return { block: false, message: "" };
|
|
@@ -16794,7 +16838,7 @@ async function inspectReleaseVersionState(root, upstream) {
|
|
|
16794
16838
|
}
|
|
16795
16839
|
async function readPackageVersion(root, relPath) {
|
|
16796
16840
|
try {
|
|
16797
|
-
const data = JSON.parse(await readFile24(
|
|
16841
|
+
const data = JSON.parse(await readFile24(path54.join(root, relPath), "utf8"));
|
|
16798
16842
|
return typeof data.version === "string" ? data.version : void 0;
|
|
16799
16843
|
} catch {
|
|
16800
16844
|
return void 0;
|
|
@@ -16982,8 +17026,8 @@ function buildScore(findings, threshold = 80) {
|
|
|
16982
17026
|
};
|
|
16983
17027
|
}
|
|
16984
17028
|
async function installGitEnforcement(root) {
|
|
16985
|
-
const hooksDir =
|
|
16986
|
-
if (!existsSync75(
|
|
17029
|
+
const hooksDir = path54.join(root, ".git", "hooks");
|
|
17030
|
+
if (!existsSync75(path54.join(root, ".git"))) {
|
|
16987
17031
|
ui.warn("No .git directory found; git enforcement hooks skipped.");
|
|
16988
17032
|
return;
|
|
16989
17033
|
}
|
|
@@ -17012,7 +17056,7 @@ haive enforce commit-msg "$1" --dir . || exit $?
|
|
|
17012
17056
|
}
|
|
17013
17057
|
];
|
|
17014
17058
|
for (const hook of hooks) {
|
|
17015
|
-
const file =
|
|
17059
|
+
const file = path54.join(hooksDir, hook.name);
|
|
17016
17060
|
if (existsSync75(file)) {
|
|
17017
17061
|
const current = await readFile24(file, "utf8").catch(() => "");
|
|
17018
17062
|
if (current.includes(ENFORCE_HOOK_MARKER)) {
|
|
@@ -17030,8 +17074,8 @@ ${hook.body}`, "utf8");
|
|
|
17030
17074
|
ui.success("Installed blocking git enforcement hooks: pre-commit, pre-push, commit-msg");
|
|
17031
17075
|
}
|
|
17032
17076
|
async function installCiEnforcement(root) {
|
|
17033
|
-
const workflowPath =
|
|
17034
|
-
await mkdir21(
|
|
17077
|
+
const workflowPath = path54.join(root, ".github", "workflows", "haive-enforcement.yml");
|
|
17078
|
+
await mkdir21(path54.dirname(workflowPath), { recursive: true });
|
|
17035
17079
|
if (existsSync75(workflowPath)) {
|
|
17036
17080
|
ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
|
|
17037
17081
|
return;
|
|
@@ -17063,7 +17107,7 @@ jobs:
|
|
|
17063
17107
|
HAIVE_HEAD_SHA: \${{ github.event.pull_request.head.sha || github.sha }}
|
|
17064
17108
|
run: haive enforce ci
|
|
17065
17109
|
`, "utf8");
|
|
17066
|
-
ui.success(`Created ${
|
|
17110
|
+
ui.success(`Created ${path54.relative(root, workflowPath)}`);
|
|
17067
17111
|
}
|
|
17068
17112
|
function printReport(report, json, explain = false) {
|
|
17069
17113
|
if (json) {
|
|
@@ -17167,8 +17211,8 @@ function extractToolPaths(payload, root) {
|
|
|
17167
17211
|
}
|
|
17168
17212
|
function normalizeToolPath(file, root) {
|
|
17169
17213
|
const normalized = file.replace(/\\/g, "/");
|
|
17170
|
-
if (!
|
|
17171
|
-
return
|
|
17214
|
+
if (!path54.isAbsolute(normalized)) return normalized.replace(/^\.\//, "");
|
|
17215
|
+
return path54.relative(root, normalized).replace(/\\/g, "/");
|
|
17172
17216
|
}
|
|
17173
17217
|
async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
|
|
17174
17218
|
if (!existsSync75(paths.memoriesDir)) return [];
|
|
@@ -17216,7 +17260,7 @@ async function readStdin2(maxBytes) {
|
|
|
17216
17260
|
}
|
|
17217
17261
|
var ATOMIC_STAGE_EXCLUDE = ["/.usage/", "/.runtime/", "/.cache/"];
|
|
17218
17262
|
async function stageResyncedArtifacts(root, paths) {
|
|
17219
|
-
const aiRel =
|
|
17263
|
+
const aiRel = path54.relative(root, paths.haiveDir);
|
|
17220
17264
|
const out = await runCommand4("git", ["diff", "--name-only", "--", aiRel], root).catch(() => "");
|
|
17221
17265
|
const toStage = out.split("\n").map((line) => line.trim()).filter(Boolean).filter((file) => !ATOMIC_STAGE_EXCLUDE.some((excl) => `/${file}`.includes(excl)));
|
|
17222
17266
|
if (toStage.length === 0) return;
|
|
@@ -17258,7 +17302,7 @@ function registerRun(program2) {
|
|
|
17258
17302
|
import { execFile as execFile4 } from "child_process";
|
|
17259
17303
|
import { existsSync as existsSync76 } from "fs";
|
|
17260
17304
|
import { chmod as chmod3, mkdir as mkdir23, readFile as readFile25, writeFile as writeFile38 } from "fs/promises";
|
|
17261
|
-
import
|
|
17305
|
+
import path55 from "path";
|
|
17262
17306
|
import { promisify as promisify4 } from "util";
|
|
17263
17307
|
import "commander";
|
|
17264
17308
|
import {
|
|
@@ -17303,7 +17347,7 @@ function registerSensors(program2) {
|
|
|
17303
17347
|
const root = findProjectRoot53(opts.dir);
|
|
17304
17348
|
const paths = resolveHaivePaths49(root);
|
|
17305
17349
|
const memories = await runnableSensorMemories(paths);
|
|
17306
|
-
const diff = opts.diffFile ? await readFile25(
|
|
17350
|
+
const diff = opts.diffFile ? await readFile25(path55.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
|
|
17307
17351
|
const targets = sensorTargetsFromDiff3(diff);
|
|
17308
17352
|
const hits = runSensors3(memories, targets.length > 0 ? targets : [{ path: "", content: diff }]);
|
|
17309
17353
|
const config = await loadConfig15(paths);
|
|
@@ -17415,13 +17459,13 @@ function registerSensors(program2) {
|
|
|
17415
17459
|
const root = findProjectRoot53(opts.dir);
|
|
17416
17460
|
const paths = resolveHaivePaths49(root);
|
|
17417
17461
|
const rows = await sensorRows(paths);
|
|
17418
|
-
const outDir =
|
|
17462
|
+
const outDir = path55.resolve(root, opts.outDir ?? ".ai/generated");
|
|
17419
17463
|
await mkdir23(outDir, { recursive: true });
|
|
17420
|
-
const outPath =
|
|
17464
|
+
const outPath = path55.join(outDir, format === "grep" ? "haive-sensors-grep.sh" : "haive-sensors-eslint.json");
|
|
17421
17465
|
const content = format === "grep" ? renderGrepScript(rows) : JSON.stringify({ sensors: rows }, null, 2) + "\n";
|
|
17422
17466
|
await writeFile38(outPath, content, "utf8");
|
|
17423
17467
|
if (format === "grep") await chmod3(outPath, 493);
|
|
17424
|
-
ui.success(`Exported ${rows.length} sensor(s): ${
|
|
17468
|
+
ui.success(`Exported ${rows.length} sensor(s): ${path55.relative(root, outPath)}`);
|
|
17425
17469
|
});
|
|
17426
17470
|
}
|
|
17427
17471
|
async function sensorRows(paths) {
|
|
@@ -17494,7 +17538,7 @@ function shellQuote(value) {
|
|
|
17494
17538
|
// src/commands/ingest.ts
|
|
17495
17539
|
import { existsSync as existsSync77 } from "fs";
|
|
17496
17540
|
import { mkdir as mkdir24, readFile as readFile26, writeFile as writeFile39 } from "fs/promises";
|
|
17497
|
-
import
|
|
17541
|
+
import path56 from "path";
|
|
17498
17542
|
import "commander";
|
|
17499
17543
|
import {
|
|
17500
17544
|
draftsFromFindings as draftsFromFindings2,
|
|
@@ -17551,7 +17595,7 @@ function registerIngest(program2) {
|
|
|
17551
17595
|
process.exitCode = 1;
|
|
17552
17596
|
return;
|
|
17553
17597
|
}
|
|
17554
|
-
const reportPath =
|
|
17598
|
+
const reportPath = path56.resolve(root, file);
|
|
17555
17599
|
if (!existsSync77(reportPath)) {
|
|
17556
17600
|
ui.error(`Report file not found: ${reportPath}`);
|
|
17557
17601
|
process.exitCode = 1;
|
|
@@ -17640,13 +17684,13 @@ function registerIngest(program2) {
|
|
|
17640
17684
|
await writeDraft2(paths, draft);
|
|
17641
17685
|
created++;
|
|
17642
17686
|
}
|
|
17643
|
-
ui.success(`Created ${created} proposed memory(ies) under ${
|
|
17687
|
+
ui.success(`Created ${created} proposed memory(ies) under ${path56.relative(root, paths.memoriesDir)}/`);
|
|
17644
17688
|
ui.info("Review with `haive memory pending`; promote sensors with `haive sensors promote <id> --yes`.");
|
|
17645
17689
|
});
|
|
17646
17690
|
}
|
|
17647
17691
|
async function writeDraft2(paths, draft) {
|
|
17648
17692
|
const file = memoryFilePath12(paths, draft.frontmatter.scope, draft.frontmatter.id, draft.frontmatter.module);
|
|
17649
|
-
await mkdir24(
|
|
17693
|
+
await mkdir24(path56.dirname(file), { recursive: true });
|
|
17650
17694
|
await writeFile39(file, serializeMemory30({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
|
|
17651
17695
|
return file;
|
|
17652
17696
|
}
|
|
@@ -17738,6 +17782,18 @@ function renderDashboard(r) {
|
|
|
17738
17782
|
console.log(` ${ui.dim("scopes:")} ${formatCounts(inv.by_scope)}`);
|
|
17739
17783
|
console.log(` ${ui.dim("types: ")} ${formatCounts(inv.by_type)}`);
|
|
17740
17784
|
console.log();
|
|
17785
|
+
console.log(ui.bold("Value") + ui.dim(" (what hAIve demonstrably earned \u2014 vs its per-task cost)"));
|
|
17786
|
+
const blocked = prevention.trend.last_30d;
|
|
17787
|
+
const demonstrated = impact.high;
|
|
17788
|
+
console.log(
|
|
17789
|
+
` ${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`
|
|
17790
|
+
);
|
|
17791
|
+
console.log(
|
|
17792
|
+
ui.dim(
|
|
17793
|
+
" Cost is real: the briefing adds context to every task; the payoff is downstream (defects/incidents avoided), not the agent's token bill."
|
|
17794
|
+
)
|
|
17795
|
+
);
|
|
17796
|
+
console.log();
|
|
17741
17797
|
console.log(ui.bold("Prevention") + ui.dim(" (caught-for-you outcome)"));
|
|
17742
17798
|
console.log(
|
|
17743
17799
|
` ${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`
|
|
@@ -17825,7 +17881,7 @@ function warnNum(n) {
|
|
|
17825
17881
|
import { execFile as execFile5 } from "child_process";
|
|
17826
17882
|
import { cp, readFile as readFile27 } from "fs/promises";
|
|
17827
17883
|
import { existsSync as existsSync79 } from "fs";
|
|
17828
|
-
import
|
|
17884
|
+
import path57 from "path";
|
|
17829
17885
|
import { promisify as promisify5 } from "util";
|
|
17830
17886
|
import "commander";
|
|
17831
17887
|
import { findProjectRoot as findProjectRoot56 } from "@hiveai/core";
|
|
@@ -17834,7 +17890,7 @@ function registerDevLink(program2) {
|
|
|
17834
17890
|
const dev = program2.commands.find((c) => c.name() === "dev") ?? program2.command("dev").description("Developer utilities for working on hAIve itself.");
|
|
17835
17891
|
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) => {
|
|
17836
17892
|
const root = findProjectRoot56(opts.dir);
|
|
17837
|
-
if (!existsSync79(
|
|
17893
|
+
if (!existsSync79(path57.join(root, "packages", "cli", "dist", "index.js"))) {
|
|
17838
17894
|
ui.error(`Not the hAIve monorepo (no packages/cli/dist) at ${root}. Run \`pnpm -r build\` first, or pass --dir.`);
|
|
17839
17895
|
process.exitCode = 1;
|
|
17840
17896
|
return;
|
|
@@ -17843,9 +17899,9 @@ function registerDevLink(program2) {
|
|
|
17843
17899
|
try {
|
|
17844
17900
|
globalModules = (await exec3("npm", ["root", "-g"])).stdout.trim();
|
|
17845
17901
|
} catch {
|
|
17846
|
-
globalModules =
|
|
17902
|
+
globalModules = path57.join(path57.dirname(path57.dirname(process.execPath)), "lib", "node_modules");
|
|
17847
17903
|
}
|
|
17848
|
-
const globalHive =
|
|
17904
|
+
const globalHive = path57.join(globalModules, "@hiveai");
|
|
17849
17905
|
if (!existsSync79(globalHive)) {
|
|
17850
17906
|
ui.error(`No global @hiveai install at ${globalHive}. Install once with \`npm i -g @hiveai/cli\`, then re-run.`);
|
|
17851
17907
|
process.exitCode = 1;
|
|
@@ -17853,21 +17909,21 @@ function registerDevLink(program2) {
|
|
|
17853
17909
|
}
|
|
17854
17910
|
const linked = [];
|
|
17855
17911
|
const copyDist = async (fromPkg, toDistDir) => {
|
|
17856
|
-
const from =
|
|
17857
|
-
if (!existsSync79(from) || !existsSync79(
|
|
17912
|
+
const from = path57.join(root, "packages", fromPkg, "dist");
|
|
17913
|
+
if (!existsSync79(from) || !existsSync79(path57.dirname(toDistDir))) return;
|
|
17858
17914
|
await cp(from, toDistDir, { recursive: true });
|
|
17859
|
-
linked.push(
|
|
17915
|
+
linked.push(path57.relative(globalModules, toDistDir));
|
|
17860
17916
|
};
|
|
17861
17917
|
for (const pkg of ["cli", "mcp"]) {
|
|
17862
|
-
await copyDist(pkg,
|
|
17918
|
+
await copyDist(pkg, path57.join(globalHive, pkg, "dist"));
|
|
17863
17919
|
for (const nested of ["core", "embeddings"]) {
|
|
17864
|
-
await copyDist(nested,
|
|
17920
|
+
await copyDist(nested, path57.join(globalHive, pkg, "node_modules", "@hiveai", nested, "dist"));
|
|
17865
17921
|
}
|
|
17866
17922
|
}
|
|
17867
|
-
await copyDist("core",
|
|
17923
|
+
await copyDist("core", path57.join(globalHive, "core", "dist"));
|
|
17868
17924
|
let version = "unknown";
|
|
17869
17925
|
try {
|
|
17870
|
-
version = JSON.parse(await readFile27(
|
|
17926
|
+
version = JSON.parse(await readFile27(path57.join(root, "package.json"), "utf8")).version ?? "unknown";
|
|
17871
17927
|
} catch {
|
|
17872
17928
|
}
|
|
17873
17929
|
if (opts.json) {
|
|
@@ -17887,7 +17943,7 @@ function registerDevLink(program2) {
|
|
|
17887
17943
|
// src/commands/coverage.ts
|
|
17888
17944
|
import { readFile as readFile28 } from "fs/promises";
|
|
17889
17945
|
import { existsSync as existsSync80 } from "fs";
|
|
17890
|
-
import
|
|
17946
|
+
import path58 from "path";
|
|
17891
17947
|
import "commander";
|
|
17892
17948
|
import {
|
|
17893
17949
|
findCoverageGaps,
|
|
@@ -17911,7 +17967,7 @@ async function readAgentHotFiles(root, cacheFile, sinceMs) {
|
|
|
17911
17967
|
}
|
|
17912
17968
|
for (const f of obs.files ?? []) {
|
|
17913
17969
|
if (typeof f !== "string" || !f) continue;
|
|
17914
|
-
const rel =
|
|
17970
|
+
const rel = path58.isAbsolute(f) ? path58.relative(root, f) : f;
|
|
17915
17971
|
if (rel.startsWith("..")) continue;
|
|
17916
17972
|
files.push(rel);
|
|
17917
17973
|
}
|
|
@@ -17955,7 +18011,7 @@ function registerCoverage(program2) {
|
|
|
17955
18011
|
}) : null;
|
|
17956
18012
|
const gitHotFiles = (radar?.hotFiles ?? []).filter((h) => !isNoisePath(h.path)).map((h) => ({ path: h.path, changes: h.changes, source: "git" }));
|
|
17957
18013
|
const sinceMs = Date.now() - days * 864e5;
|
|
17958
|
-
const agentHotFiles = useAgent ? (await readAgentHotFiles(root,
|
|
18014
|
+
const agentHotFiles = useAgent ? (await readAgentHotFiles(root, path58.join(paths.haiveDir, ".cache", "observations.jsonl"), sinceMs)).filter((h) => !isNoisePath(h.path)) : [];
|
|
17959
18015
|
const hotFiles = mergeHotFiles(gitHotFiles, agentHotFiles);
|
|
17960
18016
|
const memories = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
17961
18017
|
const gaps = findCoverageGaps(hotFiles, memories, { minChanges, limit });
|
|
@@ -17994,7 +18050,7 @@ function registerCoverage(program2) {
|
|
|
17994
18050
|
// src/commands/merge-driver.ts
|
|
17995
18051
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
17996
18052
|
import { readFileSync, writeFileSync, existsSync as existsSync81 } from "fs";
|
|
17997
|
-
import
|
|
18053
|
+
import path59 from "path";
|
|
17998
18054
|
import "commander";
|
|
17999
18055
|
import { findProjectRoot as findProjectRoot58, mergeMemoryVersions } from "@hiveai/core";
|
|
18000
18056
|
var GITATTRIBUTES_MARK = "# hAIve merge driver";
|
|
@@ -18026,7 +18082,7 @@ function registerMergeDriver(program2) {
|
|
|
18026
18082
|
process.exitCode = 1;
|
|
18027
18083
|
return;
|
|
18028
18084
|
}
|
|
18029
|
-
const gaPath =
|
|
18085
|
+
const gaPath = path59.join(root, ".gitattributes");
|
|
18030
18086
|
let content = existsSync81(gaPath) ? readFileSync(gaPath, "utf8") : "";
|
|
18031
18087
|
if (!content.includes(GITATTRIBUTES_MARK)) {
|
|
18032
18088
|
if (content.length > 0 && !content.endsWith("\n")) content += "\n";
|
|
@@ -18113,7 +18169,7 @@ function registerMemoryResolveConflict(memory2) {
|
|
|
18113
18169
|
import { execFile as execFile6 } from "child_process";
|
|
18114
18170
|
import { mkdir as mkdir25, writeFile as writeFile41 } from "fs/promises";
|
|
18115
18171
|
import { existsSync as existsSync84 } from "fs";
|
|
18116
|
-
import
|
|
18172
|
+
import path60 from "path";
|
|
18117
18173
|
import { promisify as promisify6 } from "util";
|
|
18118
18174
|
import "commander";
|
|
18119
18175
|
import {
|
|
@@ -18175,7 +18231,7 @@ _Seeded from git ${p.kind} commit ${p.source_sha}. Review and validate (or delet
|
|
|
18175
18231
|
`;
|
|
18176
18232
|
const file = memoryFilePath13(paths, fm.scope, fm.id, fm.module);
|
|
18177
18233
|
if (existsSync84(file)) continue;
|
|
18178
|
-
await mkdir25(
|
|
18234
|
+
await mkdir25(path60.dirname(file), { recursive: true });
|
|
18179
18235
|
await writeFile41(file, serializeMemory33({ frontmatter: fm, body }), "utf8");
|
|
18180
18236
|
written += 1;
|
|
18181
18237
|
}
|
|
@@ -18208,7 +18264,7 @@ async function readCommits(root, days) {
|
|
|
18208
18264
|
|
|
18209
18265
|
// src/commands/bridges.ts
|
|
18210
18266
|
import { existsSync as existsSync85 } from "fs";
|
|
18211
|
-
import
|
|
18267
|
+
import path61 from "path";
|
|
18212
18268
|
import "commander";
|
|
18213
18269
|
import {
|
|
18214
18270
|
findProjectRoot as findProjectRoot61,
|
|
@@ -18248,7 +18304,7 @@ function registerBridges(program2) {
|
|
|
18248
18304
|
targets = BRIDGE_TARGETS3;
|
|
18249
18305
|
} else {
|
|
18250
18306
|
targets = BRIDGE_TARGETS3.filter(
|
|
18251
|
-
(t) => existsSync85(
|
|
18307
|
+
(t) => existsSync85(path61.join(root, BRIDGE_TARGET_PATH3[t]))
|
|
18252
18308
|
);
|
|
18253
18309
|
if (targets.length === 0) {
|
|
18254
18310
|
ui.info(
|
|
@@ -18277,7 +18333,7 @@ function registerBridges(program2) {
|
|
|
18277
18333
|
console.log(ui.bold("hAIve bridge targets:"));
|
|
18278
18334
|
for (const target of BRIDGE_TARGETS3) {
|
|
18279
18335
|
const relPath = BRIDGE_TARGET_PATH3[target];
|
|
18280
|
-
const exists = existsSync85(
|
|
18336
|
+
const exists = existsSync85(path61.join(root, relPath));
|
|
18281
18337
|
const marker = exists ? ui.dim("\u2713") : ui.dim("\xB7");
|
|
18282
18338
|
console.log(` ${marker} ${target.padEnd(10)} ${relPath}${exists ? "" : " (not present)"}`);
|
|
18283
18339
|
}
|
|
@@ -18288,7 +18344,7 @@ function registerBridges(program2) {
|
|
|
18288
18344
|
|
|
18289
18345
|
// src/index.ts
|
|
18290
18346
|
var program = new Command64();
|
|
18291
|
-
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.
|
|
18347
|
+
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.25.0").option("--advanced", "show maintenance and experimental commands in help");
|
|
18292
18348
|
registerInit(program);
|
|
18293
18349
|
registerWelcome(program);
|
|
18294
18350
|
registerResolveProject(program);
|