@hiveai/cli 0.9.28 → 0.9.30
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 +717 -443
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command52 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/briefing.ts
|
|
7
7
|
import { existsSync as existsSync3 } from "fs";
|
|
@@ -199,7 +199,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
|
|
|
199
199
|
if (!f) continue;
|
|
200
200
|
counts.set(f, (counts.get(f) ?? 0) + 1);
|
|
201
201
|
}
|
|
202
|
-
let entries = [...counts.entries()].map(([
|
|
202
|
+
let entries = [...counts.entries()].map(([path51, changes]) => ({ path: path51, changes }));
|
|
203
203
|
const lowerPaths = filePaths.map((p) => p.toLowerCase());
|
|
204
204
|
if (lowerPaths.length > 0) {
|
|
205
205
|
entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
|
|
@@ -1213,12 +1213,14 @@ async function loadEmbeddings() {
|
|
|
1213
1213
|
}
|
|
1214
1214
|
|
|
1215
1215
|
// src/commands/index-code.ts
|
|
1216
|
+
import { existsSync as existsSync5, statSync } from "fs";
|
|
1216
1217
|
import path5 from "path";
|
|
1217
1218
|
import "commander";
|
|
1218
1219
|
import {
|
|
1219
1220
|
buildCodeMap as buildCodeMap2,
|
|
1220
1221
|
codeMapPath,
|
|
1221
1222
|
findProjectRoot as findProjectRoot5,
|
|
1223
|
+
loadCodeMap as loadCodeMap4,
|
|
1222
1224
|
resolveHaivePaths as resolveHaivePaths4,
|
|
1223
1225
|
saveCodeMap as saveCodeMap2
|
|
1224
1226
|
} from "@hiveai/core";
|
|
@@ -1228,14 +1230,18 @@ function registerIndexCode(program2) {
|
|
|
1228
1230
|
);
|
|
1229
1231
|
idx.action(() => idx.help());
|
|
1230
1232
|
idx.command("code").description(
|
|
1231
|
-
"Scan source files and write .ai/code-map.json (file \u2192 exports + 1-line description).\n\n Supported languages: TypeScript, JavaScript, Java, Python, Go, Rust, C#, PHP.\n The map is used by:\n \u2022 get_briefing (symbol_locations) \u2014 look up where a class/function lives\n \u2022 code_map MCP tool \u2014 browse exports without grepping\n \u2022 haive briefing --symbols \u2014 look up symbols from the CLI\n\n Run automatically by haive init (autopilot mode) and haive sync (if source changed).\n\n Example:\n haive index code\n haive index code --exclude generated,proto\n"
|
|
1233
|
+
"Scan source files and write .ai/code-map.json (file \u2192 exports + 1-line description).\n\n Supported languages: TypeScript, JavaScript, Java, Python, Go, Rust, C#, PHP.\n The map is used by:\n \u2022 get_briefing (symbol_locations) \u2014 look up where a class/function lives\n \u2022 code_map MCP tool \u2014 browse exports without grepping\n \u2022 haive briefing --symbols \u2014 look up symbols from the CLI\n\n Run automatically by haive init (autopilot mode) and haive sync (if source changed).\n\n Example:\n haive index code\n haive index code --status # report freshness without rebuilding\n haive index code --exclude generated,proto\n"
|
|
1232
1234
|
).option("-d, --dir <dir>", "project root").option(
|
|
1233
1235
|
"--exclude <csv>",
|
|
1234
1236
|
"extra directory names to skip (comma-separated)",
|
|
1235
1237
|
""
|
|
1236
|
-
).action(async (opts) => {
|
|
1238
|
+
).option("--status", "report code-map / code-search index freshness without rebuilding").option("--json", "with --status, emit machine-readable JSON (for CI / agents)").action(async (opts) => {
|
|
1237
1239
|
const root = findProjectRoot5(opts.dir);
|
|
1238
1240
|
const paths = resolveHaivePaths4(root);
|
|
1241
|
+
if (opts.status) {
|
|
1242
|
+
await reportIndexStatus(root, paths, opts.json === true);
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1239
1245
|
const extraExcludes = (opts.exclude ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
1240
1246
|
ui.info(`Indexing source files in ${root}\u2026`);
|
|
1241
1247
|
const map = await buildCodeMap2(root, {
|
|
@@ -1288,10 +1294,49 @@ function registerIndexCode(program2) {
|
|
|
1288
1294
|
}
|
|
1289
1295
|
});
|
|
1290
1296
|
}
|
|
1297
|
+
async function reportIndexStatus(root, paths, asJson) {
|
|
1298
|
+
const mapFile = codeMapPath(paths);
|
|
1299
|
+
const map = existsSync5(mapFile) ? await loadCodeMap4(paths) : null;
|
|
1300
|
+
const fileCount = map ? Object.keys(map.files).length : 0;
|
|
1301
|
+
const exportCount = map ? Object.values(map.files).reduce((s, f) => s + f.exports.length, 0) : 0;
|
|
1302
|
+
const mapMtime = existsSync5(mapFile) ? statSync(mapFile).mtime.toISOString() : null;
|
|
1303
|
+
const searchIndexFile = path5.join(paths.haiveDir, ".cache", "embeddings", "code-embeddings-index.json");
|
|
1304
|
+
const searchIndexPresent = existsSync5(searchIndexFile);
|
|
1305
|
+
const status = {
|
|
1306
|
+
code_map: {
|
|
1307
|
+
present: map !== null,
|
|
1308
|
+
path: path5.relative(root, mapFile),
|
|
1309
|
+
files: fileCount,
|
|
1310
|
+
exports: exportCount,
|
|
1311
|
+
generated_at: map?.generated_at ?? null,
|
|
1312
|
+
file_mtime: mapMtime
|
|
1313
|
+
},
|
|
1314
|
+
code_search_index: {
|
|
1315
|
+
present: searchIndexPresent,
|
|
1316
|
+
path: path5.relative(root, searchIndexFile)
|
|
1317
|
+
}
|
|
1318
|
+
};
|
|
1319
|
+
if (asJson) {
|
|
1320
|
+
console.log(JSON.stringify(status, null, 2));
|
|
1321
|
+
if (!status.code_map.present) process.exitCode = 1;
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
if (!status.code_map.present) {
|
|
1325
|
+
ui.warn(`No code-map at ${status.code_map.path}. Run \`haive index code\`.`);
|
|
1326
|
+
process.exitCode = 1;
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
ui.info(
|
|
1330
|
+
`code-map: ${fileCount} file(s), ${exportCount} export(s) \xB7 generated ${status.code_map.generated_at ?? "?"}`
|
|
1331
|
+
);
|
|
1332
|
+
ui.info(
|
|
1333
|
+
status.code_search_index.present ? `code-search index: present (${status.code_search_index.path})` : `code-search index: missing \u2014 run \`haive index code-search\` for semantic code lookup.`
|
|
1334
|
+
);
|
|
1335
|
+
}
|
|
1291
1336
|
|
|
1292
1337
|
// src/commands/init.ts
|
|
1293
1338
|
import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile6 } from "fs/promises";
|
|
1294
|
-
import { existsSync as
|
|
1339
|
+
import { existsSync as existsSync10 } from "fs";
|
|
1295
1340
|
import path10 from "path";
|
|
1296
1341
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
1297
1342
|
import "commander";
|
|
@@ -1305,7 +1350,7 @@ import {
|
|
|
1305
1350
|
|
|
1306
1351
|
// src/commands/agent.ts
|
|
1307
1352
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1308
|
-
import { existsSync as
|
|
1353
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1309
1354
|
import { mkdir as mkdir3, writeFile as writeFile4 } from "fs/promises";
|
|
1310
1355
|
import os2 from "os";
|
|
1311
1356
|
import path7 from "path";
|
|
@@ -1315,7 +1360,7 @@ import { findProjectRoot as findProjectRoot6, resolveHaivePaths as resolveHaiveP
|
|
|
1315
1360
|
|
|
1316
1361
|
// src/commands/init-mcp-setup.ts
|
|
1317
1362
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
|
|
1318
|
-
import { existsSync as
|
|
1363
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1319
1364
|
import path6 from "path";
|
|
1320
1365
|
import os from "os";
|
|
1321
1366
|
var HOME = os.homedir();
|
|
@@ -1336,9 +1381,9 @@ function cursorMcpPath() {
|
|
|
1336
1381
|
async function configureCursor() {
|
|
1337
1382
|
const mcpPath = cursorMcpPath();
|
|
1338
1383
|
const cursorDir = path6.join(HOME, ".cursor");
|
|
1339
|
-
if (!
|
|
1384
|
+
if (!existsSync6(cursorDir)) return { client: "Cursor", status: "not_installed" };
|
|
1340
1385
|
let config = {};
|
|
1341
|
-
if (
|
|
1386
|
+
if (existsSync6(mcpPath)) {
|
|
1342
1387
|
try {
|
|
1343
1388
|
config = JSON.parse(await readFile3(mcpPath, "utf8"));
|
|
1344
1389
|
} catch {
|
|
@@ -1362,7 +1407,7 @@ function vscodeMcpPath() {
|
|
|
1362
1407
|
path6.join(HOME, ".config", "Code - Insiders", "User", "mcp.json")
|
|
1363
1408
|
];
|
|
1364
1409
|
for (const c of candidates) {
|
|
1365
|
-
if (
|
|
1410
|
+
if (existsSync6(path6.dirname(c))) return c;
|
|
1366
1411
|
}
|
|
1367
1412
|
return null;
|
|
1368
1413
|
}
|
|
@@ -1370,7 +1415,7 @@ async function configureVSCode() {
|
|
|
1370
1415
|
const mcpPath = vscodeMcpPath();
|
|
1371
1416
|
if (!mcpPath) return { client: "VS Code", status: "not_installed" };
|
|
1372
1417
|
let config = {};
|
|
1373
|
-
if (
|
|
1418
|
+
if (existsSync6(mcpPath)) {
|
|
1374
1419
|
try {
|
|
1375
1420
|
config = JSON.parse(await readFile3(mcpPath, "utf8"));
|
|
1376
1421
|
} catch {
|
|
@@ -1385,18 +1430,18 @@ async function configureVSCode() {
|
|
|
1385
1430
|
}
|
|
1386
1431
|
function claudeConfigPath() {
|
|
1387
1432
|
const p = path6.join(HOME, ".claude.json");
|
|
1388
|
-
if (
|
|
1433
|
+
if (existsSync6(p)) return p;
|
|
1389
1434
|
const p2 = path6.join(HOME, ".config", "claude", "claude.json");
|
|
1390
|
-
if (
|
|
1435
|
+
if (existsSync6(path6.dirname(p2))) return p2;
|
|
1391
1436
|
return null;
|
|
1392
1437
|
}
|
|
1393
1438
|
async function configureClaude() {
|
|
1394
1439
|
const cfgPath = claudeConfigPath() ?? path6.join(HOME, ".claude.json");
|
|
1395
|
-
if (!
|
|
1440
|
+
if (!existsSync6(cfgPath) && !existsSync6(path6.join(HOME, ".claude"))) {
|
|
1396
1441
|
return { client: "Claude Code", status: "not_installed" };
|
|
1397
1442
|
}
|
|
1398
1443
|
let config = {};
|
|
1399
|
-
if (
|
|
1444
|
+
if (existsSync6(cfgPath)) {
|
|
1400
1445
|
try {
|
|
1401
1446
|
config = JSON.parse(await readFile3(cfgPath, "utf8"));
|
|
1402
1447
|
} catch {
|
|
@@ -1414,7 +1459,7 @@ function windsurfMcpPath() {
|
|
|
1414
1459
|
path6.join(HOME, ".windsurf", "mcp.json")
|
|
1415
1460
|
];
|
|
1416
1461
|
for (const c of candidates) {
|
|
1417
|
-
if (
|
|
1462
|
+
if (existsSync6(path6.dirname(c))) return c;
|
|
1418
1463
|
}
|
|
1419
1464
|
return null;
|
|
1420
1465
|
}
|
|
@@ -1422,7 +1467,7 @@ async function configureWindsurf() {
|
|
|
1422
1467
|
const mcpPath = windsurfMcpPath();
|
|
1423
1468
|
if (!mcpPath) return { client: "Windsurf", status: "not_installed" };
|
|
1424
1469
|
let config = {};
|
|
1425
|
-
if (
|
|
1470
|
+
if (existsSync6(mcpPath)) {
|
|
1426
1471
|
try {
|
|
1427
1472
|
config = JSON.parse(await readFile3(mcpPath, "utf8"));
|
|
1428
1473
|
} catch {
|
|
@@ -1454,7 +1499,7 @@ async function configureProjectMcpClients(root) {
|
|
|
1454
1499
|
try {
|
|
1455
1500
|
const cursorPath = path6.join(root, ".cursor", "mcp.json");
|
|
1456
1501
|
let config = {};
|
|
1457
|
-
if (
|
|
1502
|
+
if (existsSync6(cursorPath)) {
|
|
1458
1503
|
try {
|
|
1459
1504
|
config = JSON.parse(await readFile3(cursorPath, "utf8"));
|
|
1460
1505
|
} catch {
|
|
@@ -1471,7 +1516,7 @@ async function configureProjectMcpClients(root) {
|
|
|
1471
1516
|
try {
|
|
1472
1517
|
const vscodePath = path6.join(root, ".vscode", "mcp.json");
|
|
1473
1518
|
let config = {};
|
|
1474
|
-
if (
|
|
1519
|
+
if (existsSync6(vscodePath)) {
|
|
1475
1520
|
try {
|
|
1476
1521
|
config = JSON.parse(await readFile3(vscodePath, "utf8"));
|
|
1477
1522
|
} catch {
|
|
@@ -1488,7 +1533,7 @@ async function configureProjectMcpClients(root) {
|
|
|
1488
1533
|
try {
|
|
1489
1534
|
const mcpPath = path6.join(root, ".mcp.json");
|
|
1490
1535
|
let config = {};
|
|
1491
|
-
if (
|
|
1536
|
+
if (existsSync6(mcpPath)) {
|
|
1492
1537
|
try {
|
|
1493
1538
|
config = JSON.parse(await readFile3(mcpPath, "utf8"));
|
|
1494
1539
|
} catch {
|
|
@@ -1562,9 +1607,9 @@ async function detectAgentMode(dir) {
|
|
|
1562
1607
|
const root = findProjectRoot6(dir);
|
|
1563
1608
|
const paths = resolveHaivePaths5(root);
|
|
1564
1609
|
const projectMcp = [
|
|
1565
|
-
{ client: "Claude Code", path: path7.join(root, ".mcp.json"), present:
|
|
1566
|
-
{ client: "Cursor", path: path7.join(root, ".cursor", "mcp.json"), present:
|
|
1567
|
-
{ client: "VS Code", path: path7.join(root, ".vscode", "mcp.json"), present:
|
|
1610
|
+
{ client: "Claude Code", path: path7.join(root, ".mcp.json"), present: existsSync7(path7.join(root, ".mcp.json")) },
|
|
1611
|
+
{ client: "Cursor", path: path7.join(root, ".cursor", "mcp.json"), present: existsSync7(path7.join(root, ".cursor", "mcp.json")) },
|
|
1612
|
+
{ client: "VS Code", path: path7.join(root, ".vscode", "mcp.json"), present: existsSync7(path7.join(root, ".vscode", "mcp.json")) }
|
|
1568
1613
|
];
|
|
1569
1614
|
const installedAgents = [
|
|
1570
1615
|
{ agent: "Codex", command: "codex", installed: commandExists("codex"), mcp_configured: codexMcpConfigured() },
|
|
@@ -1579,7 +1624,7 @@ async function detectAgentMode(dir) {
|
|
|
1579
1624
|
const recommendedCommand = recommendedMode === "mcp" ? "Restart your AI client, then call get_briefing before editing." : recommendedMode === "wrapped" && wrapperAgent ? `haive run -- ${wrapperAgent.command}` : 'haive briefing --task "..." --files "..."';
|
|
1580
1625
|
return {
|
|
1581
1626
|
root,
|
|
1582
|
-
initialized:
|
|
1627
|
+
initialized: existsSync7(paths.haiveDir),
|
|
1583
1628
|
project_mcp: projectMcp,
|
|
1584
1629
|
installed_agents: installedAgents,
|
|
1585
1630
|
recommended_mode: recommendedMode,
|
|
@@ -1686,7 +1731,7 @@ function printSetupResult(result) {
|
|
|
1686
1731
|
|
|
1687
1732
|
// src/commands/init-bootstrap.ts
|
|
1688
1733
|
import { readdir, readFile as readFile4 } from "fs/promises";
|
|
1689
|
-
import { existsSync as
|
|
1734
|
+
import { existsSync as existsSync8, readdirSync } from "fs";
|
|
1690
1735
|
import path8 from "path";
|
|
1691
1736
|
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
1692
1737
|
"node_modules",
|
|
@@ -1773,14 +1818,38 @@ function detectKeyDeps(allDeps) {
|
|
|
1773
1818
|
return KEY_DEPS.filter((d) => allDeps[d] !== void 0);
|
|
1774
1819
|
}
|
|
1775
1820
|
function detectLanguage(root) {
|
|
1776
|
-
if (
|
|
1777
|
-
if (
|
|
1778
|
-
if (
|
|
1779
|
-
if (
|
|
1780
|
-
if (
|
|
1781
|
-
if (
|
|
1821
|
+
if (existsSync8(path8.join(root, "tsconfig.json"))) return "TypeScript";
|
|
1822
|
+
if (existsSync8(path8.join(root, "pyproject.toml")) || existsSync8(path8.join(root, "setup.py"))) return "Python";
|
|
1823
|
+
if (existsSync8(path8.join(root, "go.mod"))) return "Go";
|
|
1824
|
+
if (existsSync8(path8.join(root, "pom.xml")) || existsSync8(path8.join(root, "build.gradle"))) return "Java/Kotlin";
|
|
1825
|
+
if (existsSync8(path8.join(root, "Cargo.toml"))) return "Rust";
|
|
1826
|
+
if (existsSync8(path8.join(root, "package.json"))) {
|
|
1827
|
+
return hasSourceWithExt(root, [".ts", ".tsx", ".mts", ".cts"]) ? "TypeScript" : "JavaScript";
|
|
1828
|
+
}
|
|
1782
1829
|
return "Unknown";
|
|
1783
1830
|
}
|
|
1831
|
+
function hasSourceWithExt(root, exts) {
|
|
1832
|
+
const matches = (name) => !name.endsWith(".d.ts") && exts.some((ext) => name.endsWith(ext));
|
|
1833
|
+
const scanDir = (dir, depth) => {
|
|
1834
|
+
let entries;
|
|
1835
|
+
try {
|
|
1836
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
1837
|
+
} catch {
|
|
1838
|
+
return false;
|
|
1839
|
+
}
|
|
1840
|
+
for (const entry of entries) {
|
|
1841
|
+
if (entry.isFile() && matches(entry.name)) return true;
|
|
1842
|
+
}
|
|
1843
|
+
if (depth <= 0) return false;
|
|
1844
|
+
for (const entry of entries) {
|
|
1845
|
+
if (entry.isDirectory() && !IGNORE_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
|
|
1846
|
+
if (scanDir(path8.join(dir, entry.name), depth - 1)) return true;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
return false;
|
|
1850
|
+
};
|
|
1851
|
+
return scanDir(root, 2);
|
|
1852
|
+
}
|
|
1784
1853
|
function detectProjectType(frameworks, scripts, isMonorepo) {
|
|
1785
1854
|
if (isMonorepo) {
|
|
1786
1855
|
if (frameworks.includes("NestJS")) return "Monorepo (NestJS backend)";
|
|
@@ -1795,7 +1864,7 @@ function detectProjectType(frameworks, scripts, isMonorepo) {
|
|
|
1795
1864
|
if (frameworks.includes("Express") || frameworks.includes("Fastify") || frameworks.includes("Hono")) return "Backend API";
|
|
1796
1865
|
if (frameworks.includes("React") || frameworks.includes("Vue") || frameworks.includes("Svelte")) return "Frontend SPA";
|
|
1797
1866
|
if (scripts["build"] && !scripts["dev"]) return "CLI tool / library";
|
|
1798
|
-
if (
|
|
1867
|
+
if (existsSync8("pom.xml")) return "Java backend";
|
|
1799
1868
|
return "Application";
|
|
1800
1869
|
}
|
|
1801
1870
|
async function scanDirs(root, maxDepth = 2) {
|
|
@@ -1931,7 +2000,7 @@ function readmeExcerpt(readme) {
|
|
|
1931
2000
|
async function generateBootstrapContext(root) {
|
|
1932
2001
|
let pkg = {};
|
|
1933
2002
|
const pkgPath = path8.join(root, "package.json");
|
|
1934
|
-
if (
|
|
2003
|
+
if (existsSync8(pkgPath)) {
|
|
1935
2004
|
try {
|
|
1936
2005
|
pkg = JSON.parse(await readFile4(pkgPath, "utf8"));
|
|
1937
2006
|
} catch {
|
|
@@ -1948,7 +2017,7 @@ async function generateBootstrapContext(root) {
|
|
|
1948
2017
|
let readmeSummary = "";
|
|
1949
2018
|
for (const name of ["README.md", "readme.md", "README"]) {
|
|
1950
2019
|
const p = path8.join(root, name);
|
|
1951
|
-
if (
|
|
2020
|
+
if (existsSync8(p)) {
|
|
1952
2021
|
try {
|
|
1953
2022
|
const content = await readFile4(p, "utf8");
|
|
1954
2023
|
readmeSummary = readmeExcerpt(content);
|
|
@@ -2015,7 +2084,7 @@ async function generateBootstrapContext(root) {
|
|
|
2015
2084
|
|
|
2016
2085
|
// src/commands/init-stack-packs.ts
|
|
2017
2086
|
import { mkdir as mkdir4, writeFile as writeFile5 } from "fs/promises";
|
|
2018
|
-
import { existsSync as
|
|
2087
|
+
import { existsSync as existsSync9 } from "fs";
|
|
2019
2088
|
import path9 from "path";
|
|
2020
2089
|
import {
|
|
2021
2090
|
buildFrontmatter,
|
|
@@ -2643,7 +2712,7 @@ async function seedStackPack(haivePaths, stack) {
|
|
|
2643
2712
|
tags: [...mem.tags, STACK_PACK_TAG]
|
|
2644
2713
|
});
|
|
2645
2714
|
const filePath = memoryFilePath(haivePaths, "team", fm.id);
|
|
2646
|
-
if (
|
|
2715
|
+
if (existsSync9(filePath)) continue;
|
|
2647
2716
|
const content = serializeMemory2({ frontmatter: fm, body: `${mem.body}
|
|
2648
2717
|
|
|
2649
2718
|
${SEED_FOOTER(stack)}` });
|
|
@@ -2867,7 +2936,7 @@ function registerInit(program2) {
|
|
|
2867
2936
|
const autopilot = opts.manual !== true;
|
|
2868
2937
|
const wantBootstrap = opts.bootstrap === void 0 ? autopilot : opts.bootstrap;
|
|
2869
2938
|
const wantStack = opts.stack === void 0 ? autopilot ? "auto" : void 0 : opts.stack === "none" ? void 0 : opts.stack;
|
|
2870
|
-
if (
|
|
2939
|
+
if (existsSync10(paths.haiveDir)) {
|
|
2871
2940
|
ui.warn(`.ai/ already exists at ${paths.haiveDir} \u2014 leaving existing files in place.`);
|
|
2872
2941
|
}
|
|
2873
2942
|
await mkdir5(paths.personalDir, { recursive: true });
|
|
@@ -2876,7 +2945,7 @@ function registerInit(program2) {
|
|
|
2876
2945
|
await mkdir5(paths.modulesContextDir, { recursive: true });
|
|
2877
2946
|
await ensureAiRuntimeLayout(paths.runtimeDir);
|
|
2878
2947
|
await ensureAiCacheLayout(path10.join(paths.haiveDir, ".cache"));
|
|
2879
|
-
if (!
|
|
2948
|
+
if (!existsSync10(paths.projectContext)) {
|
|
2880
2949
|
if (wantBootstrap) {
|
|
2881
2950
|
ui.info("Bootstrapping project context from local files\u2026");
|
|
2882
2951
|
try {
|
|
@@ -2892,7 +2961,7 @@ function registerInit(program2) {
|
|
|
2892
2961
|
ui.success(`Created ${path10.relative(root, paths.projectContext)}`);
|
|
2893
2962
|
}
|
|
2894
2963
|
}
|
|
2895
|
-
const configExists =
|
|
2964
|
+
const configExists = existsSync10(
|
|
2896
2965
|
path10.join(paths.haiveDir, "haive.config.json")
|
|
2897
2966
|
);
|
|
2898
2967
|
if (!configExists) {
|
|
@@ -2931,7 +3000,7 @@ function registerInit(program2) {
|
|
|
2931
3000
|
const wantCi = opts.withCi || autopilot;
|
|
2932
3001
|
if (wantCi) {
|
|
2933
3002
|
const ciPath = path10.join(root, ".github", "workflows", "haive-sync.yml");
|
|
2934
|
-
if (
|
|
3003
|
+
if (existsSync10(ciPath)) {
|
|
2935
3004
|
ui.info("CI workflow already exists \u2014 skipped");
|
|
2936
3005
|
} else {
|
|
2937
3006
|
await mkdir5(path10.dirname(ciPath), { recursive: true });
|
|
@@ -2980,8 +3049,8 @@ function registerInit(program2) {
|
|
|
2980
3049
|
else if (r.status === "error") ui.warn(`${r.client}: ${r.error}`);
|
|
2981
3050
|
}
|
|
2982
3051
|
for (const r of agentSetup.global_results) {
|
|
2983
|
-
if (r.status === "configured") ui.success(`haive MCP configured in ${r.client}${r.path ? ` (${r.path})` : ""}`);
|
|
2984
|
-
else if (r.status === "already_configured") ui.info(`haive MCP already
|
|
3052
|
+
if (r.status === "configured") ui.success(`haive MCP configured in ${r.client} user-level config${r.path ? ` (${r.path})` : ""}`);
|
|
3053
|
+
else if (r.status === "already_configured") ui.info(`haive MCP already present in ${r.client} user-level config \u2014 left unchanged (this project's config was written above)`);
|
|
2985
3054
|
}
|
|
2986
3055
|
if (agentSetup.global_skipped_reason) ui.warn(agentSetup.global_skipped_reason);
|
|
2987
3056
|
ui.info(`Recommended agent mode: ${agentSetup.detection.recommended_mode}`);
|
|
@@ -3048,7 +3117,7 @@ async function resolveStacksToSeed(root, stackOpt) {
|
|
|
3048
3117
|
if (!stackOpt) return [];
|
|
3049
3118
|
if (stackOpt === "auto") {
|
|
3050
3119
|
const pkgPath = path10.join(root, "package.json");
|
|
3051
|
-
if (!
|
|
3120
|
+
if (!existsSync10(pkgPath)) return [];
|
|
3052
3121
|
try {
|
|
3053
3122
|
const pkg = JSON.parse(await readFile5(pkgPath, "utf8"));
|
|
3054
3123
|
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
@@ -3062,7 +3131,7 @@ async function resolveStacksToSeed(root, stackOpt) {
|
|
|
3062
3131
|
async function writeCursorHaiveRule(root) {
|
|
3063
3132
|
const relPath = ".cursor/rules/haive-mcp-required.mdc";
|
|
3064
3133
|
const target = path10.join(root, relPath);
|
|
3065
|
-
if (
|
|
3134
|
+
if (existsSync10(target)) {
|
|
3066
3135
|
ui.info(`Cursor rule ${relPath} already exists \u2014 skipped`);
|
|
3067
3136
|
return;
|
|
3068
3137
|
}
|
|
@@ -3072,7 +3141,7 @@ async function writeCursorHaiveRule(root) {
|
|
|
3072
3141
|
}
|
|
3073
3142
|
async function writeBridge(root, relPath) {
|
|
3074
3143
|
const target = path10.join(root, relPath);
|
|
3075
|
-
if (
|
|
3144
|
+
if (existsSync10(target)) {
|
|
3076
3145
|
ui.info(`Bridge ${relPath} already exists \u2014 skipped`);
|
|
3077
3146
|
return;
|
|
3078
3147
|
}
|
|
@@ -3095,18 +3164,18 @@ var RUNTIME_GITIGNORE_BODY = `*
|
|
|
3095
3164
|
async function ensureAiRuntimeLayout(runtimeDir) {
|
|
3096
3165
|
await mkdir5(runtimeDir, { recursive: true });
|
|
3097
3166
|
const gi = path10.join(runtimeDir, ".gitignore");
|
|
3098
|
-
if (!
|
|
3167
|
+
if (!existsSync10(gi)) {
|
|
3099
3168
|
await writeFile6(gi, RUNTIME_GITIGNORE_BODY, "utf8");
|
|
3100
3169
|
}
|
|
3101
3170
|
const readme = path10.join(runtimeDir, "README.md");
|
|
3102
|
-
if (!
|
|
3171
|
+
if (!existsSync10(readme)) {
|
|
3103
3172
|
await writeFile6(readme, RUNTIME_README_BODY, "utf8");
|
|
3104
3173
|
}
|
|
3105
3174
|
}
|
|
3106
3175
|
async function ensureAiCacheLayout(cacheDir) {
|
|
3107
3176
|
await mkdir5(cacheDir, { recursive: true });
|
|
3108
3177
|
const gi = path10.join(cacheDir, ".gitignore");
|
|
3109
|
-
if (!
|
|
3178
|
+
if (!existsSync10(gi)) {
|
|
3110
3179
|
await writeFile6(gi, "*\n!.gitignore\n", "utf8");
|
|
3111
3180
|
}
|
|
3112
3181
|
}
|
|
@@ -3114,7 +3183,7 @@ async function ensureGitignoreEntries(root, patterns) {
|
|
|
3114
3183
|
try {
|
|
3115
3184
|
const gitignorePath = path10.join(root, ".gitignore");
|
|
3116
3185
|
let existing = "";
|
|
3117
|
-
if (
|
|
3186
|
+
if (existsSync10(gitignorePath)) {
|
|
3118
3187
|
existing = await readFile5(gitignorePath, "utf8");
|
|
3119
3188
|
}
|
|
3120
3189
|
const lines = existing.split("\n");
|
|
@@ -3128,13 +3197,13 @@ async function ensureGitignoreEntries(root, patterns) {
|
|
|
3128
3197
|
|
|
3129
3198
|
// src/commands/install-hooks.ts
|
|
3130
3199
|
import { mkdir as mkdir7, writeFile as writeFile8, chmod, readFile as readFile7 } from "fs/promises";
|
|
3131
|
-
import { existsSync as
|
|
3200
|
+
import { existsSync as existsSync12 } from "fs";
|
|
3132
3201
|
import path12 from "path";
|
|
3133
3202
|
import "commander";
|
|
3134
3203
|
import { findProjectRoot as findProjectRoot8 } from "@hiveai/core";
|
|
3135
3204
|
|
|
3136
3205
|
// src/utils/claude-hooks.ts
|
|
3137
|
-
import { existsSync as
|
|
3206
|
+
import { existsSync as existsSync11 } from "fs";
|
|
3138
3207
|
import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile7 } from "fs/promises";
|
|
3139
3208
|
import path11 from "path";
|
|
3140
3209
|
var HAIVE_HOOK_TAG = "haive-enforcement";
|
|
@@ -3222,7 +3291,7 @@ function unpatchClaudeSettings(input) {
|
|
|
3222
3291
|
async function installClaudeHooksAtPath(settingsPath) {
|
|
3223
3292
|
let raw = null;
|
|
3224
3293
|
let created = false;
|
|
3225
|
-
if (
|
|
3294
|
+
if (existsSync11(settingsPath)) {
|
|
3226
3295
|
try {
|
|
3227
3296
|
raw = JSON.parse(await readFile6(settingsPath, "utf8"));
|
|
3228
3297
|
} catch {
|
|
@@ -3237,7 +3306,7 @@ async function installClaudeHooksAtPath(settingsPath) {
|
|
|
3237
3306
|
return { settingsPath, created };
|
|
3238
3307
|
}
|
|
3239
3308
|
async function uninstallClaudeHooksAtPath(settingsPath) {
|
|
3240
|
-
if (!
|
|
3309
|
+
if (!existsSync11(settingsPath)) {
|
|
3241
3310
|
return { settingsPath, created: false };
|
|
3242
3311
|
}
|
|
3243
3312
|
const raw = JSON.parse(await readFile6(settingsPath, "utf8"));
|
|
@@ -3308,7 +3377,7 @@ fi
|
|
|
3308
3377
|
async function installGitHooks(opts) {
|
|
3309
3378
|
const root = findProjectRoot8(opts.dir);
|
|
3310
3379
|
const gitDir = path12.join(root, ".git");
|
|
3311
|
-
if (!
|
|
3380
|
+
if (!existsSync12(gitDir)) {
|
|
3312
3381
|
ui.error(`No .git directory at ${root}.`);
|
|
3313
3382
|
process.exitCode = 1;
|
|
3314
3383
|
return;
|
|
@@ -3319,7 +3388,7 @@ async function installGitHooks(opts) {
|
|
|
3319
3388
|
let skipped = 0;
|
|
3320
3389
|
for (const { name, body } of HOOKS) {
|
|
3321
3390
|
const file = path12.join(hooksDir, name);
|
|
3322
|
-
if (
|
|
3391
|
+
if (existsSync12(file) && !opts.force) {
|
|
3323
3392
|
const existing = await readFile7(file, "utf8");
|
|
3324
3393
|
if (!existing.includes(HOOK_MARKER)) {
|
|
3325
3394
|
ui.warn(`${name} already exists and was not written by hAIve. Re-run with --force to overwrite.`);
|
|
@@ -3383,7 +3452,7 @@ function registerInstallHooks(program2) {
|
|
|
3383
3452
|
|
|
3384
3453
|
// src/commands/observe.ts
|
|
3385
3454
|
import { appendFile, mkdir as mkdir8 } from "fs/promises";
|
|
3386
|
-
import { existsSync as
|
|
3455
|
+
import { existsSync as existsSync13 } from "fs";
|
|
3387
3456
|
import path13 from "path";
|
|
3388
3457
|
import "commander";
|
|
3389
3458
|
import { findProjectRoot as findProjectRoot9, resolveHaivePaths as resolveHaivePaths7 } from "@hiveai/core";
|
|
@@ -3484,7 +3553,7 @@ function registerObserve(program2) {
|
|
|
3484
3553
|
})();
|
|
3485
3554
|
if (!root) return;
|
|
3486
3555
|
const paths = resolveHaivePaths7(root);
|
|
3487
|
-
if (!
|
|
3556
|
+
if (!existsSync13(paths.haiveDir)) return;
|
|
3488
3557
|
const failureHint = detectFailure(payload);
|
|
3489
3558
|
const observation = {
|
|
3490
3559
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -3516,7 +3585,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
3516
3585
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3517
3586
|
import { findProjectRoot as findProjectRoot10, resolveHaivePaths as resolveHaivePaths8 } from "@hiveai/core";
|
|
3518
3587
|
import { mkdir as mkdir9, writeFile as writeFile9 } from "fs/promises";
|
|
3519
|
-
import { existsSync as
|
|
3588
|
+
import { existsSync as existsSync14 } from "fs";
|
|
3520
3589
|
import path14 from "path";
|
|
3521
3590
|
import { z } from "zod";
|
|
3522
3591
|
import { readFile as readFile8, readdir as readdir2 } from "fs/promises";
|
|
@@ -3619,7 +3688,7 @@ import {
|
|
|
3619
3688
|
} from "@hiveai/core";
|
|
3620
3689
|
import { z as z13 } from "zod";
|
|
3621
3690
|
import { mkdir as mkdir32, writeFile as writeFile72 } from "fs/promises";
|
|
3622
|
-
import { existsSync as
|
|
3691
|
+
import { existsSync as existsSync142 } from "fs";
|
|
3623
3692
|
import path52 from "path";
|
|
3624
3693
|
import {
|
|
3625
3694
|
buildFrontmatter as buildFrontmatter22,
|
|
@@ -3672,7 +3741,7 @@ import {
|
|
|
3672
3741
|
isStackPackSeed as isStackPackSeed2,
|
|
3673
3742
|
literalMatchesAllTokens as literalMatchesAllTokens22,
|
|
3674
3743
|
literalMatchesAnyToken as literalMatchesAnyToken22,
|
|
3675
|
-
loadCodeMap as
|
|
3744
|
+
loadCodeMap as loadCodeMap5,
|
|
3676
3745
|
loadConfig as loadConfig3,
|
|
3677
3746
|
loadMemoriesFromDir as loadMemoriesFromDir13,
|
|
3678
3747
|
loadUsageIndex as loadUsageIndex7,
|
|
@@ -3795,7 +3864,7 @@ var BootstrapProjectSaveInputSchema = {
|
|
|
3795
3864
|
};
|
|
3796
3865
|
async function bootstrapProjectSave(input, ctx) {
|
|
3797
3866
|
const target = input.module ? path14.join(ctx.paths.modulesContextDir, input.module, "context.md") : ctx.paths.projectContext;
|
|
3798
|
-
const exists =
|
|
3867
|
+
const exists = existsSync14(target);
|
|
3799
3868
|
if (exists && !input.overwrite) {
|
|
3800
3869
|
throw new Error(
|
|
3801
3870
|
`${target} already exists. Pass overwrite=true to replace it.`
|
|
@@ -4675,7 +4744,7 @@ var MemTriedInputSchema = {
|
|
|
4675
4744
|
author: z14.string().optional().describe("Author handle or email")
|
|
4676
4745
|
};
|
|
4677
4746
|
async function memTried(input, ctx) {
|
|
4678
|
-
if (!
|
|
4747
|
+
if (!existsSync142(ctx.paths.haiveDir)) {
|
|
4679
4748
|
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
|
|
4680
4749
|
}
|
|
4681
4750
|
const slug = input.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 5).join("-");
|
|
@@ -4697,7 +4766,7 @@ async function memTried(input, ctx) {
|
|
|
4697
4766
|
const body = lines.join("\n") + "\n";
|
|
4698
4767
|
const file = memoryFilePath22(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
4699
4768
|
await mkdir32(path52.dirname(file), { recursive: true });
|
|
4700
|
-
if (
|
|
4769
|
+
if (existsSync142(file)) {
|
|
4701
4770
|
throw new Error(`Memory already exists at ${file}`);
|
|
4702
4771
|
}
|
|
4703
4772
|
await writeFile72(file, serializeMemory6({ frontmatter, body }), "utf8");
|
|
@@ -5189,7 +5258,7 @@ async function getBriefing(input, ctx) {
|
|
|
5189
5258
|
if ((isTemplateContext || !existsSync18(ctx.paths.projectContext)) && input.include_project_context) {
|
|
5190
5259
|
const haiveConfig = await loadConfig3(ctx.paths);
|
|
5191
5260
|
if (haiveConfig.autoContext) {
|
|
5192
|
-
const codeMap = await
|
|
5261
|
+
const codeMap = await loadCodeMap5(ctx.paths);
|
|
5193
5262
|
if (codeMap) {
|
|
5194
5263
|
const totalFiles = Object.keys(codeMap.files).length;
|
|
5195
5264
|
const extensions = /* @__PURE__ */ new Map();
|
|
@@ -5317,7 +5386,7 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
5317
5386
|
}
|
|
5318
5387
|
}
|
|
5319
5388
|
if (symbolsToLookup.size > 0) {
|
|
5320
|
-
const codeMap = await
|
|
5389
|
+
const codeMap = await loadCodeMap5(ctx.paths);
|
|
5321
5390
|
if (codeMap) {
|
|
5322
5391
|
symbolLocations = [];
|
|
5323
5392
|
for (const sym of symbolsToLookup) {
|
|
@@ -5944,6 +6013,58 @@ var AntiPatternsCheckInputSchema = {
|
|
|
5944
6013
|
"When true, also use semantic search (requires @hiveai/embeddings + memory index) to find related anti-patterns."
|
|
5945
6014
|
)
|
|
5946
6015
|
};
|
|
6016
|
+
var CODE_STOPWORDS = /* @__PURE__ */ new Set([
|
|
6017
|
+
"import",
|
|
6018
|
+
"export",
|
|
6019
|
+
"function",
|
|
6020
|
+
"return",
|
|
6021
|
+
"const",
|
|
6022
|
+
"let",
|
|
6023
|
+
"var",
|
|
6024
|
+
"class",
|
|
6025
|
+
"public",
|
|
6026
|
+
"private",
|
|
6027
|
+
"protected",
|
|
6028
|
+
"static",
|
|
6029
|
+
"this",
|
|
6030
|
+
"true",
|
|
6031
|
+
"false",
|
|
6032
|
+
"null",
|
|
6033
|
+
"undefined",
|
|
6034
|
+
"void",
|
|
6035
|
+
"async",
|
|
6036
|
+
"await",
|
|
6037
|
+
"from",
|
|
6038
|
+
"type",
|
|
6039
|
+
"interface",
|
|
6040
|
+
"extends",
|
|
6041
|
+
"implements",
|
|
6042
|
+
"number",
|
|
6043
|
+
"string",
|
|
6044
|
+
"boolean",
|
|
6045
|
+
"value",
|
|
6046
|
+
"default",
|
|
6047
|
+
"case",
|
|
6048
|
+
"break",
|
|
6049
|
+
"continue",
|
|
6050
|
+
"throw",
|
|
6051
|
+
"catch",
|
|
6052
|
+
"finally",
|
|
6053
|
+
"else",
|
|
6054
|
+
"while",
|
|
6055
|
+
"for",
|
|
6056
|
+
"new",
|
|
6057
|
+
"super",
|
|
6058
|
+
"yield",
|
|
6059
|
+
"module",
|
|
6060
|
+
"require",
|
|
6061
|
+
"console"
|
|
6062
|
+
]);
|
|
6063
|
+
function tokenizeDiffForLiteral(diff) {
|
|
6064
|
+
const wsTokens = tokenizeQuery3(diff);
|
|
6065
|
+
const wordTokens = diff.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 4 && !CODE_STOPWORDS.has(t));
|
|
6066
|
+
return [.../* @__PURE__ */ new Set([...wsTokens, ...wordTokens])];
|
|
6067
|
+
}
|
|
5947
6068
|
async function antiPatternsCheck(input, ctx) {
|
|
5948
6069
|
if (!input.diff && input.paths.length === 0) {
|
|
5949
6070
|
return {
|
|
@@ -5997,7 +6118,7 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
5997
6118
|
}
|
|
5998
6119
|
}
|
|
5999
6120
|
if (input.diff) {
|
|
6000
|
-
const tokens =
|
|
6121
|
+
const tokens = tokenizeDiffForLiteral(input.diff);
|
|
6001
6122
|
if (tokens.length > 0) {
|
|
6002
6123
|
for (const { memory: memory2 } of negative) {
|
|
6003
6124
|
if (literalMatchesAnyToken3(memory2, tokens)) {
|
|
@@ -6450,7 +6571,10 @@ var PreCommitCheckInputSchema = {
|
|
|
6450
6571
|
block_on: z28.enum(["any", "high-confidence", "never"]).default("high-confidence").describe(
|
|
6451
6572
|
"When to set should_block=true: 'any' = any warning blocks; 'high-confidence' = only warnings from authoritative/trusted memories block; 'never' = report only, never block."
|
|
6452
6573
|
),
|
|
6453
|
-
semantic: z28.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index).")
|
|
6574
|
+
semantic: z28.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index)."),
|
|
6575
|
+
anchored_blocks: z28.boolean().default(false).describe(
|
|
6576
|
+
"When true, ALSO block a high-confidence anti-pattern (attempt/gotcha) that is anchored to a touched file AND corroborated by the diff (literal token overlap, or semantic >= 0.45) \u2014 not just very strong semantic matches. Powers the 'anchored' enforcement gate. Config/docs-only commits are still downgraded. Default false preserves the soft, semantic-only blocking behavior."
|
|
6577
|
+
)
|
|
6454
6578
|
};
|
|
6455
6579
|
async function preCommitCheck(input, ctx) {
|
|
6456
6580
|
if (!input.diff && input.paths.length === 0) {
|
|
@@ -6475,7 +6599,7 @@ async function preCommitCheck(input, ctx) {
|
|
|
6475
6599
|
const filesTouching = new Set(relevantMatches.map((m) => m.id));
|
|
6476
6600
|
const staleHits = verifyResult.results.filter((r) => r.stale && filesTouching.has(r.id));
|
|
6477
6601
|
const blockOn = input.block_on;
|
|
6478
|
-
const classifiedWarnings = apResult.warnings.map((warning) => classifyWarning(warning, input.paths));
|
|
6602
|
+
const classifiedWarnings = apResult.warnings.map((warning) => classifyWarning(warning, input.paths, input.anchored_blocks));
|
|
6479
6603
|
const blockingWarnings = classifiedWarnings.filter((w) => w.level === "blocking");
|
|
6480
6604
|
const reviewWarnings = classifiedWarnings.filter((w) => w.level === "review");
|
|
6481
6605
|
const infoWarnings = classifiedWarnings.filter((w) => w.level === "info");
|
|
@@ -6515,7 +6639,7 @@ async function preCommitCheck(input, ctx) {
|
|
|
6515
6639
|
})
|
|
6516
6640
|
};
|
|
6517
6641
|
}
|
|
6518
|
-
function classifyWarning(warning, paths) {
|
|
6642
|
+
function classifyWarning(warning, paths, anchoredBlocks = false) {
|
|
6519
6643
|
const affectedFiles = paths.filter((p) => !p.startsWith(".ai/.usage/"));
|
|
6520
6644
|
const repairCommand = repairCommandForWarning(warning, affectedFiles);
|
|
6521
6645
|
const fileDowngrade = fileTypeDowngradeReason(warning, affectedFiles);
|
|
@@ -6540,6 +6664,15 @@ function classifyWarning(warning, paths) {
|
|
|
6540
6664
|
const hasSemantic = warning.reasons.includes("semantic");
|
|
6541
6665
|
const semanticScore = warning.semantic_score ?? 0;
|
|
6542
6666
|
const highConfidence = warning.confidence === "authoritative" || warning.confidence === "trusted";
|
|
6667
|
+
if (anchoredBlocks && highConfidence && warning.reasons.includes("anchor") && (warning.reasons.includes("literal") || hasSemantic && semanticScore >= 0.45)) {
|
|
6668
|
+
return {
|
|
6669
|
+
...warning,
|
|
6670
|
+
level: "blocking",
|
|
6671
|
+
rationale: "high-confidence anti-pattern anchored to a touched file and corroborated by the diff (anchored gate)",
|
|
6672
|
+
affected_files: affectedFiles,
|
|
6673
|
+
repair_command: repairCommand
|
|
6674
|
+
};
|
|
6675
|
+
}
|
|
6543
6676
|
if (hasSemantic && semanticScore >= 0.45 || highConfidence && warning.reasons.includes("anchor") && warning.reasons.includes("literal")) {
|
|
6544
6677
|
return {
|
|
6545
6678
|
...warning,
|
|
@@ -7190,7 +7323,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
7190
7323
|
};
|
|
7191
7324
|
}
|
|
7192
7325
|
var SERVER_NAME = "haive";
|
|
7193
|
-
var SERVER_VERSION = "0.9.
|
|
7326
|
+
var SERVER_VERSION = "0.9.30";
|
|
7194
7327
|
function jsonResult(data) {
|
|
7195
7328
|
return {
|
|
7196
7329
|
content: [
|
|
@@ -8172,7 +8305,7 @@ import {
|
|
|
8172
8305
|
isAutoPromoteEligible as isAutoPromoteEligible2,
|
|
8173
8306
|
isDecaying as isDecaying2,
|
|
8174
8307
|
isStackPackSeed as isStackPackSeed3,
|
|
8175
|
-
loadCodeMap as
|
|
8308
|
+
loadCodeMap as loadCodeMap6,
|
|
8176
8309
|
loadConfig as loadConfig4,
|
|
8177
8310
|
loadMemoriesFromDir as loadMemoriesFromDir23,
|
|
8178
8311
|
loadUsageIndex as loadUsageIndex12,
|
|
@@ -8543,7 +8676,7 @@ Attends une **confirmation explicite** avant d'agir.
|
|
|
8543
8676
|
ui.warn(`contract watcher failed: ${String(err)}`);
|
|
8544
8677
|
}
|
|
8545
8678
|
}
|
|
8546
|
-
const existingMap = await
|
|
8679
|
+
const existingMap = await loadCodeMap6(paths);
|
|
8547
8680
|
if (!dryRun && !existingMap && (config.autopilot || autoRepair.codeMap)) {
|
|
8548
8681
|
try {
|
|
8549
8682
|
const { buildCodeMap: buildCodeMap4, saveCodeMap: saveCodeMap4 } = await import("@hiveai/core");
|
|
@@ -8737,7 +8870,7 @@ function registerMemoryAdd(memory2) {
|
|
|
8737
8870
|
haive memory add --type convention --slug flyway-no-modify --topic flyway \\\\
|
|
8738
8871
|
--scope team --body "Never modify existing migrations. Create V{n+1}__desc.sql."
|
|
8739
8872
|
`
|
|
8740
|
-
).requiredOption("--type <type>", "skill | convention | decision | gotcha | architecture | glossary | attempt").option("--slug <slug>", "short kebab-case identifier used in the file name (auto-derived from --title/--body when omitted)").option("--title <text>", "memory title \u2014 becomes the first heading of the body").option("--scope <scope>", "personal | team | module (default: config default; team in autopilot)").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags for easier retrieval").option("--domain <domain>", "domain (e.g. transactions)").option("--author <author>", "author email or handle").option("--paths <csv>", "anchor to source files \u2014 used for staleness detection by haive sync").option("--symbols <csv>", "anchor to specific symbols (class/function names)").option("--commit <sha>", "anchor to a specific commit SHA").option("--body <text>", "memory body content (Markdown) \u2014 overrides --title default body").option("--body-file <path>", "read memory body from a Markdown file \u2014 for long content").option("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("--topic <key>", "stable key for upsert: if a memory with this topic+scope already exists, update it in-place (revision_count++)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
8873
|
+
).requiredOption("--type <type>", "skill | convention | decision | gotcha | architecture | glossary | attempt").option("--slug <slug>", "short kebab-case identifier used in the file name (auto-derived from --title/--body when omitted)").option("--title <text>", "memory title \u2014 becomes the first heading of the body").option("--scope <scope>", "personal | team | module (default: config default; team in autopilot)").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags for easier retrieval").option("--domain <domain>", "domain (e.g. transactions)").option("--author <author>", "author email or handle").option("--paths <csv>", "anchor to source files \u2014 used for staleness detection by haive sync").option("--files <csv>", "alias for --paths (matches the MCP `files` parameter)").option("--symbols <csv>", "anchor to specific symbols (class/function names)").option("--commit <sha>", "anchor to a specific commit SHA").option("--body <text>", "memory body content (Markdown) \u2014 overrides --title default body").option("--body-file <path>", "read memory body from a Markdown file \u2014 for long content").option("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("--topic <key>", "stable key for upsert: if a memory with this topic+scope already exists, update it in-place (revision_count++)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
8741
8874
|
const root = findProjectRoot13(opts.dir);
|
|
8742
8875
|
const paths = resolveHaivePaths10(root);
|
|
8743
8876
|
if (!existsSync30(paths.haiveDir)) {
|
|
@@ -8747,7 +8880,7 @@ function registerMemoryAdd(memory2) {
|
|
|
8747
8880
|
}
|
|
8748
8881
|
const config = await loadConfig5(paths);
|
|
8749
8882
|
const userTags = parseCsv2(opts.tags);
|
|
8750
|
-
const anchorPaths = parseCsv2(opts.paths);
|
|
8883
|
+
const anchorPaths = parseCsv2(opts.paths ?? opts.files);
|
|
8751
8884
|
const autoTagsEnabled = opts.autoTag !== false;
|
|
8752
8885
|
const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
|
|
8753
8886
|
const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
|
|
@@ -9156,7 +9289,7 @@ import {
|
|
|
9156
9289
|
serializeMemory as serializeMemory15
|
|
9157
9290
|
} from "@hiveai/core";
|
|
9158
9291
|
function registerMemoryUpdate(memory2) {
|
|
9159
|
-
memory2.command("update <id>").description("Update body, tags, or anchor of an existing memory (preserves id and usage history)").option("--type <type>", "change the memory type (convention | decision | gotcha | architecture | glossary | skill | attempt)").option("--title <text>", "new title \u2014 replaces the first heading of the body").option("--body <text>", "new Markdown body \u2014 replaces the existing body").option("--body-file <path>", "read new body from a Markdown file \u2014 for long content").option("--tags <csv>", "new tags, comma-separated \u2014 fully replaces existing tags").option("--paths <csv>", "new anchor paths, comma-separated").option("--symbols <csv>", "new anchor symbols, comma-separated").option("--commit <sha>", "new anchor commit SHA").option("--domain <domain>", "new domain label").option("--author <author>", "new author handle or email").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
9292
|
+
memory2.command("update <id>").description("Update body, tags, or anchor of an existing memory (preserves id and usage history)").option("--type <type>", "change the memory type (convention | decision | gotcha | architecture | glossary | skill | attempt)").option("--title <text>", "new title \u2014 replaces the first heading of the body").option("--body <text>", "new Markdown body \u2014 replaces the existing body").option("--body-file <path>", "read new body from a Markdown file \u2014 for long content").option("--tags <csv>", "new tags, comma-separated \u2014 fully replaces existing tags").option("--paths <csv>", "new anchor paths, comma-separated").option("--files <csv>", "alias for --paths (matches the MCP `files` parameter)").option("--symbols <csv>", "new anchor symbols, comma-separated").option("--commit <sha>", "new anchor commit SHA").option("--domain <domain>", "new domain label").option("--author <author>", "new author handle or email").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
9160
9293
|
const root = findProjectRoot17(opts.dir);
|
|
9161
9294
|
const paths = resolveHaivePaths14(root);
|
|
9162
9295
|
if (!existsSync35(paths.memoriesDir)) {
|
|
@@ -9174,8 +9307,9 @@ function registerMemoryUpdate(memory2) {
|
|
|
9174
9307
|
const updated = [];
|
|
9175
9308
|
const { frontmatter, body } = loaded.memory;
|
|
9176
9309
|
const newAnchor = { ...frontmatter.anchor };
|
|
9177
|
-
|
|
9178
|
-
|
|
9310
|
+
const pathsOpt = opts.paths ?? opts.files;
|
|
9311
|
+
if (pathsOpt !== void 0) {
|
|
9312
|
+
newAnchor.paths = parseCsv3(pathsOpt);
|
|
9179
9313
|
updated.push("anchor.paths");
|
|
9180
9314
|
}
|
|
9181
9315
|
if (opts.symbols !== void 0) {
|
|
@@ -9570,7 +9704,7 @@ function registerMemoryTried(memory2) {
|
|
|
9570
9704
|
--instead "use static import in the entry file" \\\\
|
|
9571
9705
|
--paths packages/cli/src/index.ts
|
|
9572
9706
|
`
|
|
9573
|
-
).requiredOption("--what <text>", "what approach was tried (short, descriptive title)").requiredOption("--why-failed <text>", "why it failed or should NOT be used (include the exact error if possible)").option("--instead <text>", "the correct approach to use instead").option("--scope <scope>", "personal | team | module", "personal").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags").option("--paths <csv>", "anchor paths, comma-separated").option("--author <author>", "author email or handle").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9707
|
+
).requiredOption("--what <text>", "what approach was tried (short, descriptive title)").requiredOption("--why-failed <text>", "why it failed or should NOT be used (include the exact error if possible)").option("--instead <text>", "the correct approach to use instead").option("--scope <scope>", "personal | team | module", "personal").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags").option("--paths <csv>", "anchor paths, comma-separated").option("--files <csv>", "alias for --paths (matches the MCP `files` parameter)").option("--author <author>", "author email or handle").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9574
9708
|
const root = findProjectRoot22(opts.dir);
|
|
9575
9709
|
const paths = resolveHaivePaths19(root);
|
|
9576
9710
|
if (!existsSync40(paths.haiveDir)) {
|
|
@@ -9585,7 +9719,7 @@ function registerMemoryTried(memory2) {
|
|
|
9585
9719
|
scope: opts.scope,
|
|
9586
9720
|
module: opts.module,
|
|
9587
9721
|
tags: parseCsv4(opts.tags),
|
|
9588
|
-
paths: parseCsv4(opts.paths),
|
|
9722
|
+
paths: parseCsv4(opts.paths ?? opts.files),
|
|
9589
9723
|
author: opts.author
|
|
9590
9724
|
});
|
|
9591
9725
|
const frontmatter = { ...baseFm, status: "validated" };
|
|
@@ -9612,21 +9746,118 @@ function parseCsv4(value) {
|
|
|
9612
9746
|
return value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
9613
9747
|
}
|
|
9614
9748
|
|
|
9615
|
-
// src/commands/memory-
|
|
9749
|
+
// src/commands/memory-seed.ts
|
|
9750
|
+
import { readFile as readFile13 } from "fs/promises";
|
|
9616
9751
|
import { existsSync as existsSync41 } from "fs";
|
|
9617
9752
|
import path27 from "path";
|
|
9618
9753
|
import "commander";
|
|
9619
9754
|
import {
|
|
9620
9755
|
findProjectRoot as findProjectRoot23,
|
|
9756
|
+
loadConfig as loadConfig6,
|
|
9757
|
+
resolveHaivePaths as resolveHaivePaths20
|
|
9758
|
+
} from "@hiveai/core";
|
|
9759
|
+
async function readDependencyMap(root) {
|
|
9760
|
+
try {
|
|
9761
|
+
const raw = await readFile13(path27.join(root, "package.json"), "utf8");
|
|
9762
|
+
const pkg = JSON.parse(raw);
|
|
9763
|
+
return { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
9764
|
+
} catch {
|
|
9765
|
+
return {};
|
|
9766
|
+
}
|
|
9767
|
+
}
|
|
9768
|
+
function registerMemorySeed(memory2) {
|
|
9769
|
+
memory2.command("seed [stack]").description(
|
|
9770
|
+
"Seed a stack pack of starter memories on demand.\n\n Stack packs are generic framework gotchas/conventions every team using that\n stack rediscovers. They are tagged `stack-pack` and kept at BACKGROUND priority\n in briefings until you anchor them to a real file or replace them with a\n repo-specific note \u2014 so they never crowd out your own knowledge.\n\n Examples:\n haive memory seed # auto-detect stacks from package.json and seed them\n haive memory seed nestjs # seed a specific stack\n haive memory seed --list # show supported + auto-detected stacks\n haive memory seed --list --json\n"
|
|
9771
|
+
).option("--list", "list supported stacks (and which are auto-detected here) and exit").option("--json", "machine-readable output (use with --list)").option("-d, --dir <dir>", "project root").action(async (stack, opts) => {
|
|
9772
|
+
const root = findProjectRoot23(opts.dir);
|
|
9773
|
+
const paths = resolveHaivePaths20(root);
|
|
9774
|
+
const deps = await readDependencyMap(root);
|
|
9775
|
+
const detected = autoDetectStacks(deps);
|
|
9776
|
+
if (opts.list) {
|
|
9777
|
+
if (opts.json) {
|
|
9778
|
+
console.log(JSON.stringify({ supported: SUPPORTED_STACKS, detected }, null, 2));
|
|
9779
|
+
return;
|
|
9780
|
+
}
|
|
9781
|
+
ui.info("Supported stacks:");
|
|
9782
|
+
for (const s of SUPPORTED_STACKS) {
|
|
9783
|
+
const mark = detected.includes(s) ? ui.green(" \u2713 detected here") : "";
|
|
9784
|
+
console.log(` \u2022 ${s}${mark}`);
|
|
9785
|
+
}
|
|
9786
|
+
if (detected.length === 0) {
|
|
9787
|
+
ui.info("No stack auto-detected from package.json \u2014 pass a stack name explicitly.");
|
|
9788
|
+
}
|
|
9789
|
+
return;
|
|
9790
|
+
}
|
|
9791
|
+
if (!existsSync41(paths.haiveDir)) {
|
|
9792
|
+
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
9793
|
+
process.exitCode = 1;
|
|
9794
|
+
return;
|
|
9795
|
+
}
|
|
9796
|
+
let stacksToSeed;
|
|
9797
|
+
if (stack) {
|
|
9798
|
+
if (!isValidStack(stack)) {
|
|
9799
|
+
ui.error(`Unknown stack '${stack}'. Supported: ${SUPPORTED_STACKS.join(", ")}.`);
|
|
9800
|
+
ui.info("Run `haive memory seed --list` to see all stacks.");
|
|
9801
|
+
process.exitCode = 1;
|
|
9802
|
+
return;
|
|
9803
|
+
}
|
|
9804
|
+
stacksToSeed = [stack];
|
|
9805
|
+
} else if (detected.length > 0) {
|
|
9806
|
+
stacksToSeed = detected;
|
|
9807
|
+
ui.info(`Auto-detected from package.json: ${detected.join(", ")}`);
|
|
9808
|
+
} else {
|
|
9809
|
+
ui.error("No stack auto-detected from package.json.");
|
|
9810
|
+
ui.info("Pass a stack name (e.g. `haive memory seed nestjs`) or run `haive memory seed --list`.");
|
|
9811
|
+
process.exitCode = 1;
|
|
9812
|
+
return;
|
|
9813
|
+
}
|
|
9814
|
+
let total = 0;
|
|
9815
|
+
const seededStacks = [];
|
|
9816
|
+
for (const s of stacksToSeed) {
|
|
9817
|
+
const count = await seedStackPack(paths, s);
|
|
9818
|
+
if (count > 0) {
|
|
9819
|
+
total += count;
|
|
9820
|
+
seededStacks.push(`${s} (${count})`);
|
|
9821
|
+
} else {
|
|
9822
|
+
ui.info(`Stack pack '${s}': all memories already exist \u2014 skipped.`);
|
|
9823
|
+
}
|
|
9824
|
+
}
|
|
9825
|
+
if (total === 0) {
|
|
9826
|
+
ui.info("Nothing new to seed \u2014 every memory in the selected pack(s) already exists.");
|
|
9827
|
+
return;
|
|
9828
|
+
}
|
|
9829
|
+
ui.success(`Seeded ${total} starter memor${total === 1 ? "y" : "ies"}: ${seededStacks.join(", ")}`);
|
|
9830
|
+
ui.info("Kept at background priority. Anchor them to a real file (or replace them) to make them high-signal:");
|
|
9831
|
+
ui.info(" haive memory update <id> --paths <key-file> # anchor a seed to a file");
|
|
9832
|
+
const config = await loadConfig6(paths);
|
|
9833
|
+
if (config.autopilot || config.autoRepair?.corpus === true) {
|
|
9834
|
+
const repairs = await applyAutopilotRepairs(root, paths, {
|
|
9835
|
+
applyConfig: false,
|
|
9836
|
+
applyContext: false,
|
|
9837
|
+
applyCorpus: true,
|
|
9838
|
+
applyCodeMap: false,
|
|
9839
|
+
applyCodeSearch: false
|
|
9840
|
+
});
|
|
9841
|
+
for (const repair of repairs) ui.info(repair.message);
|
|
9842
|
+
}
|
|
9843
|
+
});
|
|
9844
|
+
}
|
|
9845
|
+
|
|
9846
|
+
// src/commands/memory-pending.ts
|
|
9847
|
+
import { existsSync as existsSync43 } from "fs";
|
|
9848
|
+
import path28 from "path";
|
|
9849
|
+
import "commander";
|
|
9850
|
+
import {
|
|
9851
|
+
findProjectRoot as findProjectRoot24,
|
|
9621
9852
|
getUsage as getUsage14,
|
|
9622
9853
|
loadUsageIndex as loadUsageIndex16,
|
|
9623
|
-
resolveHaivePaths as
|
|
9854
|
+
resolveHaivePaths as resolveHaivePaths21
|
|
9624
9855
|
} from "@hiveai/core";
|
|
9625
9856
|
function registerMemoryPending(memory2) {
|
|
9626
9857
|
memory2.command("pending").description("List draft and proposed memories awaiting review (sorted by reads desc).\n\n draft = created but not yet activated \xB7 proposed = promoted, awaiting team validation").option("--scope <scope>", "filter by scope (personal | team | module)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9627
|
-
const root =
|
|
9628
|
-
const paths =
|
|
9629
|
-
if (!
|
|
9858
|
+
const root = findProjectRoot24(opts.dir);
|
|
9859
|
+
const paths = resolveHaivePaths21(root);
|
|
9860
|
+
if (!existsSync43(paths.memoriesDir)) {
|
|
9630
9861
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9631
9862
|
process.exitCode = 1;
|
|
9632
9863
|
return;
|
|
@@ -9660,7 +9891,7 @@ function registerMemoryPending(memory2) {
|
|
|
9660
9891
|
console.log(
|
|
9661
9892
|
` ${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count}`)}`
|
|
9662
9893
|
);
|
|
9663
|
-
console.log(` ${ui.dim(
|
|
9894
|
+
console.log(` ${ui.dim(path28.relative(root, filePath))}`);
|
|
9664
9895
|
}
|
|
9665
9896
|
if (proposed.length > 0) console.log(ui.dim(` \u2192 haive memory approve <id> or haive memory auto-promote`));
|
|
9666
9897
|
console.log();
|
|
@@ -9675,7 +9906,7 @@ function registerMemoryPending(memory2) {
|
|
|
9675
9906
|
console.log(
|
|
9676
9907
|
` ${ui.bold(fm.id)} ${ui.dim(`${fm.scope}/${fm.type}`)} ${ui.dim(`age=${ageStr} reads=${u.read_count}`)}`
|
|
9677
9908
|
);
|
|
9678
|
-
console.log(` ${ui.dim(
|
|
9909
|
+
console.log(` ${ui.dim(path28.relative(root, filePath))}`);
|
|
9679
9910
|
}
|
|
9680
9911
|
console.log(ui.dim(` \u2192 haive memory approve <id> (activate) | haive memory promote <id> (share with team)`));
|
|
9681
9912
|
}
|
|
@@ -9684,24 +9915,24 @@ function registerMemoryPending(memory2) {
|
|
|
9684
9915
|
}
|
|
9685
9916
|
|
|
9686
9917
|
// src/commands/memory-query.ts
|
|
9687
|
-
import { existsSync as
|
|
9688
|
-
import
|
|
9918
|
+
import { existsSync as existsSync44 } from "fs";
|
|
9919
|
+
import path29 from "path";
|
|
9689
9920
|
import "commander";
|
|
9690
9921
|
import {
|
|
9691
9922
|
extractSnippet as extractSnippet2,
|
|
9692
|
-
findProjectRoot as
|
|
9923
|
+
findProjectRoot as findProjectRoot25,
|
|
9693
9924
|
literalMatchesAllTokens as literalMatchesAllTokens3,
|
|
9694
9925
|
literalMatchesAnyToken as literalMatchesAnyToken4,
|
|
9695
9926
|
pickSnippetNeedle as pickSnippetNeedle2,
|
|
9696
|
-
resolveHaivePaths as
|
|
9927
|
+
resolveHaivePaths as resolveHaivePaths22,
|
|
9697
9928
|
tokenizeQuery as tokenizeQuery6,
|
|
9698
9929
|
trackReads as trackReads4
|
|
9699
9930
|
} from "@hiveai/core";
|
|
9700
9931
|
function registerMemoryQuery(memory2) {
|
|
9701
9932
|
memory2.command("query <text>").alias("search").description("Search memories by id, tag, or substring (AND, OR fallback). Alias: search").option("-d, --dir <dir>", "project root").option("--limit <n>", "max results", "20").option("--scope <scope>", "personal | team | module").option("--status <csv>", "filter by status (draft,proposed,validated,stale,rejected)").option("--show-rejected", "include rejected memories (hidden by default)").action(async (text, opts) => {
|
|
9702
|
-
const root =
|
|
9703
|
-
const paths =
|
|
9704
|
-
if (!
|
|
9933
|
+
const root = findProjectRoot25(opts.dir);
|
|
9934
|
+
const paths = resolveHaivePaths22(root);
|
|
9935
|
+
if (!existsSync44(paths.memoriesDir)) {
|
|
9705
9936
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
9706
9937
|
process.exitCode = 1;
|
|
9707
9938
|
return;
|
|
@@ -9742,7 +9973,7 @@ function registerMemoryQuery(memory2) {
|
|
|
9742
9973
|
const fm = mem.frontmatter;
|
|
9743
9974
|
const statusBadge = ui.statusBadge(fm.status);
|
|
9744
9975
|
console.log(`${ui.bold(fm.id)} ${ui.dim(fm.scope)} ${statusBadge}`);
|
|
9745
|
-
console.log(` ${ui.dim(
|
|
9976
|
+
console.log(` ${ui.dim(path29.relative(root, filePath))}`);
|
|
9746
9977
|
const snippet = extractSnippet2(mem.body, snippetNeedle);
|
|
9747
9978
|
if (snippet) console.log(` ${snippet}`);
|
|
9748
9979
|
}
|
|
@@ -9760,21 +9991,21 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
|
|
|
9760
9991
|
|
|
9761
9992
|
// src/commands/memory-reject.ts
|
|
9762
9993
|
import { writeFile as writeFile20 } from "fs/promises";
|
|
9763
|
-
import { existsSync as
|
|
9994
|
+
import { existsSync as existsSync45 } from "fs";
|
|
9764
9995
|
import "commander";
|
|
9765
9996
|
import {
|
|
9766
|
-
findProjectRoot as
|
|
9997
|
+
findProjectRoot as findProjectRoot26,
|
|
9767
9998
|
loadUsageIndex as loadUsageIndex17,
|
|
9768
9999
|
recordRejection as recordRejection2,
|
|
9769
|
-
resolveHaivePaths as
|
|
10000
|
+
resolveHaivePaths as resolveHaivePaths23,
|
|
9770
10001
|
saveUsageIndex as saveUsageIndex3,
|
|
9771
10002
|
serializeMemory as serializeMemory18
|
|
9772
10003
|
} from "@hiveai/core";
|
|
9773
10004
|
function registerMemoryReject(memory2) {
|
|
9774
10005
|
memory2.command("reject <id>").description("Record a rejection (blocks auto-promotion and lowers confidence)").option("-r, --reason <reason>", "why this memory is being rejected").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
9775
|
-
const root =
|
|
9776
|
-
const paths =
|
|
9777
|
-
if (!
|
|
10006
|
+
const root = findProjectRoot26(opts.dir);
|
|
10007
|
+
const paths = resolveHaivePaths23(root);
|
|
10008
|
+
if (!existsSync45(paths.memoriesDir)) {
|
|
9778
10009
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9779
10010
|
process.exitCode = 1;
|
|
9780
10011
|
return;
|
|
@@ -9810,22 +10041,22 @@ function registerMemoryReject(memory2) {
|
|
|
9810
10041
|
}
|
|
9811
10042
|
|
|
9812
10043
|
// src/commands/memory-rm.ts
|
|
9813
|
-
import { existsSync as
|
|
10044
|
+
import { existsSync as existsSync46 } from "fs";
|
|
9814
10045
|
import { unlink as unlink3 } from "fs/promises";
|
|
9815
|
-
import
|
|
10046
|
+
import path30 from "path";
|
|
9816
10047
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
9817
10048
|
import "commander";
|
|
9818
10049
|
import {
|
|
9819
|
-
findProjectRoot as
|
|
10050
|
+
findProjectRoot as findProjectRoot27,
|
|
9820
10051
|
loadUsageIndex as loadUsageIndex18,
|
|
9821
|
-
resolveHaivePaths as
|
|
10052
|
+
resolveHaivePaths as resolveHaivePaths24,
|
|
9822
10053
|
saveUsageIndex as saveUsageIndex4
|
|
9823
10054
|
} from "@hiveai/core";
|
|
9824
10055
|
function registerMemoryRm(memory2) {
|
|
9825
10056
|
memory2.command("rm <id>").description("Delete a memory file (and its usage entry by default)").option("-y, --yes", "skip the confirmation prompt").option("--keep-usage", "do not remove the usage.json entry").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
9826
|
-
const root =
|
|
9827
|
-
const paths =
|
|
9828
|
-
if (!
|
|
10057
|
+
const root = findProjectRoot27(opts.dir);
|
|
10058
|
+
const paths = resolveHaivePaths24(root);
|
|
10059
|
+
if (!existsSync46(paths.memoriesDir)) {
|
|
9829
10060
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9830
10061
|
process.exitCode = 1;
|
|
9831
10062
|
return;
|
|
@@ -9837,7 +10068,7 @@ function registerMemoryRm(memory2) {
|
|
|
9837
10068
|
process.exitCode = 1;
|
|
9838
10069
|
return;
|
|
9839
10070
|
}
|
|
9840
|
-
const rel =
|
|
10071
|
+
const rel = path30.relative(root, found.filePath);
|
|
9841
10072
|
if (!opts.yes) {
|
|
9842
10073
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
9843
10074
|
const answer = (await rl.question(`Delete ${rel}? [y/N] `)).trim().toLowerCase();
|
|
@@ -9861,22 +10092,22 @@ function registerMemoryRm(memory2) {
|
|
|
9861
10092
|
}
|
|
9862
10093
|
|
|
9863
10094
|
// src/commands/memory-show.ts
|
|
9864
|
-
import { existsSync as
|
|
9865
|
-
import { readFile as
|
|
9866
|
-
import
|
|
10095
|
+
import { existsSync as existsSync47 } from "fs";
|
|
10096
|
+
import { readFile as readFile14 } from "fs/promises";
|
|
10097
|
+
import path31 from "path";
|
|
9867
10098
|
import "commander";
|
|
9868
10099
|
import {
|
|
9869
10100
|
deriveConfidence as deriveConfidence10,
|
|
9870
|
-
findProjectRoot as
|
|
10101
|
+
findProjectRoot as findProjectRoot28,
|
|
9871
10102
|
getUsage as getUsage15,
|
|
9872
10103
|
loadUsageIndex as loadUsageIndex19,
|
|
9873
|
-
resolveHaivePaths as
|
|
10104
|
+
resolveHaivePaths as resolveHaivePaths25
|
|
9874
10105
|
} from "@hiveai/core";
|
|
9875
10106
|
function registerMemoryShow(memory2) {
|
|
9876
10107
|
memory2.command("show <id>").description("Print a memory's frontmatter, body, and confidence/usage").option("--raw", "print the raw file contents instead of a summary").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
9877
|
-
const root =
|
|
9878
|
-
const paths =
|
|
9879
|
-
if (!
|
|
10108
|
+
const root = findProjectRoot28(opts.dir);
|
|
10109
|
+
const paths = resolveHaivePaths25(root);
|
|
10110
|
+
if (!existsSync47(paths.memoriesDir)) {
|
|
9880
10111
|
ui.error(`No .ai/memories at ${root}.`);
|
|
9881
10112
|
process.exitCode = 1;
|
|
9882
10113
|
return;
|
|
@@ -9889,7 +10120,7 @@ function registerMemoryShow(memory2) {
|
|
|
9889
10120
|
return;
|
|
9890
10121
|
}
|
|
9891
10122
|
if (opts.raw) {
|
|
9892
|
-
console.log(await
|
|
10123
|
+
console.log(await readFile14(found.filePath, "utf8"));
|
|
9893
10124
|
return;
|
|
9894
10125
|
}
|
|
9895
10126
|
const fm = found.memory.frontmatter;
|
|
@@ -9905,7 +10136,7 @@ function registerMemoryShow(memory2) {
|
|
|
9905
10136
|
if (fm.verified_at) console.log(`${ui.dim("verified:")} ${fm.verified_at}`);
|
|
9906
10137
|
if (fm.stale_reason) console.log(`${ui.dim("stale:")} ${fm.stale_reason}`);
|
|
9907
10138
|
console.log(`${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`);
|
|
9908
|
-
console.log(`${ui.dim("file:")} ${
|
|
10139
|
+
console.log(`${ui.dim("file:")} ${path31.relative(root, found.filePath)}`);
|
|
9909
10140
|
if (fm.anchor.paths.length || fm.anchor.symbols.length) {
|
|
9910
10141
|
console.log(ui.dim("anchor:"));
|
|
9911
10142
|
if (fm.anchor.commit) console.log(` ${ui.dim("commit:")} ${fm.anchor.commit}`);
|
|
@@ -9920,21 +10151,21 @@ function registerMemoryShow(memory2) {
|
|
|
9920
10151
|
}
|
|
9921
10152
|
|
|
9922
10153
|
// src/commands/memory-stats.ts
|
|
9923
|
-
import { existsSync as
|
|
9924
|
-
import
|
|
10154
|
+
import { existsSync as existsSync48 } from "fs";
|
|
10155
|
+
import path33 from "path";
|
|
9925
10156
|
import "commander";
|
|
9926
10157
|
import {
|
|
9927
10158
|
deriveConfidence as deriveConfidence11,
|
|
9928
|
-
findProjectRoot as
|
|
10159
|
+
findProjectRoot as findProjectRoot29,
|
|
9929
10160
|
getUsage as getUsage16,
|
|
9930
10161
|
loadUsageIndex as loadUsageIndex20,
|
|
9931
|
-
resolveHaivePaths as
|
|
10162
|
+
resolveHaivePaths as resolveHaivePaths26
|
|
9932
10163
|
} from "@hiveai/core";
|
|
9933
10164
|
function registerMemoryStats(memory2) {
|
|
9934
10165
|
memory2.command("stats").description("Show usage stats and confidence levels per memory").option("--id <id>", "show stats for a single memory id").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9935
|
-
const root =
|
|
9936
|
-
const paths =
|
|
9937
|
-
if (!
|
|
10166
|
+
const root = findProjectRoot29(opts.dir);
|
|
10167
|
+
const paths = resolveHaivePaths26(root);
|
|
10168
|
+
if (!existsSync48(paths.memoriesDir)) {
|
|
9938
10169
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
9939
10170
|
process.exitCode = 1;
|
|
9940
10171
|
return;
|
|
@@ -9959,63 +10190,83 @@ function registerMemoryStats(memory2) {
|
|
|
9959
10190
|
console.log(
|
|
9960
10191
|
` ${ui.dim("status:")} ${fm.status} ${ui.dim("reads:")} ${u.read_count} ${ui.dim("rejections:")} ${u.rejected_count}`
|
|
9961
10192
|
);
|
|
9962
|
-
console.log(` ${ui.dim(
|
|
10193
|
+
console.log(` ${ui.dim(path33.relative(root, filePath))}`);
|
|
9963
10194
|
}
|
|
9964
10195
|
});
|
|
9965
10196
|
}
|
|
9966
10197
|
|
|
9967
10198
|
// src/commands/memory-verify.ts
|
|
9968
10199
|
import { writeFile as writeFile21 } from "fs/promises";
|
|
9969
|
-
import { existsSync as
|
|
9970
|
-
import
|
|
10200
|
+
import { existsSync as existsSync49 } from "fs";
|
|
10201
|
+
import path34 from "path";
|
|
9971
10202
|
import "commander";
|
|
9972
10203
|
import {
|
|
9973
|
-
findProjectRoot as
|
|
9974
|
-
resolveHaivePaths as
|
|
10204
|
+
findProjectRoot as findProjectRoot30,
|
|
10205
|
+
resolveHaivePaths as resolveHaivePaths27,
|
|
9975
10206
|
serializeMemory as serializeMemory19,
|
|
9976
10207
|
verifyAnchor as verifyAnchor3
|
|
9977
10208
|
} from "@hiveai/core";
|
|
9978
10209
|
function registerMemoryVerify(memory2) {
|
|
9979
10210
|
memory2.command("verify").description(
|
|
9980
10211
|
"Check that memory anchor paths still exist in the current codebase.\n\n A memory is 'stale' when its anchored file or symbol was moved, deleted, or renamed.\n Stale memories are shown with a warning in get_briefing and should be updated or deleted.\n\n haive sync runs this automatically. Use this command for on-demand checks or in CI.\n\n CI recommendation: add 'haive memory verify' to your haive-sync.yml PR check job\n to catch stale memories before they reach main.\n\n Examples:\n haive memory verify # check all, report only\n haive memory verify --update # mark stale/fresh on disk\n haive memory verify --id 2026-04-28-gotcha-x # check one memory\n"
|
|
9981
|
-
).option("--id <id>", "verify a single memory by id").option("--all", "verify every memory (default if --id is omitted)").option("--update", "write status=stale or status=validated back to disk").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
9982
|
-
const root =
|
|
9983
|
-
const paths =
|
|
9984
|
-
if (!
|
|
9985
|
-
|
|
10212
|
+
).option("--id <id>", "verify a single memory by id").option("--all", "verify every memory (default if --id is omitted)").option("--update", "write status=stale or status=validated back to disk").option("--json", "emit machine-readable JSON (for CI / agents)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10213
|
+
const root = findProjectRoot30(opts.dir);
|
|
10214
|
+
const paths = resolveHaivePaths27(root);
|
|
10215
|
+
if (!existsSync49(paths.memoriesDir)) {
|
|
10216
|
+
if (opts.json) {
|
|
10217
|
+
console.log(JSON.stringify({ error: "not-initialized", root }, null, 2));
|
|
10218
|
+
} else {
|
|
10219
|
+
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
10220
|
+
}
|
|
9986
10221
|
process.exitCode = 1;
|
|
9987
10222
|
return;
|
|
9988
10223
|
}
|
|
9989
10224
|
const all = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
9990
10225
|
const targets = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
|
|
9991
10226
|
if (opts.id && targets.length === 0) {
|
|
9992
|
-
|
|
10227
|
+
if (opts.json) {
|
|
10228
|
+
console.log(JSON.stringify({ error: "not-found", id: opts.id }, null, 2));
|
|
10229
|
+
} else {
|
|
10230
|
+
ui.error(`No memory with id "${opts.id}".`);
|
|
10231
|
+
}
|
|
9993
10232
|
process.exitCode = 1;
|
|
9994
10233
|
return;
|
|
9995
10234
|
}
|
|
9996
10235
|
let staleCount = 0;
|
|
9997
10236
|
let freshCount = 0;
|
|
9998
10237
|
const anchorlessIds = [];
|
|
10238
|
+
const entries = [];
|
|
9999
10239
|
let updated = 0;
|
|
10000
10240
|
for (const { memory: mem, filePath } of targets) {
|
|
10001
10241
|
const result = await verifyAnchor3(mem, { projectRoot: root });
|
|
10002
10242
|
const isAnchored = mem.frontmatter.anchor.paths.length > 0 || mem.frontmatter.anchor.symbols.length > 0;
|
|
10243
|
+
const rel = path34.relative(root, filePath);
|
|
10003
10244
|
if (!isAnchored) {
|
|
10004
10245
|
anchorlessIds.push(mem.frontmatter.id);
|
|
10246
|
+
entries.push({ id: mem.frontmatter.id, status: "anchorless", path: rel });
|
|
10005
10247
|
continue;
|
|
10006
10248
|
}
|
|
10007
|
-
const rel = path33.relative(root, filePath);
|
|
10008
10249
|
if (result.stale) {
|
|
10009
10250
|
staleCount++;
|
|
10010
|
-
|
|
10011
|
-
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10251
|
+
entries.push({
|
|
10252
|
+
id: mem.frontmatter.id,
|
|
10253
|
+
status: "stale",
|
|
10254
|
+
path: rel,
|
|
10255
|
+
reason: result.reason ?? void 0,
|
|
10256
|
+
possible_renames: result.possibleRenames
|
|
10257
|
+
});
|
|
10258
|
+
if (!opts.json) {
|
|
10259
|
+
console.log(`${ui.bold("STALE")} ${mem.frontmatter.id}`);
|
|
10260
|
+
console.log(` ${ui.dim(rel)}`);
|
|
10261
|
+
console.log(` ${result.reason}`);
|
|
10262
|
+
if (result.possibleRenames.length > 0) {
|
|
10263
|
+
console.log(` ${ui.yellow("Possible renames:")} ${result.possibleRenames.join(", ")}`);
|
|
10264
|
+
}
|
|
10015
10265
|
}
|
|
10016
10266
|
} else {
|
|
10017
10267
|
freshCount++;
|
|
10018
|
-
|
|
10268
|
+
entries.push({ id: mem.frontmatter.id, status: "fresh", path: rel });
|
|
10269
|
+
if (!opts.json) console.log(`${ui.dim("fresh")} ${mem.frontmatter.id}`);
|
|
10019
10270
|
}
|
|
10020
10271
|
if (opts.update) {
|
|
10021
10272
|
const next = applyVerification2(mem, result);
|
|
@@ -10023,6 +10274,20 @@ function registerMemoryVerify(memory2) {
|
|
|
10023
10274
|
updated++;
|
|
10024
10275
|
}
|
|
10025
10276
|
}
|
|
10277
|
+
if (opts.json) {
|
|
10278
|
+
console.log(JSON.stringify({
|
|
10279
|
+
summary: {
|
|
10280
|
+
checked: freshCount + staleCount,
|
|
10281
|
+
fresh: freshCount,
|
|
10282
|
+
stale: staleCount,
|
|
10283
|
+
anchorless: anchorlessIds.length,
|
|
10284
|
+
updated
|
|
10285
|
+
},
|
|
10286
|
+
results: entries
|
|
10287
|
+
}, null, 2));
|
|
10288
|
+
if (staleCount > 0) process.exitCode = 1;
|
|
10289
|
+
return;
|
|
10290
|
+
}
|
|
10026
10291
|
const summary = [
|
|
10027
10292
|
`${freshCount} fresh`,
|
|
10028
10293
|
`${staleCount} stale`,
|
|
@@ -10067,30 +10332,30 @@ function applyVerification2(mem, result) {
|
|
|
10067
10332
|
}
|
|
10068
10333
|
|
|
10069
10334
|
// src/commands/memory-import.ts
|
|
10070
|
-
import { readFile as
|
|
10071
|
-
import { existsSync as
|
|
10335
|
+
import { readFile as readFile15 } from "fs/promises";
|
|
10336
|
+
import { existsSync as existsSync50 } from "fs";
|
|
10072
10337
|
import "commander";
|
|
10073
10338
|
import {
|
|
10074
|
-
findProjectRoot as
|
|
10075
|
-
resolveHaivePaths as
|
|
10339
|
+
findProjectRoot as findProjectRoot31,
|
|
10340
|
+
resolveHaivePaths as resolveHaivePaths28
|
|
10076
10341
|
} from "@hiveai/core";
|
|
10077
10342
|
function registerMemoryImport(memory2) {
|
|
10078
10343
|
memory2.command("import").description(
|
|
10079
10344
|
"Parse a Markdown file and suggest memories via the import_docs MCP prompt (prints a ready-to-use prompt invocation)"
|
|
10080
10345
|
).requiredOption("--from <file>", "Markdown/text file to import from").option("--scope <scope>", "personal | team (default: team)", "team").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10081
|
-
const root =
|
|
10082
|
-
const paths =
|
|
10083
|
-
if (!
|
|
10346
|
+
const root = findProjectRoot31(opts.dir);
|
|
10347
|
+
const paths = resolveHaivePaths28(root);
|
|
10348
|
+
if (!existsSync50(paths.haiveDir)) {
|
|
10084
10349
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
10085
10350
|
process.exitCode = 1;
|
|
10086
10351
|
return;
|
|
10087
10352
|
}
|
|
10088
|
-
if (!
|
|
10353
|
+
if (!existsSync50(opts.from)) {
|
|
10089
10354
|
ui.error(`File not found: ${opts.from}`);
|
|
10090
10355
|
process.exitCode = 1;
|
|
10091
10356
|
return;
|
|
10092
10357
|
}
|
|
10093
|
-
const content = await
|
|
10358
|
+
const content = await readFile15(opts.from, "utf8");
|
|
10094
10359
|
const scope = opts.scope ?? "team";
|
|
10095
10360
|
ui.info(`Preparing import from: ${opts.from} (scope=${scope})`);
|
|
10096
10361
|
ui.info(`Content length: ${content.length} chars`);
|
|
@@ -10118,14 +10383,14 @@ function registerMemoryImport(memory2) {
|
|
|
10118
10383
|
}
|
|
10119
10384
|
|
|
10120
10385
|
// src/commands/memory-import-changelog.ts
|
|
10121
|
-
import { existsSync as
|
|
10122
|
-
import { readFile as
|
|
10123
|
-
import
|
|
10386
|
+
import { existsSync as existsSync51 } from "fs";
|
|
10387
|
+
import { readFile as readFile16, mkdir as mkdir14, writeFile as writeFile23 } from "fs/promises";
|
|
10388
|
+
import path35 from "path";
|
|
10124
10389
|
import "commander";
|
|
10125
10390
|
import {
|
|
10126
10391
|
buildFrontmatter as buildFrontmatter9,
|
|
10127
|
-
findProjectRoot as
|
|
10128
|
-
resolveHaivePaths as
|
|
10392
|
+
findProjectRoot as findProjectRoot32,
|
|
10393
|
+
resolveHaivePaths as resolveHaivePaths29,
|
|
10129
10394
|
serializeMemory as serializeMemory20
|
|
10130
10395
|
} from "@hiveai/core";
|
|
10131
10396
|
function parseChangelog(content) {
|
|
@@ -10190,15 +10455,15 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
10190
10455
|
"--versions <csv>",
|
|
10191
10456
|
"only import specific versions (comma-separated), or 'latest' for the most recent breaking version"
|
|
10192
10457
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10193
|
-
const root =
|
|
10194
|
-
const paths =
|
|
10195
|
-
const changelogPath =
|
|
10196
|
-
if (!
|
|
10458
|
+
const root = findProjectRoot32(opts.dir);
|
|
10459
|
+
const paths = resolveHaivePaths29(root);
|
|
10460
|
+
const changelogPath = path35.resolve(root, opts.fromChangelog);
|
|
10461
|
+
if (!existsSync51(changelogPath)) {
|
|
10197
10462
|
ui.error(`CHANGELOG not found: ${changelogPath}`);
|
|
10198
10463
|
process.exitCode = 1;
|
|
10199
10464
|
return;
|
|
10200
10465
|
}
|
|
10201
|
-
const content = await
|
|
10466
|
+
const content = await readFile16(changelogPath, "utf8");
|
|
10202
10467
|
let entries = parseChangelog(content);
|
|
10203
10468
|
if (entries.length === 0) {
|
|
10204
10469
|
ui.warn("No breaking changes, deprecations, or removals found in the CHANGELOG.");
|
|
@@ -10213,9 +10478,9 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
10213
10478
|
entries = entries.filter((e) => requested.includes(e.version));
|
|
10214
10479
|
}
|
|
10215
10480
|
}
|
|
10216
|
-
const pkgName = opts.package ??
|
|
10481
|
+
const pkgName = opts.package ?? path35.basename(path35.dirname(changelogPath));
|
|
10217
10482
|
const scope = opts.scope ?? "team";
|
|
10218
|
-
const teamDir =
|
|
10483
|
+
const teamDir = path35.join(paths.memoriesDir, scope);
|
|
10219
10484
|
await mkdir14(teamDir, { recursive: true });
|
|
10220
10485
|
let saved = 0;
|
|
10221
10486
|
for (const entry of entries) {
|
|
@@ -10238,7 +10503,7 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
10238
10503
|
lines.push("");
|
|
10239
10504
|
}
|
|
10240
10505
|
lines.push(
|
|
10241
|
-
`**Source:** \`${
|
|
10506
|
+
`**Source:** \`${path35.relative(root, changelogPath)}\`
|
|
10242
10507
|
**Action:** Update all usages of ${pkgName} if they rely on any of the above.`
|
|
10243
10508
|
);
|
|
10244
10509
|
const slug = `changelog-${pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase()}-v${entry.version.replace(/\./g, "-")}`;
|
|
@@ -10253,11 +10518,11 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
10253
10518
|
pkgName.replace(/[^a-z0-9]/gi, "-").toLowerCase(),
|
|
10254
10519
|
`v${entry.version}`
|
|
10255
10520
|
],
|
|
10256
|
-
paths: [
|
|
10521
|
+
paths: [path35.relative(root, changelogPath)],
|
|
10257
10522
|
topic: `changelog-${pkgName}-${entry.version}`
|
|
10258
10523
|
});
|
|
10259
10524
|
await writeFile23(
|
|
10260
|
-
|
|
10525
|
+
path35.join(teamDir, `${fm.id}.md`),
|
|
10261
10526
|
serializeMemory20({ frontmatter: fm, body: lines.join("\n") }),
|
|
10262
10527
|
"utf8"
|
|
10263
10528
|
);
|
|
@@ -10280,17 +10545,17 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
|
|
|
10280
10545
|
}
|
|
10281
10546
|
|
|
10282
10547
|
// src/commands/memory-digest.ts
|
|
10283
|
-
import { existsSync as
|
|
10548
|
+
import { existsSync as existsSync53 } from "fs";
|
|
10284
10549
|
import { writeFile as writeFile24 } from "fs/promises";
|
|
10285
|
-
import
|
|
10550
|
+
import path36 from "path";
|
|
10286
10551
|
import "commander";
|
|
10287
10552
|
import {
|
|
10288
10553
|
deriveConfidence as deriveConfidence12,
|
|
10289
|
-
findProjectRoot as
|
|
10554
|
+
findProjectRoot as findProjectRoot33,
|
|
10290
10555
|
getUsage as getUsage17,
|
|
10291
10556
|
loadMemoriesFromDir as loadMemoriesFromDir26,
|
|
10292
10557
|
loadUsageIndex as loadUsageIndex21,
|
|
10293
|
-
resolveHaivePaths as
|
|
10558
|
+
resolveHaivePaths as resolveHaivePaths30
|
|
10294
10559
|
} from "@hiveai/core";
|
|
10295
10560
|
var CONFIDENCE_EMOJI = {
|
|
10296
10561
|
unverified: "\u2B1C",
|
|
@@ -10303,9 +10568,9 @@ function registerMemoryDigest(program2) {
|
|
|
10303
10568
|
program2.command("digest").description(
|
|
10304
10569
|
"Generate a Markdown review digest of recently added or updated memories.\n\n Groups memories by type, shows confidence, status, read count, and anchor info.\n Each memory has action checkboxes (approve / reject / keep as-is) for peer review.\n\n Use this to do a bulk weekly review of team memories, or share with teammates\n as a pull-request attachment so humans can validate what the AI captured.\n\n Examples:\n haive memory digest # last 7 days, team scope\n haive memory digest --days 30 --scope all # last 30 days, all scopes\n haive memory digest --out review.md # write to file\n"
|
|
10305
10570
|
).option("--days <n>", "look-back window in days (default: 7)", "7").option("--scope <scope>", "personal | team | module | all (default: team)", "team").option("--out <file>", "write digest to a file instead of stdout").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10306
|
-
const root =
|
|
10307
|
-
const paths =
|
|
10308
|
-
if (!
|
|
10571
|
+
const root = findProjectRoot33(opts.dir);
|
|
10572
|
+
const paths = resolveHaivePaths30(root);
|
|
10573
|
+
if (!existsSync53(paths.memoriesDir)) {
|
|
10309
10574
|
ui.error("No .ai/memories found. Run `haive init` first.");
|
|
10310
10575
|
process.exitCode = 1;
|
|
10311
10576
|
return;
|
|
@@ -10377,7 +10642,7 @@ function registerMemoryDigest(program2) {
|
|
|
10377
10642
|
);
|
|
10378
10643
|
const digest = lines.join("\n");
|
|
10379
10644
|
if (opts.out) {
|
|
10380
|
-
const outPath =
|
|
10645
|
+
const outPath = path36.resolve(process.cwd(), opts.out);
|
|
10381
10646
|
await writeFile24(outPath, digest, "utf8");
|
|
10382
10647
|
ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
|
|
10383
10648
|
} else {
|
|
@@ -10387,23 +10652,23 @@ function registerMemoryDigest(program2) {
|
|
|
10387
10652
|
}
|
|
10388
10653
|
|
|
10389
10654
|
// src/commands/session-end.ts
|
|
10390
|
-
import { writeFile as writeFile25, mkdir as mkdir15, readFile as
|
|
10391
|
-
import { existsSync as
|
|
10655
|
+
import { writeFile as writeFile25, mkdir as mkdir15, readFile as readFile17, rm as rm2 } from "fs/promises";
|
|
10656
|
+
import { existsSync as existsSync54 } from "fs";
|
|
10392
10657
|
import { spawn as spawn4 } from "child_process";
|
|
10393
|
-
import
|
|
10658
|
+
import path37 from "path";
|
|
10394
10659
|
import "commander";
|
|
10395
10660
|
import {
|
|
10396
10661
|
buildFrontmatter as buildFrontmatter10,
|
|
10397
|
-
findProjectRoot as
|
|
10662
|
+
findProjectRoot as findProjectRoot34,
|
|
10398
10663
|
loadMemoriesFromDir as loadMemoriesFromDir27,
|
|
10399
10664
|
memoryFilePath as memoryFilePath9,
|
|
10400
|
-
resolveHaivePaths as
|
|
10665
|
+
resolveHaivePaths as resolveHaivePaths31,
|
|
10401
10666
|
serializeMemory as serializeMemory21
|
|
10402
10667
|
} from "@hiveai/core";
|
|
10403
10668
|
async function buildAutoRecap(paths) {
|
|
10404
|
-
const obsFile =
|
|
10405
|
-
if (!
|
|
10406
|
-
const raw = await
|
|
10669
|
+
const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
10670
|
+
if (!existsSync54(obsFile)) return await buildGitAutoRecap(paths);
|
|
10671
|
+
const raw = await readFile17(obsFile, "utf8").catch(() => "");
|
|
10407
10672
|
if (!raw.trim()) return await buildGitAutoRecap(paths);
|
|
10408
10673
|
const lines = raw.split("\n").filter(Boolean);
|
|
10409
10674
|
const obs = [];
|
|
@@ -10591,9 +10856,9 @@ function registerSessionEnd(session2) {
|
|
|
10591
10856
|
--next "Add integration tests for webhook signature validation"
|
|
10592
10857
|
`
|
|
10593
10858
|
).option("--goal <text>", "what you were trying to accomplish (1\u20132 sentences)").option("--accomplished <text>", "what was actually done (bullet list recommended)").option("--discoveries <text>", "bugs, surprises, or inconsistencies found during this session").option("--files <csv>", "key files touched, comma-separated (used as anchor for staleness detection)").option("--next <text>", "what should happen next (for the next session or a teammate)").option("--scope <scope>", "personal | team | module (default: personal)", "personal").option("--module <name>", "module name (required when scope=module)").option("--auto", "synthesize the recap from .ai/.cache/observations.jsonl (used by Claude Code SessionEnd hook)").option("--quiet", "suppress non-error output (for hook use)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10594
|
-
const root =
|
|
10595
|
-
const paths =
|
|
10596
|
-
if (!
|
|
10859
|
+
const root = findProjectRoot34(opts.dir);
|
|
10860
|
+
const paths = resolveHaivePaths31(root);
|
|
10861
|
+
if (!existsSync54(paths.haiveDir)) {
|
|
10597
10862
|
if (opts.auto || opts.quiet) return;
|
|
10598
10863
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
10599
10864
|
process.exitCode = 1;
|
|
@@ -10626,18 +10891,18 @@ function registerSessionEnd(session2) {
|
|
|
10626
10891
|
});
|
|
10627
10892
|
const topic = recapTopic2(scope, opts.module);
|
|
10628
10893
|
const filesTouched = parseCsv5(resolvedFiles).map((p) => normalizeAnchorPath(root, p));
|
|
10629
|
-
const missingPaths = filesTouched.filter((p) => !
|
|
10894
|
+
const missingPaths = filesTouched.filter((p) => !existsSync54(path37.resolve(root, p)));
|
|
10630
10895
|
if (missingPaths.length > 0 && !opts.quiet) {
|
|
10631
10896
|
ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
|
|
10632
10897
|
for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
|
|
10633
10898
|
}
|
|
10634
10899
|
const cleanupObservations = async () => {
|
|
10635
10900
|
if (!opts.auto) return;
|
|
10636
|
-
const obsFile =
|
|
10637
|
-
if (
|
|
10901
|
+
const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
10902
|
+
if (existsSync54(obsFile)) await rm2(obsFile).catch(() => {
|
|
10638
10903
|
});
|
|
10639
10904
|
};
|
|
10640
|
-
if (
|
|
10905
|
+
if (existsSync54(paths.memoriesDir)) {
|
|
10641
10906
|
const existing = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10642
10907
|
const topicMatch = existing.find(
|
|
10643
10908
|
({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
|
|
@@ -10658,7 +10923,7 @@ function registerSessionEnd(session2) {
|
|
|
10658
10923
|
await cleanupObservations();
|
|
10659
10924
|
if (!opts.quiet) {
|
|
10660
10925
|
ui.success(`Session recap updated (revision #${revisionCount})`);
|
|
10661
|
-
ui.info(`id=${fm.id} file=${
|
|
10926
|
+
ui.info(`id=${fm.id} file=${path37.relative(root, topicMatch.filePath)}`);
|
|
10662
10927
|
ui.info("Tip: `haive stats --export-report` generates a usage JSON suitable for dashboards.");
|
|
10663
10928
|
}
|
|
10664
10929
|
return;
|
|
@@ -10675,12 +10940,12 @@ function registerSessionEnd(session2) {
|
|
|
10675
10940
|
status: "validated"
|
|
10676
10941
|
});
|
|
10677
10942
|
const file = memoryFilePath9(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
10678
|
-
await mkdir15(
|
|
10943
|
+
await mkdir15(path37.dirname(file), { recursive: true });
|
|
10679
10944
|
await writeFile25(file, serializeMemory21({ frontmatter, body }), "utf8");
|
|
10680
10945
|
await cleanupObservations();
|
|
10681
10946
|
if (!opts.quiet) {
|
|
10682
10947
|
ui.success(`Session recap created`);
|
|
10683
|
-
ui.info(`id=${frontmatter.id} scope=${scope} file=${
|
|
10948
|
+
ui.info(`id=${frontmatter.id} scope=${scope} file=${path37.relative(root, file)}`);
|
|
10684
10949
|
ui.info("Next session: call `get_briefing` \u2014 the recap will be surfaced automatically.");
|
|
10685
10950
|
ui.info("Tip: export a local MCP usage rollup with `haive stats --export-report .ai/tool-usage-roi-report.json`.");
|
|
10686
10951
|
}
|
|
@@ -10692,22 +10957,22 @@ function parseCsv5(value) {
|
|
|
10692
10957
|
}
|
|
10693
10958
|
function normalizeAnchorPath(root, filePath) {
|
|
10694
10959
|
if (!filePath) return filePath;
|
|
10695
|
-
if (!
|
|
10696
|
-
const rel =
|
|
10960
|
+
if (!path37.isAbsolute(filePath)) return filePath;
|
|
10961
|
+
const rel = path37.relative(root, filePath);
|
|
10697
10962
|
if (rel.startsWith("..")) return filePath;
|
|
10698
10963
|
return rel;
|
|
10699
10964
|
}
|
|
10700
10965
|
|
|
10701
10966
|
// src/commands/snapshot.ts
|
|
10702
|
-
import { existsSync as
|
|
10967
|
+
import { existsSync as existsSync55 } from "fs";
|
|
10703
10968
|
import { readdir as readdir4 } from "fs/promises";
|
|
10704
|
-
import
|
|
10969
|
+
import path38 from "path";
|
|
10705
10970
|
import "commander";
|
|
10706
10971
|
import {
|
|
10707
10972
|
diffContract,
|
|
10708
|
-
findProjectRoot as
|
|
10709
|
-
loadConfig as
|
|
10710
|
-
resolveHaivePaths as
|
|
10973
|
+
findProjectRoot as findProjectRoot35,
|
|
10974
|
+
loadConfig as loadConfig7,
|
|
10975
|
+
resolveHaivePaths as resolveHaivePaths32,
|
|
10711
10976
|
snapshotContract
|
|
10712
10977
|
} from "@hiveai/core";
|
|
10713
10978
|
function registerSnapshot(program2) {
|
|
@@ -10732,16 +10997,16 @@ function registerSnapshot(program2) {
|
|
|
10732
10997
|
"--format <format>",
|
|
10733
10998
|
"contract format: openapi | graphql | proto | typescript | json-schema (auto-detected if omitted)"
|
|
10734
10999
|
).option("--diff", "compare the contract against its stored snapshot").option("--list", "list all stored contract snapshots").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10735
|
-
const root =
|
|
10736
|
-
const paths =
|
|
10737
|
-
if (!
|
|
11000
|
+
const root = findProjectRoot35(opts.dir);
|
|
11001
|
+
const paths = resolveHaivePaths32(root);
|
|
11002
|
+
if (!existsSync55(paths.haiveDir)) {
|
|
10738
11003
|
ui.error("No .ai/ found. Run `haive init` first.");
|
|
10739
11004
|
process.exitCode = 1;
|
|
10740
11005
|
return;
|
|
10741
11006
|
}
|
|
10742
11007
|
if (opts.list) {
|
|
10743
|
-
const contractsDir =
|
|
10744
|
-
if (!
|
|
11008
|
+
const contractsDir = path38.join(paths.haiveDir, "contracts");
|
|
11009
|
+
if (!existsSync55(contractsDir)) {
|
|
10745
11010
|
console.log(ui.dim("No contract snapshots found."));
|
|
10746
11011
|
return;
|
|
10747
11012
|
}
|
|
@@ -10761,7 +11026,7 @@ function registerSnapshot(program2) {
|
|
|
10761
11026
|
}
|
|
10762
11027
|
if (opts.diff) {
|
|
10763
11028
|
if (!opts.name) {
|
|
10764
|
-
const config2 = await
|
|
11029
|
+
const config2 = await loadConfig7(paths);
|
|
10765
11030
|
const contracts = config2.contractFiles ?? [];
|
|
10766
11031
|
if (contracts.length === 0) {
|
|
10767
11032
|
ui.error("--diff requires --name, or configure contractFiles in haive.config.json");
|
|
@@ -10773,7 +11038,7 @@ function registerSnapshot(program2) {
|
|
|
10773
11038
|
}
|
|
10774
11039
|
return;
|
|
10775
11040
|
}
|
|
10776
|
-
const config = await
|
|
11041
|
+
const config = await loadConfig7(paths);
|
|
10777
11042
|
const configured = (config.contractFiles ?? []).find((c) => c.name === opts.name);
|
|
10778
11043
|
if (!configured && !opts.contract) {
|
|
10779
11044
|
ui.error(
|
|
@@ -10796,7 +11061,7 @@ function registerSnapshot(program2) {
|
|
|
10796
11061
|
return;
|
|
10797
11062
|
}
|
|
10798
11063
|
const contractPath = opts.contract;
|
|
10799
|
-
const name = opts.name ??
|
|
11064
|
+
const name = opts.name ?? path38.basename(contractPath, path38.extname(contractPath));
|
|
10800
11065
|
const format = opts.format ?? detectFormat(contractPath) ?? "openapi";
|
|
10801
11066
|
const contract = { name, path: contractPath, format };
|
|
10802
11067
|
try {
|
|
@@ -10851,8 +11116,8 @@ async function runDiff(root, haiveDir, contract) {
|
|
|
10851
11116
|
}
|
|
10852
11117
|
}
|
|
10853
11118
|
function detectFormat(filePath) {
|
|
10854
|
-
const ext =
|
|
10855
|
-
const base =
|
|
11119
|
+
const ext = path38.extname(filePath).toLowerCase();
|
|
11120
|
+
const base = path38.basename(filePath).toLowerCase();
|
|
10856
11121
|
if (ext === ".yaml" || ext === ".yml" || ext === ".json") {
|
|
10857
11122
|
if (base.includes("openapi") || base.includes("swagger")) return "openapi";
|
|
10858
11123
|
if (base.includes("schema") || base.includes("graphql")) return "graphql";
|
|
@@ -10865,16 +11130,16 @@ function detectFormat(filePath) {
|
|
|
10865
11130
|
}
|
|
10866
11131
|
|
|
10867
11132
|
// src/commands/hub.ts
|
|
10868
|
-
import { existsSync as
|
|
10869
|
-
import { mkdir as mkdir16, readFile as
|
|
10870
|
-
import
|
|
11133
|
+
import { existsSync as existsSync56 } from "fs";
|
|
11134
|
+
import { mkdir as mkdir16, readFile as readFile18, writeFile as writeFile26, copyFile } from "fs/promises";
|
|
11135
|
+
import path39 from "path";
|
|
10871
11136
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
10872
11137
|
import "commander";
|
|
10873
11138
|
import {
|
|
10874
|
-
findProjectRoot as
|
|
10875
|
-
loadConfig as
|
|
11139
|
+
findProjectRoot as findProjectRoot36,
|
|
11140
|
+
loadConfig as loadConfig8,
|
|
10876
11141
|
loadMemoriesFromDir as loadMemoriesFromDir28,
|
|
10877
|
-
resolveHaivePaths as
|
|
11142
|
+
resolveHaivePaths as resolveHaivePaths33,
|
|
10878
11143
|
saveConfig as saveConfig3,
|
|
10879
11144
|
serializeMemory as serializeMemory23
|
|
10880
11145
|
} from "@hiveai/core";
|
|
@@ -10886,7 +11151,7 @@ function registerHub(program2) {
|
|
|
10886
11151
|
hub.command("init <hubPath>").description(
|
|
10887
11152
|
"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"
|
|
10888
11153
|
).action(async (hubPath) => {
|
|
10889
|
-
const absPath =
|
|
11154
|
+
const absPath = path39.resolve(hubPath);
|
|
10890
11155
|
await mkdir16(absPath, { recursive: true });
|
|
10891
11156
|
const gitCheck = spawnSync5("git", ["rev-parse", "--git-dir"], { cwd: absPath });
|
|
10892
11157
|
if (gitCheck.status !== 0) {
|
|
@@ -10897,10 +11162,10 @@ function registerHub(program2) {
|
|
|
10897
11162
|
return;
|
|
10898
11163
|
}
|
|
10899
11164
|
}
|
|
10900
|
-
const sharedDir =
|
|
11165
|
+
const sharedDir = path39.join(absPath, ".ai", "memories", "shared");
|
|
10901
11166
|
await mkdir16(sharedDir, { recursive: true });
|
|
10902
11167
|
await writeFile26(
|
|
10903
|
-
|
|
11168
|
+
path39.join(absPath, ".ai", "README.md"),
|
|
10904
11169
|
`# hAIve Team Knowledge Hub
|
|
10905
11170
|
|
|
10906
11171
|
This repo is a shared knowledge hub for hAIve.
|
|
@@ -10922,7 +11187,7 @@ haive hub pull # import into a project
|
|
|
10922
11187
|
"utf8"
|
|
10923
11188
|
);
|
|
10924
11189
|
await writeFile26(
|
|
10925
|
-
|
|
11190
|
+
path39.join(absPath, ".gitignore"),
|
|
10926
11191
|
".ai/.cache/\n.ai/memories/personal/\n",
|
|
10927
11192
|
"utf8"
|
|
10928
11193
|
);
|
|
@@ -10937,7 +11202,7 @@ haive hub pull # import into a project
|
|
|
10937
11202
|
`
|
|
10938
11203
|
Next steps:
|
|
10939
11204
|
1. Add hubPath to your project's .ai/haive.config.json:
|
|
10940
|
-
{ "hubPath": "${
|
|
11205
|
+
{ "hubPath": "${path39.relative(process.cwd(), absPath)}" }
|
|
10941
11206
|
2. Run \`haive hub push\` to publish your shared memories
|
|
10942
11207
|
3. Share ${absPath} with teammates (git remote, NFS, etc.)
|
|
10943
11208
|
`
|
|
@@ -10956,9 +11221,9 @@ Next steps:
|
|
|
10956
11221
|
haive hub push --commit --message "feat: add payment API contract memories"
|
|
10957
11222
|
`
|
|
10958
11223
|
).option("-d, --dir <dir>", "project root").option("--commit", "auto-commit to the hub repo after pushing").option("--message <msg>", "commit message for the hub (used with --commit)").action(async (opts) => {
|
|
10959
|
-
const root =
|
|
10960
|
-
const paths =
|
|
10961
|
-
const config = await
|
|
11224
|
+
const root = findProjectRoot36(opts.dir);
|
|
11225
|
+
const paths = resolveHaivePaths33(root);
|
|
11226
|
+
const config = await loadConfig8(paths);
|
|
10962
11227
|
if (!config.hubPath) {
|
|
10963
11228
|
ui.error(
|
|
10964
11229
|
'hubPath not configured in .ai/haive.config.json.\n Add: { "hubPath": "../team-hub" }\n Or run: haive hub init <path> first.'
|
|
@@ -10966,14 +11231,14 @@ Next steps:
|
|
|
10966
11231
|
process.exitCode = 1;
|
|
10967
11232
|
return;
|
|
10968
11233
|
}
|
|
10969
|
-
const hubRoot =
|
|
10970
|
-
if (!
|
|
11234
|
+
const hubRoot = path39.resolve(root, config.hubPath);
|
|
11235
|
+
if (!existsSync56(hubRoot)) {
|
|
10971
11236
|
ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
|
|
10972
11237
|
process.exitCode = 1;
|
|
10973
11238
|
return;
|
|
10974
11239
|
}
|
|
10975
|
-
const projectName =
|
|
10976
|
-
const destDir =
|
|
11240
|
+
const projectName = path39.basename(root);
|
|
11241
|
+
const destDir = path39.join(hubRoot, ".ai", "memories", "shared", projectName);
|
|
10977
11242
|
await mkdir16(destDir, { recursive: true });
|
|
10978
11243
|
const all = await loadMemoriesFromDir28(paths.memoriesDir);
|
|
10979
11244
|
const shared = all.filter(
|
|
@@ -10992,7 +11257,7 @@ Next steps:
|
|
|
10992
11257
|
for (const { memory: memory2 } of shared) {
|
|
10993
11258
|
const fm = memory2.frontmatter;
|
|
10994
11259
|
const fileName = `${fm.id}.md`;
|
|
10995
|
-
const destPath =
|
|
11260
|
+
const destPath = path39.join(destDir, fileName);
|
|
10996
11261
|
await writeFile26(destPath, serializeMemory23(memory2), "utf8");
|
|
10997
11262
|
pushed++;
|
|
10998
11263
|
}
|
|
@@ -11000,7 +11265,7 @@ Next steps:
|
|
|
11000
11265
|
console.log(ui.dim(` Location: ${destDir}`));
|
|
11001
11266
|
if (opts.commit) {
|
|
11002
11267
|
const message = opts.message ?? `haive: sync shared memories from ${projectName} (${pushed} memories)`;
|
|
11003
|
-
spawnSync5("git", ["add",
|
|
11268
|
+
spawnSync5("git", ["add", path39.join(".ai", "memories", "shared", projectName)], {
|
|
11004
11269
|
cwd: hubRoot
|
|
11005
11270
|
});
|
|
11006
11271
|
const commit = spawnSync5("git", ["commit", "-m", message], {
|
|
@@ -11025,9 +11290,9 @@ Next steps:
|
|
|
11025
11290
|
hub.command("pull").description(
|
|
11026
11291
|
"Pull shared memories from the hub into this project.\n\n Imports all memories from hub/.ai/memories/shared/ EXCEPT this project's own.\n Imported memories land in .ai/memories/shared/<source-project-name>/.\n\n Examples:\n haive hub pull\n"
|
|
11027
11292
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11028
|
-
const root =
|
|
11029
|
-
const paths =
|
|
11030
|
-
const config = await
|
|
11293
|
+
const root = findProjectRoot36(opts.dir);
|
|
11294
|
+
const paths = resolveHaivePaths33(root);
|
|
11295
|
+
const config = await loadConfig8(paths);
|
|
11031
11296
|
if (!config.hubPath) {
|
|
11032
11297
|
ui.error(
|
|
11033
11298
|
'hubPath not configured in .ai/haive.config.json.\n Add: { "hubPath": "../team-hub" }\n Or run: haive hub init <path> first.'
|
|
@@ -11035,13 +11300,13 @@ Next steps:
|
|
|
11035
11300
|
process.exitCode = 1;
|
|
11036
11301
|
return;
|
|
11037
11302
|
}
|
|
11038
|
-
const hubRoot =
|
|
11039
|
-
const hubSharedDir =
|
|
11040
|
-
if (!
|
|
11303
|
+
const hubRoot = path39.resolve(root, config.hubPath);
|
|
11304
|
+
const hubSharedDir = path39.join(hubRoot, ".ai", "memories", "shared");
|
|
11305
|
+
if (!existsSync56(hubSharedDir)) {
|
|
11041
11306
|
ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
|
|
11042
11307
|
return;
|
|
11043
11308
|
}
|
|
11044
|
-
const projectName =
|
|
11309
|
+
const projectName = path39.basename(root);
|
|
11045
11310
|
const { readdir: readdir7 } = await import("fs/promises");
|
|
11046
11311
|
const projectDirs = (await readdir7(hubSharedDir, { withFileTypes: true })).filter((d) => d.isDirectory() && d.name !== projectName).map((d) => d.name);
|
|
11047
11312
|
if (projectDirs.length === 0) {
|
|
@@ -11051,17 +11316,17 @@ Next steps:
|
|
|
11051
11316
|
let totalImported = 0;
|
|
11052
11317
|
let totalUpdated = 0;
|
|
11053
11318
|
for (const sourceName of projectDirs) {
|
|
11054
|
-
const sourceDir =
|
|
11055
|
-
const destDir =
|
|
11319
|
+
const sourceDir = path39.join(hubSharedDir, sourceName);
|
|
11320
|
+
const destDir = path39.join(paths.memoriesDir, "shared", sourceName);
|
|
11056
11321
|
await mkdir16(destDir, { recursive: true });
|
|
11057
11322
|
const sourceFiles = (await readdir7(sourceDir)).filter((f) => f.endsWith(".md"));
|
|
11058
11323
|
const { loadMemoriesFromDir: loadDir } = await import("@hiveai/core");
|
|
11059
11324
|
const existingInDest = await loadDir(destDir);
|
|
11060
11325
|
const existingIds = new Set(existingInDest.map(({ memory: memory2 }) => memory2.frontmatter.id));
|
|
11061
11326
|
for (const file of sourceFiles) {
|
|
11062
|
-
const srcPath =
|
|
11063
|
-
const destPath =
|
|
11064
|
-
const fileContent = await
|
|
11327
|
+
const srcPath = path39.join(sourceDir, file);
|
|
11328
|
+
const destPath = path39.join(destDir, file);
|
|
11329
|
+
const fileContent = await readFile18(srcPath, "utf8");
|
|
11065
11330
|
const alreadyTagged = fileContent.includes(`cross-repo:${sourceName}`);
|
|
11066
11331
|
if (!alreadyTagged) {
|
|
11067
11332
|
await copyFile(srcPath, destPath);
|
|
@@ -11084,21 +11349,21 @@ Next steps:
|
|
|
11084
11349
|
);
|
|
11085
11350
|
});
|
|
11086
11351
|
hub.command("status").description("Show hub sync status.").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11087
|
-
const root =
|
|
11088
|
-
const paths =
|
|
11089
|
-
const config = await
|
|
11352
|
+
const root = findProjectRoot36(opts.dir);
|
|
11353
|
+
const paths = resolveHaivePaths33(root);
|
|
11354
|
+
const config = await loadConfig8(paths);
|
|
11090
11355
|
console.log(ui.bold("Hub status"));
|
|
11091
11356
|
console.log(
|
|
11092
11357
|
` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
|
|
11093
11358
|
);
|
|
11094
|
-
const sharedDir =
|
|
11095
|
-
if (
|
|
11359
|
+
const sharedDir = path39.join(paths.memoriesDir, "shared");
|
|
11360
|
+
if (existsSync56(sharedDir)) {
|
|
11096
11361
|
const { readdir: readdir7 } = await import("fs/promises");
|
|
11097
11362
|
const sources = (await readdir7(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
11098
11363
|
console.log(`
|
|
11099
11364
|
Imported from ${sources.length} source(s):`);
|
|
11100
11365
|
for (const src of sources) {
|
|
11101
|
-
const files = (await readdir7(
|
|
11366
|
+
const files = (await readdir7(path39.join(sharedDir, src))).filter((f) => f.endsWith(".md"));
|
|
11102
11367
|
console.log(` ${src}: ${files.length} memor${files.length === 1 ? "y" : "ies"}`);
|
|
11103
11368
|
}
|
|
11104
11369
|
} else {
|
|
@@ -11113,7 +11378,7 @@ Next steps:
|
|
|
11113
11378
|
if (outgoing.length > 0) {
|
|
11114
11379
|
console.log(ui.dim(" Run `haive hub push` to publish them to the hub."));
|
|
11115
11380
|
}
|
|
11116
|
-
void
|
|
11381
|
+
void readFile18;
|
|
11117
11382
|
void writeFile26;
|
|
11118
11383
|
void saveConfig3;
|
|
11119
11384
|
});
|
|
@@ -11121,17 +11386,17 @@ Next steps:
|
|
|
11121
11386
|
|
|
11122
11387
|
// src/commands/stats.ts
|
|
11123
11388
|
import "commander";
|
|
11124
|
-
import { existsSync as
|
|
11389
|
+
import { existsSync as existsSync57 } from "fs";
|
|
11125
11390
|
import { mkdir as mkdir17, writeFile as writeFile27 } from "fs/promises";
|
|
11126
|
-
import
|
|
11391
|
+
import path40 from "path";
|
|
11127
11392
|
import {
|
|
11128
11393
|
aggregateUsage,
|
|
11129
|
-
findProjectRoot as
|
|
11394
|
+
findProjectRoot as findProjectRoot37,
|
|
11130
11395
|
loadMemoriesFromDir as loadMemoriesFromDir29,
|
|
11131
11396
|
loadUsageIndex as loadUsageIndex23,
|
|
11132
11397
|
parseSince,
|
|
11133
11398
|
readUsageEvents as readUsageEvents2,
|
|
11134
|
-
resolveHaivePaths as
|
|
11399
|
+
resolveHaivePaths as resolveHaivePaths34,
|
|
11135
11400
|
usageLogSize
|
|
11136
11401
|
} from "@hiveai/core";
|
|
11137
11402
|
function registerStats(program2) {
|
|
@@ -11140,8 +11405,8 @@ function registerStats(program2) {
|
|
|
11140
11405
|
"write a JSON rollup (tools + briefing counts + heuristic ROI hints). Parent dirs are created if needed.",
|
|
11141
11406
|
void 0
|
|
11142
11407
|
).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11143
|
-
const root =
|
|
11144
|
-
const paths =
|
|
11408
|
+
const root = findProjectRoot37(opts.dir);
|
|
11409
|
+
const paths = resolveHaivePaths34(root);
|
|
11145
11410
|
if (opts.exportReport) {
|
|
11146
11411
|
await writeRoiReport(paths, root, opts.since ?? "30d", opts.exportReport);
|
|
11147
11412
|
return;
|
|
@@ -11195,11 +11460,11 @@ function registerStats(program2) {
|
|
|
11195
11460
|
});
|
|
11196
11461
|
}
|
|
11197
11462
|
async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
11198
|
-
const outAbs =
|
|
11463
|
+
const outAbs = path40.isAbsolute(outRelative) ? path40.resolve(outRelative) : path40.resolve(root, outRelative);
|
|
11199
11464
|
const size = await usageLogSize(paths);
|
|
11200
11465
|
let events = await readUsageEvents2(paths);
|
|
11201
11466
|
let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
|
|
11202
|
-
if (
|
|
11467
|
+
if (existsSync57(paths.memoriesDir)) {
|
|
11203
11468
|
const mems = await loadMemoriesFromDir29(paths.memoriesDir);
|
|
11204
11469
|
for (const { memory: memory2 } of mems) {
|
|
11205
11470
|
const fm = memory2.frontmatter;
|
|
@@ -11230,7 +11495,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
11230
11495
|
ui.warn("Usage log missing or empty \u2014 report still written with partial data.");
|
|
11231
11496
|
events = [];
|
|
11232
11497
|
}
|
|
11233
|
-
await mkdir17(
|
|
11498
|
+
await mkdir17(path40.dirname(outAbs), { recursive: true });
|
|
11234
11499
|
const payload = {
|
|
11235
11500
|
generated_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11236
11501
|
project_root: root,
|
|
@@ -11294,13 +11559,13 @@ import { performance } from "perf_hooks";
|
|
|
11294
11559
|
import "commander";
|
|
11295
11560
|
import {
|
|
11296
11561
|
estimateTokens as estimateTokens3,
|
|
11297
|
-
findProjectRoot as
|
|
11298
|
-
resolveHaivePaths as
|
|
11562
|
+
findProjectRoot as findProjectRoot38,
|
|
11563
|
+
resolveHaivePaths as resolveHaivePaths35
|
|
11299
11564
|
} from "@hiveai/core";
|
|
11300
11565
|
function registerBench(program2) {
|
|
11301
11566
|
program2.command("bench").description("Self-test the local hAIve setup: runs core MCP tools against this project and reports latency + payload size.").option("-t, --task <task>", "task description for ranking-aware tools", "audit dependencies for security risks").option("--json", "emit JSON instead of a table", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11302
|
-
const root =
|
|
11303
|
-
const paths =
|
|
11567
|
+
const root = findProjectRoot38(opts.dir);
|
|
11568
|
+
const paths = resolveHaivePaths35(root);
|
|
11304
11569
|
const ctx = { paths };
|
|
11305
11570
|
const task = opts.task ?? "audit dependencies for security risks";
|
|
11306
11571
|
const scenarios = [
|
|
@@ -11419,11 +11684,11 @@ function summarize(name, t0, payload, notes) {
|
|
|
11419
11684
|
}
|
|
11420
11685
|
|
|
11421
11686
|
// src/commands/benchmark.ts
|
|
11422
|
-
import { existsSync as
|
|
11423
|
-
import { readdir as readdir5, readFile as
|
|
11424
|
-
import
|
|
11687
|
+
import { existsSync as existsSync58 } from "fs";
|
|
11688
|
+
import { readdir as readdir5, readFile as readFile19, writeFile as writeFile28 } from "fs/promises";
|
|
11689
|
+
import path41 from "path";
|
|
11425
11690
|
import "commander";
|
|
11426
|
-
import { estimateTokens as estimateTokens4, findProjectRoot as
|
|
11691
|
+
import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot39 } from "@hiveai/core";
|
|
11427
11692
|
function registerBenchmark(program2) {
|
|
11428
11693
|
const benchmark = program2.command("benchmark").description("Official hAIve benchmark/demo utilities for measuring agent enforcement value.");
|
|
11429
11694
|
benchmark.command("report").description("Summarize BENCHMARK_AGENT_REPORT.md files from a paired hAIve/plain agent benchmark.").option("-d, --dir <dir>", "benchmark root", "benchmarks/agent-benchmark").option("--out <file>", "write a Markdown report").option("--json", "emit JSON", false).action(async (opts) => {
|
|
@@ -11436,9 +11701,9 @@ function registerBenchmark(program2) {
|
|
|
11436
11701
|
}
|
|
11437
11702
|
const markdown = renderMarkdown(root, summary, rows);
|
|
11438
11703
|
if (opts.out) {
|
|
11439
|
-
const outFile =
|
|
11704
|
+
const outFile = path41.isAbsolute(opts.out) ? opts.out : path41.join(root, opts.out);
|
|
11440
11705
|
await writeFile28(outFile, markdown, "utf8");
|
|
11441
|
-
ui.success(`wrote ${
|
|
11706
|
+
ui.success(`wrote ${path41.relative(process.cwd(), outFile)}`);
|
|
11442
11707
|
return;
|
|
11443
11708
|
}
|
|
11444
11709
|
console.log(markdown);
|
|
@@ -11462,20 +11727,20 @@ function registerBenchmark(program2) {
|
|
|
11462
11727
|
}
|
|
11463
11728
|
function resolveBenchmarkRoot(dir) {
|
|
11464
11729
|
const candidate = dir ?? "benchmarks/agent-benchmark";
|
|
11465
|
-
if (
|
|
11466
|
-
const projectRoot =
|
|
11467
|
-
return
|
|
11730
|
+
if (path41.isAbsolute(candidate)) return candidate;
|
|
11731
|
+
const projectRoot = findProjectRoot39(process.cwd());
|
|
11732
|
+
return path41.join(projectRoot, candidate);
|
|
11468
11733
|
}
|
|
11469
11734
|
async function collectRows(root) {
|
|
11470
|
-
if (!
|
|
11735
|
+
if (!existsSync58(root)) throw new Error(`Benchmark directory not found: ${root}`);
|
|
11471
11736
|
const entries = await readdir5(root, { withFileTypes: true });
|
|
11472
11737
|
const rows = [];
|
|
11473
11738
|
for (const entry of entries) {
|
|
11474
11739
|
if (!entry.isDirectory()) continue;
|
|
11475
|
-
const fixtureDir =
|
|
11476
|
-
const reportFile =
|
|
11477
|
-
if (!
|
|
11478
|
-
const report = await
|
|
11740
|
+
const fixtureDir = path41.join(root, entry.name);
|
|
11741
|
+
const reportFile = path41.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
|
|
11742
|
+
if (!existsSync58(reportFile)) continue;
|
|
11743
|
+
const report = await readFile19(reportFile, "utf8");
|
|
11479
11744
|
rows.push(parseAgentReport(entry.name, report));
|
|
11480
11745
|
}
|
|
11481
11746
|
return rows.sort((a, b) => a.fixture.localeCompare(b.fixture));
|
|
@@ -11565,19 +11830,19 @@ function escapeRegExp(value) {
|
|
|
11565
11830
|
|
|
11566
11831
|
// src/commands/memory-suggest.ts
|
|
11567
11832
|
import { mkdir as mkdir18, writeFile as writeFile29 } from "fs/promises";
|
|
11568
|
-
import { existsSync as
|
|
11569
|
-
import
|
|
11833
|
+
import { existsSync as existsSync59 } from "fs";
|
|
11834
|
+
import path43 from "path";
|
|
11570
11835
|
import "commander";
|
|
11571
11836
|
import {
|
|
11572
11837
|
aggregateUsage as aggregateUsage2,
|
|
11573
11838
|
buildFrontmatter as buildFrontmatter11,
|
|
11574
|
-
findProjectRoot as
|
|
11575
|
-
loadConfig as
|
|
11839
|
+
findProjectRoot as findProjectRoot40,
|
|
11840
|
+
loadConfig as loadConfig9,
|
|
11576
11841
|
loadMemoriesFromDir as loadMemoriesFromDir30,
|
|
11577
11842
|
memoryFilePath as memoryFilePath10,
|
|
11578
11843
|
parseSince as parseSince2,
|
|
11579
11844
|
readUsageEvents as readUsageEvents3,
|
|
11580
|
-
resolveHaivePaths as
|
|
11845
|
+
resolveHaivePaths as resolveHaivePaths36,
|
|
11581
11846
|
serializeMemory as serializeMemory24
|
|
11582
11847
|
} from "@hiveai/core";
|
|
11583
11848
|
var SEARCH_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -11594,8 +11859,8 @@ function registerMemorySuggest(memory2) {
|
|
|
11594
11859
|
memory2.command("suggest").description(
|
|
11595
11860
|
"Suggest memories to create based on recurring search queries in the usage log.\n\n Use --auto-save to save the top-N suggestions using the project defaults.\n In autopilot, suggestions land as validated team records; in manual mode they stay draft."
|
|
11596
11861
|
).option("--since <window>", "ISO date or relative (e.g. '7d', '24h')", "30d").option("--min <count>", "minimum repeat count to surface a query", "2").option("--top-n <n>", "with --auto-save, draft this many top suggestions", "3").option("--scope <scope>", "with --auto-save, scope of saved memories (personal | team; default: config default)").option("--auto-save", "save top-N suggestions as memories on disk", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11597
|
-
const root =
|
|
11598
|
-
const paths =
|
|
11862
|
+
const root = findProjectRoot40(opts.dir);
|
|
11863
|
+
const paths = resolveHaivePaths36(root);
|
|
11599
11864
|
const events = await readUsageEvents3(paths);
|
|
11600
11865
|
if (events.length === 0) {
|
|
11601
11866
|
if (opts.json) {
|
|
@@ -11633,7 +11898,7 @@ function registerMemorySuggest(memory2) {
|
|
|
11633
11898
|
inferred_type: inferType(v.tools, query)
|
|
11634
11899
|
})).sort((a, b) => b.count - a.count);
|
|
11635
11900
|
if (opts.autoSave) {
|
|
11636
|
-
const config = await
|
|
11901
|
+
const config = await loadConfig9(paths);
|
|
11637
11902
|
const topN = Math.max(1, parseInt(opts.topN ?? "3", 10));
|
|
11638
11903
|
const scope = opts.scope === "personal" || opts.scope === "team" ? opts.scope : config.defaultScope ?? "personal";
|
|
11639
11904
|
const status = config.defaultStatus === "validated" ? "validated" : "draft";
|
|
@@ -11644,7 +11909,7 @@ function registerMemorySuggest(memory2) {
|
|
|
11644
11909
|
}
|
|
11645
11910
|
const created = [];
|
|
11646
11911
|
const skipped = [];
|
|
11647
|
-
const existing =
|
|
11912
|
+
const existing = existsSync59(paths.memoriesDir) ? await loadMemoriesFromDir30(paths.memoriesDir) : [];
|
|
11648
11913
|
for (const s of top) {
|
|
11649
11914
|
const slug = slugify2(s.query);
|
|
11650
11915
|
if (!slug) {
|
|
@@ -11667,13 +11932,13 @@ function registerMemorySuggest(memory2) {
|
|
|
11667
11932
|
});
|
|
11668
11933
|
const body = renderTemplate(s, fm.id, status);
|
|
11669
11934
|
const file = memoryFilePath10(paths, fm.scope, fm.id, fm.module);
|
|
11670
|
-
await mkdir18(
|
|
11671
|
-
if (
|
|
11672
|
-
skipped.push({ query: s.query, reason: `file already exists at ${
|
|
11935
|
+
await mkdir18(path43.dirname(file), { recursive: true });
|
|
11936
|
+
if (existsSync59(file)) {
|
|
11937
|
+
skipped.push({ query: s.query, reason: `file already exists at ${path43.relative(root, file)}` });
|
|
11673
11938
|
continue;
|
|
11674
11939
|
}
|
|
11675
11940
|
await writeFile29(file, serializeMemory24({ frontmatter: fm, body }), "utf8");
|
|
11676
|
-
created.push({ id: fm.id, file:
|
|
11941
|
+
created.push({ id: fm.id, file: path43.relative(root, file), query: s.query });
|
|
11677
11942
|
}
|
|
11678
11943
|
if (opts.json) {
|
|
11679
11944
|
console.log(JSON.stringify({ created, skipped }, null, 2));
|
|
@@ -11771,16 +12036,16 @@ function truncate2(text, max) {
|
|
|
11771
12036
|
}
|
|
11772
12037
|
|
|
11773
12038
|
// src/commands/memory-archive.ts
|
|
11774
|
-
import { existsSync as
|
|
12039
|
+
import { existsSync as existsSync60 } from "fs";
|
|
11775
12040
|
import { writeFile as writeFile30 } from "fs/promises";
|
|
11776
|
-
import
|
|
12041
|
+
import path44 from "path";
|
|
11777
12042
|
import "commander";
|
|
11778
12043
|
import {
|
|
11779
|
-
findProjectRoot as
|
|
12044
|
+
findProjectRoot as findProjectRoot41,
|
|
11780
12045
|
getUsage as getUsage18,
|
|
11781
12046
|
loadMemoriesFromDir as loadMemoriesFromDir31,
|
|
11782
12047
|
loadUsageIndex as loadUsageIndex24,
|
|
11783
|
-
resolveHaivePaths as
|
|
12048
|
+
resolveHaivePaths as resolveHaivePaths37,
|
|
11784
12049
|
serializeMemory as serializeMemory25
|
|
11785
12050
|
} from "@hiveai/core";
|
|
11786
12051
|
var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
|
|
@@ -11788,9 +12053,9 @@ function registerMemoryArchive(memory2) {
|
|
|
11788
12053
|
memory2.command("archive").description(
|
|
11789
12054
|
"Archive obsolete memories: marks status='deprecated' for memories not read in N days\n whose anchored paths have all disappeared (or have no anchor at all).\n\n Defaults to a DRY RUN \u2014 pass --apply to actually rewrite files.\n Targets `attempt` memories by default since they age the fastest.\n\n Recover later with `haive memory edit <id>` to set status back to validated."
|
|
11790
12055
|
).option("--since <window>", "minimum age since last read (e.g. '180d', '6m')", "180d").option("--type <type>", "limit to a memory type (default 'attempt'). Pass 'all' to scan all types.", "attempt").option("--apply", "actually rewrite files (default: dry run)", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11791
|
-
const root =
|
|
11792
|
-
const paths =
|
|
11793
|
-
if (!
|
|
12056
|
+
const root = findProjectRoot41(opts.dir);
|
|
12057
|
+
const paths = resolveHaivePaths37(root);
|
|
12058
|
+
if (!existsSync60(paths.memoriesDir)) {
|
|
11794
12059
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
11795
12060
|
process.exitCode = 1;
|
|
11796
12061
|
return;
|
|
@@ -11811,7 +12076,7 @@ function registerMemoryArchive(memory2) {
|
|
|
11811
12076
|
if (typeFilter && fm.type !== typeFilter) continue;
|
|
11812
12077
|
if (fm.status === "deprecated" || fm.status === "rejected") continue;
|
|
11813
12078
|
const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
|
|
11814
|
-
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !
|
|
12079
|
+
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync60(path44.join(paths.root, p)));
|
|
11815
12080
|
const isAnchorless = !hasAnyAnchor;
|
|
11816
12081
|
if (!isAnchorless && !allPathsGone) continue;
|
|
11817
12082
|
const u = getUsage18(usage, fm.id);
|
|
@@ -11885,33 +12150,33 @@ function parseDays(input) {
|
|
|
11885
12150
|
}
|
|
11886
12151
|
|
|
11887
12152
|
// src/commands/doctor.ts
|
|
11888
|
-
import { existsSync as
|
|
11889
|
-
import { readFile as
|
|
11890
|
-
import
|
|
12153
|
+
import { existsSync as existsSync61, statSync as statSync2 } from "fs";
|
|
12154
|
+
import { readFile as readFile20, stat, writeFile as writeFile31 } from "fs/promises";
|
|
12155
|
+
import path45 from "path";
|
|
11891
12156
|
import { execFileSync, execSync as execSync3 } from "child_process";
|
|
11892
12157
|
import "commander";
|
|
11893
12158
|
import {
|
|
11894
12159
|
codeMapPath as codeMapPath2,
|
|
11895
|
-
findProjectRoot as
|
|
12160
|
+
findProjectRoot as findProjectRoot42,
|
|
11896
12161
|
getUsage as getUsage19,
|
|
11897
|
-
loadCodeMap as
|
|
11898
|
-
loadConfig as
|
|
12162
|
+
loadCodeMap as loadCodeMap7,
|
|
12163
|
+
loadConfig as loadConfig10,
|
|
11899
12164
|
loadMemoriesFromDir as loadMemoriesFromDir33,
|
|
11900
12165
|
loadUsageIndex as loadUsageIndex25,
|
|
11901
12166
|
readUsageEvents as readUsageEvents4,
|
|
11902
|
-
resolveHaivePaths as
|
|
12167
|
+
resolveHaivePaths as resolveHaivePaths38
|
|
11903
12168
|
} from "@hiveai/core";
|
|
11904
12169
|
var MS_PER_DAY3 = 24 * 60 * 60 * 1e3;
|
|
11905
12170
|
function registerDoctor(program2) {
|
|
11906
12171
|
program2.command("doctor").description(
|
|
11907
12172
|
"Analyze the local hAIve setup and emit actionable recommendations.\n\n Inspects: project-context status, memory health (stale/anchorless/decay/pending),\n code-map freshness, usage log signals (low-hit briefings, repeated empty searches).\n\n Read-only by default. Pass --fix to apply safe autopilot repairs."
|
|
11908
12173
|
).option("--json", "emit JSON instead of human-readable output", false).option("--fix", "include suggested fix commands in human output", false).option("--dry-run", "with --fix, show delegated repairs without applying them", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11909
|
-
const root =
|
|
11910
|
-
const paths =
|
|
12174
|
+
const root = findProjectRoot42(opts.dir);
|
|
12175
|
+
const paths = resolveHaivePaths38(root);
|
|
11911
12176
|
const findings = [];
|
|
11912
12177
|
const repairs = [];
|
|
11913
|
-
const config = await
|
|
11914
|
-
if (!
|
|
12178
|
+
const config = await loadConfig10(paths);
|
|
12179
|
+
if (!existsSync61(paths.haiveDir)) {
|
|
11915
12180
|
findings.push({
|
|
11916
12181
|
severity: "error",
|
|
11917
12182
|
code: "not-initialized",
|
|
@@ -11932,7 +12197,7 @@ function registerDoctor(program2) {
|
|
|
11932
12197
|
})
|
|
11933
12198
|
);
|
|
11934
12199
|
}
|
|
11935
|
-
if (!
|
|
12200
|
+
if (!existsSync61(paths.projectContext)) {
|
|
11936
12201
|
findings.push({
|
|
11937
12202
|
severity: "warn",
|
|
11938
12203
|
code: "no-project-context",
|
|
@@ -11940,8 +12205,8 @@ function registerDoctor(program2) {
|
|
|
11940
12205
|
fix: "haive init"
|
|
11941
12206
|
});
|
|
11942
12207
|
} else {
|
|
11943
|
-
const { readFile:
|
|
11944
|
-
const content = await
|
|
12208
|
+
const { readFile: readFile23 } = await import("fs/promises");
|
|
12209
|
+
const content = await readFile23(paths.projectContext, "utf8");
|
|
11945
12210
|
const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
|
|
11946
12211
|
if (isTemplate) {
|
|
11947
12212
|
findings.push({
|
|
@@ -11961,7 +12226,7 @@ function registerDoctor(program2) {
|
|
|
11961
12226
|
});
|
|
11962
12227
|
}
|
|
11963
12228
|
}
|
|
11964
|
-
const memories =
|
|
12229
|
+
const memories = existsSync61(paths.memoriesDir) ? await loadMemoriesFromDir33(paths.memoriesDir) : [];
|
|
11965
12230
|
const now = Date.now();
|
|
11966
12231
|
if (memories.length === 0) {
|
|
11967
12232
|
findings.push({
|
|
@@ -12043,7 +12308,7 @@ function registerDoctor(program2) {
|
|
|
12043
12308
|
fix: "haive memory lint --fix --apply"
|
|
12044
12309
|
});
|
|
12045
12310
|
}
|
|
12046
|
-
const codeMap = await
|
|
12311
|
+
const codeMap = await loadCodeMap7(paths);
|
|
12047
12312
|
if (!codeMap) {
|
|
12048
12313
|
findings.push({
|
|
12049
12314
|
severity: "warn",
|
|
@@ -12102,12 +12367,12 @@ function registerDoctor(program2) {
|
|
|
12102
12367
|
}
|
|
12103
12368
|
}
|
|
12104
12369
|
if (config.enforcement?.requireBriefingFirst) {
|
|
12105
|
-
const claudeSettings =
|
|
12370
|
+
const claudeSettings = path45.join(root, ".claude", "settings.local.json");
|
|
12106
12371
|
let hasClaudeEnforcement = false;
|
|
12107
|
-
if (
|
|
12372
|
+
if (existsSync61(claudeSettings)) {
|
|
12108
12373
|
try {
|
|
12109
|
-
const { readFile:
|
|
12110
|
-
const raw = await
|
|
12374
|
+
const { readFile: readFile23 } = await import("fs/promises");
|
|
12375
|
+
const raw = await readFile23(claudeSettings, "utf8");
|
|
12111
12376
|
hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
|
|
12112
12377
|
} catch {
|
|
12113
12378
|
hasClaudeEnforcement = false;
|
|
@@ -12130,14 +12395,14 @@ function registerDoctor(program2) {
|
|
|
12130
12395
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
|
|
12131
12396
|
});
|
|
12132
12397
|
}
|
|
12133
|
-
findings.push(...await collectInstallFindings(root, "0.9.
|
|
12398
|
+
findings.push(...await collectInstallFindings(root, "0.9.30"));
|
|
12134
12399
|
try {
|
|
12135
12400
|
const legacyRaw = execSync3("haive-mcp --version", {
|
|
12136
12401
|
encoding: "utf8",
|
|
12137
12402
|
timeout: 3e3,
|
|
12138
12403
|
stdio: ["ignore", "pipe", "ignore"]
|
|
12139
12404
|
}).trim();
|
|
12140
|
-
const cliVersion = "0.9.
|
|
12405
|
+
const cliVersion = "0.9.30";
|
|
12141
12406
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
12142
12407
|
findings.push({
|
|
12143
12408
|
severity: "warn",
|
|
@@ -12153,17 +12418,17 @@ npm uninstall -g @hiveai/mcp`
|
|
|
12153
12418
|
}
|
|
12154
12419
|
{
|
|
12155
12420
|
const configPaths = [
|
|
12156
|
-
|
|
12157
|
-
|
|
12158
|
-
|
|
12421
|
+
path45.join(root, ".mcp.json"),
|
|
12422
|
+
path45.join(root, ".cursor", "mcp.json"),
|
|
12423
|
+
path45.join(root, ".vscode", "mcp.json")
|
|
12159
12424
|
];
|
|
12160
12425
|
const staleConfigs = [];
|
|
12161
12426
|
for (const cfgPath of configPaths) {
|
|
12162
|
-
if (!
|
|
12427
|
+
if (!existsSync61(cfgPath)) continue;
|
|
12163
12428
|
try {
|
|
12164
|
-
const raw = await
|
|
12429
|
+
const raw = await readFile20(cfgPath, "utf8");
|
|
12165
12430
|
if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
|
|
12166
|
-
staleConfigs.push(
|
|
12431
|
+
staleConfigs.push(path45.relative(root, cfgPath));
|
|
12167
12432
|
if (opts.fix && !opts.dryRun) {
|
|
12168
12433
|
const updated = raw.replace(/"command"\s*:\s*"haive-mcp"/g, '"command": "haive"').replace(/"args"\s*:\s*\[\]/g, '"args": ["mcp", "--stdio"]');
|
|
12169
12434
|
await writeFile31(cfgPath, updated, "utf8");
|
|
@@ -12453,9 +12718,9 @@ which -a haive`
|
|
|
12453
12718
|
".vscode/mcp.json"
|
|
12454
12719
|
];
|
|
12455
12720
|
for (const rel of integrationFiles) {
|
|
12456
|
-
const file =
|
|
12457
|
-
if (!
|
|
12458
|
-
const text = await
|
|
12721
|
+
const file = path45.join(root, rel);
|
|
12722
|
+
if (!existsSync61(file)) continue;
|
|
12723
|
+
const text = await readFile20(file, "utf8").catch(() => "");
|
|
12459
12724
|
for (const bin of extractAbsoluteHaiveBins(text)) {
|
|
12460
12725
|
const version = versionForBinary(bin);
|
|
12461
12726
|
if (!version) {
|
|
@@ -12479,7 +12744,7 @@ which -a haive`
|
|
|
12479
12744
|
}
|
|
12480
12745
|
async function collectWorkspaceVersionFindings(root, expectedVersion) {
|
|
12481
12746
|
const findings = [];
|
|
12482
|
-
const rootPkg = await readJson(
|
|
12747
|
+
const rootPkg = await readJson(path45.join(root, "package.json"));
|
|
12483
12748
|
const workspacePackages = [
|
|
12484
12749
|
"packages/core/package.json",
|
|
12485
12750
|
"packages/embeddings/package.json",
|
|
@@ -12488,7 +12753,7 @@ async function collectWorkspaceVersionFindings(root, expectedVersion) {
|
|
|
12488
12753
|
];
|
|
12489
12754
|
const existing = (await Promise.all(workspacePackages.map(async (rel) => ({
|
|
12490
12755
|
rel,
|
|
12491
|
-
pkg: await readJson(
|
|
12756
|
+
pkg: await readJson(path45.join(root, rel))
|
|
12492
12757
|
})))).filter((item) => item.pkg);
|
|
12493
12758
|
const isHaiveWorkspace = rootPkg?.name === "haive-monorepo" || existing.some((item) => item.pkg?.name?.startsWith("@hiveai/"));
|
|
12494
12759
|
if (!isHaiveWorkspace) return findings;
|
|
@@ -12550,9 +12815,9 @@ function collectGlobalHivemoduleFindings(expectedVersion) {
|
|
|
12550
12815
|
}
|
|
12551
12816
|
}
|
|
12552
12817
|
async function readJson(file) {
|
|
12553
|
-
if (!
|
|
12818
|
+
if (!existsSync61(file)) return null;
|
|
12554
12819
|
try {
|
|
12555
|
-
return JSON.parse(await
|
|
12820
|
+
return JSON.parse(await readFile20(file, "utf8"));
|
|
12556
12821
|
} catch {
|
|
12557
12822
|
return null;
|
|
12558
12823
|
}
|
|
@@ -12588,7 +12853,7 @@ function extractAbsoluteHaiveBins(text) {
|
|
|
12588
12853
|
const p = match[2];
|
|
12589
12854
|
if (!p) continue;
|
|
12590
12855
|
try {
|
|
12591
|
-
if (
|
|
12856
|
+
if (statSync2(p).isDirectory()) continue;
|
|
12592
12857
|
} catch {
|
|
12593
12858
|
}
|
|
12594
12859
|
out.add(p);
|
|
@@ -12597,22 +12862,22 @@ function extractAbsoluteHaiveBins(text) {
|
|
|
12597
12862
|
}
|
|
12598
12863
|
|
|
12599
12864
|
// src/commands/playback.ts
|
|
12600
|
-
import { existsSync as
|
|
12865
|
+
import { existsSync as existsSync63 } from "fs";
|
|
12601
12866
|
import "commander";
|
|
12602
12867
|
import {
|
|
12603
|
-
findProjectRoot as
|
|
12868
|
+
findProjectRoot as findProjectRoot43,
|
|
12604
12869
|
loadMemoriesFromDir as loadMemoriesFromDir34,
|
|
12605
12870
|
parseSince as parseSince3,
|
|
12606
12871
|
readUsageEvents as readUsageEvents5,
|
|
12607
|
-
resolveHaivePaths as
|
|
12872
|
+
resolveHaivePaths as resolveHaivePaths39
|
|
12608
12873
|
} from "@hiveai/core";
|
|
12609
12874
|
var MS_PER_MINUTE = 6e4;
|
|
12610
12875
|
function registerPlayback(program2) {
|
|
12611
12876
|
program2.command("playback").description(
|
|
12612
12877
|
"Replay past sessions from the usage log. For each session, show:\n - tool calls (what kind, how many)\n - briefing tasks asked\n - memories that have been created since then (that the session didn't have)\n\n Useful to ask 'would today's haive have helped past me on this task?'"
|
|
12613
12878
|
).option("--since <window>", "limit to events in this window (e.g. '7d')", "30d").option("--session-gap <minutes>", "minutes of inactivity that splits a session", "30").option("--limit <n>", "show at most this many sessions (newest first)", "10").option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
12614
|
-
const root =
|
|
12615
|
-
const paths =
|
|
12879
|
+
const root = findProjectRoot43(opts.dir);
|
|
12880
|
+
const paths = resolveHaivePaths39(root);
|
|
12616
12881
|
const events = await readUsageEvents5(paths);
|
|
12617
12882
|
if (events.length === 0) {
|
|
12618
12883
|
if (opts.json) {
|
|
@@ -12627,7 +12892,7 @@ function registerPlayback(program2) {
|
|
|
12627
12892
|
const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
|
|
12628
12893
|
const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
|
|
12629
12894
|
const sessions = bucketSessions(filtered, gapMs);
|
|
12630
|
-
const all =
|
|
12895
|
+
const all = existsSync63(paths.memoriesDir) ? await loadMemoriesFromDir34(paths.memoriesDir) : [];
|
|
12631
12896
|
const memByCreatedAt = all.filter(({ memory: memory2 }) => memory2.frontmatter.type !== "session_recap").map(({ memory: memory2 }) => ({ id: memory2.frontmatter.id, at: Date.parse(memory2.frontmatter.created_at) })).sort((a, b) => a.at - b.at);
|
|
12632
12897
|
const enriched = sessions.map((s, i) => {
|
|
12633
12898
|
const startMs = Date.parse(s.start);
|
|
@@ -12717,8 +12982,8 @@ function truncate3(text, max) {
|
|
|
12717
12982
|
import { spawn as spawn5 } from "child_process";
|
|
12718
12983
|
import "commander";
|
|
12719
12984
|
import {
|
|
12720
|
-
findProjectRoot as
|
|
12721
|
-
resolveHaivePaths as
|
|
12985
|
+
findProjectRoot as findProjectRoot44,
|
|
12986
|
+
resolveHaivePaths as resolveHaivePaths40
|
|
12722
12987
|
} from "@hiveai/core";
|
|
12723
12988
|
function registerPrecommit(program2) {
|
|
12724
12989
|
program2.command("precommit").description(
|
|
@@ -12727,9 +12992,12 @@ function registerPrecommit(program2) {
|
|
|
12727
12992
|
"--block-on <mode>",
|
|
12728
12993
|
"'any' | 'high-confidence' (default) | 'never' (report only)",
|
|
12729
12994
|
"high-confidence"
|
|
12730
|
-
).option("--no-semantic", "disable semantic search in anti-patterns matching").option(
|
|
12731
|
-
|
|
12732
|
-
|
|
12995
|
+
).option("--no-semantic", "disable semantic search in anti-patterns matching").option(
|
|
12996
|
+
"--no-anchored-blocks",
|
|
12997
|
+
"do not block on anchored, diff-corroborated anti-patterns (only block on very strong semantic matches)"
|
|
12998
|
+
).option("--json", "emit JSON instead of human-readable output", false).option("--paths <paths...>", "explicit paths to check (skips git diff)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
12999
|
+
const root = findProjectRoot44(opts.dir);
|
|
13000
|
+
const paths = resolveHaivePaths40(root);
|
|
12733
13001
|
const ctx = { paths };
|
|
12734
13002
|
let diff = "";
|
|
12735
13003
|
let touchedPaths = opts.paths ?? [];
|
|
@@ -12769,6 +13037,7 @@ function registerPrecommit(program2) {
|
|
|
12769
13037
|
diff: diff || void 0,
|
|
12770
13038
|
paths: touchedPaths,
|
|
12771
13039
|
block_on: opts.blockOn ?? "high-confidence",
|
|
13040
|
+
anchored_blocks: opts.anchoredBlocks !== false,
|
|
12772
13041
|
semantic: opts.noSemantic ? false : true
|
|
12773
13042
|
}, ctx);
|
|
12774
13043
|
if (opts.json) {
|
|
@@ -12859,12 +13128,12 @@ function runCommand3(cmd, args, cwd) {
|
|
|
12859
13128
|
}
|
|
12860
13129
|
|
|
12861
13130
|
// src/commands/welcome.ts
|
|
12862
|
-
import { existsSync as
|
|
13131
|
+
import { existsSync as existsSync64 } from "fs";
|
|
12863
13132
|
import "commander";
|
|
12864
13133
|
import {
|
|
12865
|
-
findProjectRoot as
|
|
13134
|
+
findProjectRoot as findProjectRoot45,
|
|
12866
13135
|
loadMemoriesFromDir as loadMemoriesFromDir35,
|
|
12867
|
-
resolveHaivePaths as
|
|
13136
|
+
resolveHaivePaths as resolveHaivePaths41
|
|
12868
13137
|
} from "@hiveai/core";
|
|
12869
13138
|
var TYPE_RANK = {
|
|
12870
13139
|
skill: 0,
|
|
@@ -12879,9 +13148,9 @@ function registerWelcome(program2) {
|
|
|
12879
13148
|
program2.command("welcome").description(
|
|
12880
13149
|
"Onboarding checklist: ranks validated/proposed **team** memories by type.\nUse after `haive init` so new devs skim institutional knowledge quickly.\n\n haive welcome\n haive welcome --limit 15\n"
|
|
12881
13150
|
).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
12882
|
-
const root =
|
|
12883
|
-
const paths =
|
|
12884
|
-
if (!
|
|
13151
|
+
const root = findProjectRoot45(opts.dir);
|
|
13152
|
+
const paths = resolveHaivePaths41(root);
|
|
13153
|
+
if (!existsSync64(paths.memoriesDir)) {
|
|
12885
13154
|
ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
|
|
12886
13155
|
process.exitCode = 1;
|
|
12887
13156
|
return;
|
|
@@ -12940,27 +13209,27 @@ function registerMemorySuggestTopic(memory2) {
|
|
|
12940
13209
|
}
|
|
12941
13210
|
|
|
12942
13211
|
// src/commands/resolve-project.ts
|
|
12943
|
-
import
|
|
13212
|
+
import path46 from "path";
|
|
12944
13213
|
import "commander";
|
|
12945
13214
|
import { resolveProjectInfo as resolveProjectInfo2 } from "@hiveai/core";
|
|
12946
13215
|
function registerResolveProject(program2) {
|
|
12947
13216
|
program2.command("resolve-project").description(
|
|
12948
13217
|
"Print JSON for hAIve project root resolution (HAIVE_PROJECT_ROOT, markers, .ai layout)."
|
|
12949
13218
|
).option("-d, --dir <dir>", "working directory", process.cwd()).action((opts) => {
|
|
12950
|
-
const info = resolveProjectInfo2({ cwd:
|
|
13219
|
+
const info = resolveProjectInfo2({ cwd: path46.resolve(opts.dir) });
|
|
12951
13220
|
console.log(JSON.stringify({ ok: true, info }, null, 2));
|
|
12952
13221
|
});
|
|
12953
13222
|
}
|
|
12954
13223
|
|
|
12955
13224
|
// src/commands/runtime-journal.ts
|
|
12956
|
-
import { existsSync as
|
|
12957
|
-
import
|
|
13225
|
+
import { existsSync as existsSync65 } from "fs";
|
|
13226
|
+
import path47 from "path";
|
|
12958
13227
|
import "commander";
|
|
12959
13228
|
import {
|
|
12960
13229
|
appendRuntimeJournalEntry as appendRuntimeJournalEntry3,
|
|
12961
|
-
findProjectRoot as
|
|
13230
|
+
findProjectRoot as findProjectRoot46,
|
|
12962
13231
|
readRuntimeJournalTail as readRuntimeJournalTail2,
|
|
12963
|
-
resolveHaivePaths as
|
|
13232
|
+
resolveHaivePaths as resolveHaivePaths42
|
|
12964
13233
|
} from "@hiveai/core";
|
|
12965
13234
|
function registerRuntime(program2) {
|
|
12966
13235
|
const runtime = program2.command("runtime").description(
|
|
@@ -12968,18 +13237,18 @@ function registerRuntime(program2) {
|
|
|
12968
13237
|
);
|
|
12969
13238
|
const journal = runtime.command("journal").description("Append or read the machine-local session journal (NDJSON)");
|
|
12970
13239
|
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) => {
|
|
12971
|
-
const root =
|
|
12972
|
-
const paths =
|
|
13240
|
+
const root = path47.resolve(opts.dir ?? process.cwd());
|
|
13241
|
+
const paths = resolveHaivePaths42(findProjectRoot46(root));
|
|
12973
13242
|
const raw = opts.kind ?? "note";
|
|
12974
13243
|
const kind = ["note", "session_end", "mcp"].includes(raw) ? raw : "note";
|
|
12975
13244
|
await appendRuntimeJournalEntry3(paths, { kind, message });
|
|
12976
|
-
ui.success(`Appended to ${
|
|
13245
|
+
ui.success(`Appended to ${path47.relative(root, paths.runtimeDir)}/session-journal.ndjson`);
|
|
12977
13246
|
});
|
|
12978
13247
|
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) => {
|
|
12979
|
-
const root =
|
|
12980
|
-
const paths =
|
|
13248
|
+
const root = path47.resolve(opts.dir ?? process.cwd());
|
|
13249
|
+
const paths = resolveHaivePaths42(findProjectRoot46(root));
|
|
12981
13250
|
const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
|
|
12982
|
-
if (!
|
|
13251
|
+
if (!existsSync65(paths.haiveDir)) {
|
|
12983
13252
|
ui.error("No .ai/ \u2014 run `haive init` first.");
|
|
12984
13253
|
process.exitCode = 1;
|
|
12985
13254
|
return;
|
|
@@ -12994,13 +13263,13 @@ function registerRuntime(program2) {
|
|
|
12994
13263
|
}
|
|
12995
13264
|
|
|
12996
13265
|
// src/commands/memory-timeline.ts
|
|
12997
|
-
import { existsSync as
|
|
12998
|
-
import
|
|
13266
|
+
import { existsSync as existsSync66 } from "fs";
|
|
13267
|
+
import path48 from "path";
|
|
12999
13268
|
import "commander";
|
|
13000
13269
|
import {
|
|
13001
13270
|
collectTimelineEntries as collectTimelineEntries2,
|
|
13002
|
-
findProjectRoot as
|
|
13003
|
-
resolveHaivePaths as
|
|
13271
|
+
findProjectRoot as findProjectRoot47,
|
|
13272
|
+
resolveHaivePaths as resolveHaivePaths43
|
|
13004
13273
|
} from "@hiveai/core";
|
|
13005
13274
|
function registerMemoryTimeline(memory2) {
|
|
13006
13275
|
memory2.command("timeline").description(
|
|
@@ -13011,9 +13280,9 @@ function registerMemoryTimeline(memory2) {
|
|
|
13011
13280
|
process.exitCode = 1;
|
|
13012
13281
|
return;
|
|
13013
13282
|
}
|
|
13014
|
-
const root =
|
|
13015
|
-
const paths =
|
|
13016
|
-
if (!
|
|
13283
|
+
const root = path48.resolve(opts.dir ?? process.cwd());
|
|
13284
|
+
const paths = resolveHaivePaths43(findProjectRoot47(root));
|
|
13285
|
+
if (!existsSync66(paths.memoriesDir)) {
|
|
13017
13286
|
ui.error("No memories \u2014 run `haive init`.");
|
|
13018
13287
|
process.exitCode = 1;
|
|
13019
13288
|
return;
|
|
@@ -13031,14 +13300,14 @@ function registerMemoryTimeline(memory2) {
|
|
|
13031
13300
|
}
|
|
13032
13301
|
|
|
13033
13302
|
// src/commands/memory-conflict-candidates.ts
|
|
13034
|
-
import { existsSync as
|
|
13035
|
-
import
|
|
13303
|
+
import { existsSync as existsSync67 } from "fs";
|
|
13304
|
+
import path49 from "path";
|
|
13036
13305
|
import "commander";
|
|
13037
13306
|
import {
|
|
13038
13307
|
findLexicalConflictPairs as findLexicalConflictPairs2,
|
|
13039
13308
|
findTopicStatusConflictPairs as findTopicStatusConflictPairs2,
|
|
13040
|
-
findProjectRoot as
|
|
13041
|
-
resolveHaivePaths as
|
|
13309
|
+
findProjectRoot as findProjectRoot48,
|
|
13310
|
+
resolveHaivePaths as resolveHaivePaths44
|
|
13042
13311
|
} from "@hiveai/core";
|
|
13043
13312
|
function parseTypes(csv) {
|
|
13044
13313
|
const allowed = ["decision", "architecture", "convention", "gotcha"];
|
|
@@ -13054,9 +13323,9 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
13054
13323
|
"decision,architecture,convention,gotcha (lexical scan)",
|
|
13055
13324
|
"decision,architecture"
|
|
13056
13325
|
).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) => {
|
|
13057
|
-
const root =
|
|
13058
|
-
const paths =
|
|
13059
|
-
if (!
|
|
13326
|
+
const root = path49.resolve(opts.dir ?? process.cwd());
|
|
13327
|
+
const paths = resolveHaivePaths44(findProjectRoot48(root));
|
|
13328
|
+
if (!existsSync67(paths.memoriesDir)) {
|
|
13060
13329
|
ui.error("No memories \u2014 run `haive init`.");
|
|
13061
13330
|
process.exitCode = 1;
|
|
13062
13331
|
return;
|
|
@@ -13092,20 +13361,20 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
13092
13361
|
|
|
13093
13362
|
// src/commands/enforce.ts
|
|
13094
13363
|
import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
|
|
13095
|
-
import { existsSync as
|
|
13096
|
-
import { chmod as chmod2, mkdir as mkdir19, readFile as
|
|
13097
|
-
import
|
|
13364
|
+
import { existsSync as existsSync68, statSync as statSync3 } from "fs";
|
|
13365
|
+
import { chmod as chmod2, mkdir as mkdir19, readFile as readFile21, readdir as readdir6, rm as rm3, writeFile as writeFile33 } from "fs/promises";
|
|
13366
|
+
import path50 from "path";
|
|
13098
13367
|
import "commander";
|
|
13099
13368
|
import {
|
|
13100
|
-
findProjectRoot as
|
|
13369
|
+
findProjectRoot as findProjectRoot49,
|
|
13101
13370
|
hasRecentBriefingMarker,
|
|
13102
13371
|
isFreshIsoDate,
|
|
13103
|
-
loadConfig as
|
|
13372
|
+
loadConfig as loadConfig11,
|
|
13104
13373
|
loadMemoriesFromDir as loadMemoriesFromDir36,
|
|
13105
13374
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
|
|
13106
13375
|
readRecentBriefingMarker,
|
|
13107
13376
|
resolveBriefingBudget as resolveBriefingBudget3,
|
|
13108
|
-
resolveHaivePaths as
|
|
13377
|
+
resolveHaivePaths as resolveHaivePaths45,
|
|
13109
13378
|
saveConfig as saveConfig4,
|
|
13110
13379
|
SESSION_RECAP_TTL_MS,
|
|
13111
13380
|
verifyAnchor as verifyAnchor4,
|
|
@@ -13118,10 +13387,10 @@ function registerEnforce(program2) {
|
|
|
13118
13387
|
"Agent-agnostic enforcement helpers: install policy gates, report status, and block unsafe workflows."
|
|
13119
13388
|
);
|
|
13120
13389
|
enforce.command("install").description("Install hAIve enforcement across MCP config, git hooks, CI template, and supported client hooks.").option("-d, --dir <dir>", "project root").option("--no-git", "skip git pre-commit/pre-push enforcement hooks").option("--no-claude", "skip Claude Code hooks").option("--no-ci", "skip GitHub Actions enforcement workflow").action(async (opts) => {
|
|
13121
|
-
const root =
|
|
13122
|
-
const paths =
|
|
13390
|
+
const root = findProjectRoot49(opts.dir);
|
|
13391
|
+
const paths = resolveHaivePaths45(root);
|
|
13123
13392
|
await mkdir19(paths.haiveDir, { recursive: true });
|
|
13124
|
-
const current = await
|
|
13393
|
+
const current = await loadConfig11(paths);
|
|
13125
13394
|
await saveConfig4(paths, {
|
|
13126
13395
|
...current,
|
|
13127
13396
|
enforcement: {
|
|
@@ -13144,7 +13413,7 @@ function registerEnforce(program2) {
|
|
|
13144
13413
|
if (opts.claude !== false) {
|
|
13145
13414
|
try {
|
|
13146
13415
|
const result = await installClaudeHooksAtPath(defaultClaudeSettingsPath("project", root));
|
|
13147
|
-
ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${
|
|
13416
|
+
ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path50.relative(root, result.settingsPath)})`);
|
|
13148
13417
|
} catch (err) {
|
|
13149
13418
|
ui.warn(`Claude Code hooks not installed: ${err instanceof Error ? err.message : String(err)}`);
|
|
13150
13419
|
}
|
|
@@ -13163,21 +13432,21 @@ function registerEnforce(program2) {
|
|
|
13163
13432
|
if (report.should_block) process.exit(2);
|
|
13164
13433
|
});
|
|
13165
13434
|
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) => {
|
|
13166
|
-
const root =
|
|
13167
|
-
const paths =
|
|
13168
|
-
const cacheDir =
|
|
13169
|
-
if (
|
|
13170
|
-
if (opts.dryRun) ui.info(`would clean ${
|
|
13435
|
+
const root = findProjectRoot49(opts.dir);
|
|
13436
|
+
const paths = resolveHaivePaths45(root);
|
|
13437
|
+
const cacheDir = path50.join(paths.haiveDir, ".cache");
|
|
13438
|
+
if (existsSync68(cacheDir)) {
|
|
13439
|
+
if (opts.dryRun) ui.info(`would clean ${path50.relative(root, cacheDir)} (preserving .gitignore)`);
|
|
13171
13440
|
else {
|
|
13172
13441
|
const removed = await cleanupCacheDir(cacheDir);
|
|
13173
|
-
ui.success(`cleaned ${
|
|
13442
|
+
ui.success(`cleaned ${path50.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
|
|
13174
13443
|
}
|
|
13175
13444
|
}
|
|
13176
|
-
if (
|
|
13177
|
-
if (opts.dryRun) ui.info(`would clean ${
|
|
13445
|
+
if (existsSync68(paths.runtimeDir)) {
|
|
13446
|
+
if (opts.dryRun) ui.info(`would clean ${path50.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
|
|
13178
13447
|
else {
|
|
13179
13448
|
const removed = await cleanupRuntimeDir(paths.runtimeDir);
|
|
13180
|
-
ui.success(`cleaned ${
|
|
13449
|
+
ui.success(`cleaned ${path50.relative(root, paths.runtimeDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
|
|
13181
13450
|
}
|
|
13182
13451
|
}
|
|
13183
13452
|
});
|
|
@@ -13190,8 +13459,8 @@ function registerEnforce(program2) {
|
|
|
13190
13459
|
const payload = await readHookPayload();
|
|
13191
13460
|
const root = resolveRoot(opts.dir, payload);
|
|
13192
13461
|
if (!root) return;
|
|
13193
|
-
const paths =
|
|
13194
|
-
if (!
|
|
13462
|
+
const paths = resolveHaivePaths45(root);
|
|
13463
|
+
if (!existsSync68(paths.haiveDir)) return;
|
|
13195
13464
|
await mkdir19(paths.runtimeDir, { recursive: true });
|
|
13196
13465
|
const sessionId = opts.sessionId ?? payload.session_id;
|
|
13197
13466
|
const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this hAIve-initialized project.";
|
|
@@ -13253,8 +13522,8 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
13253
13522
|
const payload = await readHookPayload();
|
|
13254
13523
|
const root = resolveRoot(opts.dir, payload);
|
|
13255
13524
|
if (!root) return;
|
|
13256
|
-
const paths =
|
|
13257
|
-
if (!
|
|
13525
|
+
const paths = resolveHaivePaths45(root);
|
|
13526
|
+
if (!existsSync68(paths.haiveDir)) return;
|
|
13258
13527
|
if (!isWriteLikeTool(payload)) return;
|
|
13259
13528
|
const ok = await hasRecentBriefingMarker(paths, payload.session_id);
|
|
13260
13529
|
if (ok) {
|
|
@@ -13296,9 +13565,9 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
13296
13565
|
});
|
|
13297
13566
|
}
|
|
13298
13567
|
async function runWithEnforcement(command, args, opts) {
|
|
13299
|
-
const root =
|
|
13300
|
-
const paths =
|
|
13301
|
-
if (!
|
|
13568
|
+
const root = findProjectRoot49(opts.dir);
|
|
13569
|
+
const paths = resolveHaivePaths45(root);
|
|
13570
|
+
if (!existsSync68(paths.haiveDir)) {
|
|
13302
13571
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
13303
13572
|
process.exit(1);
|
|
13304
13573
|
}
|
|
@@ -13317,7 +13586,7 @@ async function runWithEnforcement(command, args, opts) {
|
|
|
13317
13586
|
process.exit(2);
|
|
13318
13587
|
}
|
|
13319
13588
|
ui.info(`hAIve briefing marker created for wrapped agent session: ${sessionId}`);
|
|
13320
|
-
ui.info(`Briefing written to ${
|
|
13589
|
+
ui.info(`Briefing written to ${path50.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
|
|
13321
13590
|
const child = spawn6(command, args, {
|
|
13322
13591
|
cwd: root,
|
|
13323
13592
|
stdio: "inherit",
|
|
@@ -13367,9 +13636,9 @@ async function writeWrapperBriefing(paths, sessionId, task) {
|
|
|
13367
13636
|
source: "haive-run",
|
|
13368
13637
|
memoryIds: briefing.memories.map((m) => m.id)
|
|
13369
13638
|
});
|
|
13370
|
-
const dir =
|
|
13639
|
+
const dir = path50.join(paths.runtimeDir, "enforcement", "briefings");
|
|
13371
13640
|
await mkdir19(dir, { recursive: true });
|
|
13372
|
-
const file =
|
|
13641
|
+
const file = path50.join(dir, `${sessionId}.md`);
|
|
13373
13642
|
const parts = [
|
|
13374
13643
|
"# hAIve Briefing",
|
|
13375
13644
|
"",
|
|
@@ -13391,10 +13660,10 @@ async function writeWrapperBriefing(paths, sessionId, task) {
|
|
|
13391
13660
|
return file;
|
|
13392
13661
|
}
|
|
13393
13662
|
async function buildEnforcementReport(dir, stage, sessionId) {
|
|
13394
|
-
const root =
|
|
13395
|
-
const paths =
|
|
13396
|
-
const initialized =
|
|
13397
|
-
const config = initialized ? await
|
|
13663
|
+
const root = findProjectRoot49(dir);
|
|
13664
|
+
const paths = resolveHaivePaths45(root);
|
|
13665
|
+
const initialized = existsSync68(paths.haiveDir);
|
|
13666
|
+
const config = initialized ? await loadConfig11(paths) : {};
|
|
13398
13667
|
if (initialized) await applyLightweightRepairs(root, paths);
|
|
13399
13668
|
const mode = config.enforcement?.mode ?? "strict";
|
|
13400
13669
|
const findings = [];
|
|
@@ -13424,7 +13693,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
13424
13693
|
findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
|
|
13425
13694
|
});
|
|
13426
13695
|
}
|
|
13427
|
-
findings.push(...await inspectIntegrationVersions(root, "0.9.
|
|
13696
|
+
findings.push(...await inspectIntegrationVersions(root, "0.9.30"));
|
|
13428
13697
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
13429
13698
|
const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
|
|
13430
13699
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
|
|
@@ -13458,7 +13727,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
13458
13727
|
findings.push(...await verifyDecisionCoverage(paths, stage, sessionId));
|
|
13459
13728
|
}
|
|
13460
13729
|
if (stage === "pre-commit" || stage === "ci") {
|
|
13461
|
-
findings.push(...await runPrecommitPolicy(paths));
|
|
13730
|
+
findings.push(...await runPrecommitPolicy(paths, config.enforcement?.antiPatternGate ?? "anchored"));
|
|
13462
13731
|
}
|
|
13463
13732
|
if (config.enforcement?.cleanupGeneratedArtifacts !== false) {
|
|
13464
13733
|
findings.push(...await findGeneratedArtifacts(paths));
|
|
@@ -13494,7 +13763,7 @@ function withCategories(report) {
|
|
|
13494
13763
|
};
|
|
13495
13764
|
}
|
|
13496
13765
|
async function hasRecentSessionRecap(paths) {
|
|
13497
|
-
if (!
|
|
13766
|
+
if (!existsSync68(paths.memoriesDir)) return false;
|
|
13498
13767
|
const all = await loadMemoriesFromDir36(paths.memoriesDir);
|
|
13499
13768
|
return all.some(({ memory: memory2 }) => {
|
|
13500
13769
|
const fm = memory2.frontmatter;
|
|
@@ -13503,7 +13772,7 @@ async function hasRecentSessionRecap(paths) {
|
|
|
13503
13772
|
});
|
|
13504
13773
|
}
|
|
13505
13774
|
async function verifyMemoryPolicy(paths, config) {
|
|
13506
|
-
if (!
|
|
13775
|
+
if (!existsSync68(paths.memoriesDir)) return [];
|
|
13507
13776
|
const all = await loadMemoriesFromDir36(paths.memoriesDir);
|
|
13508
13777
|
const findings = [];
|
|
13509
13778
|
const staleImportant = [];
|
|
@@ -13541,7 +13810,7 @@ async function verifyMemoryPolicy(paths, config) {
|
|
|
13541
13810
|
return findings;
|
|
13542
13811
|
}
|
|
13543
13812
|
async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
13544
|
-
if (!
|
|
13813
|
+
if (!existsSync68(paths.memoriesDir)) return [];
|
|
13545
13814
|
const changedFiles = await getChangedFiles(paths.root, stage);
|
|
13546
13815
|
if (changedFiles.length === 0) {
|
|
13547
13816
|
return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
|
|
@@ -13582,7 +13851,10 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
|
13582
13851
|
impact: Math.min(35, 10 + missing.length * 5)
|
|
13583
13852
|
}];
|
|
13584
13853
|
}
|
|
13585
|
-
async function runPrecommitPolicy(paths) {
|
|
13854
|
+
async function runPrecommitPolicy(paths, gate) {
|
|
13855
|
+
if (gate === "off") {
|
|
13856
|
+
return [{ severity: "info", code: "precommit-policy-off", message: "Anti-pattern gate is disabled (enforcement.antiPatternGate=off)." }];
|
|
13857
|
+
}
|
|
13586
13858
|
const staged = await runCommand4("git", ["diff", "--cached", "--name-only"], paths.root).catch(() => "");
|
|
13587
13859
|
const touchedPaths = staged.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
13588
13860
|
if (touchedPaths.length === 0) {
|
|
@@ -13592,7 +13864,8 @@ async function runPrecommitPolicy(paths) {
|
|
|
13592
13864
|
const result = await preCommitCheck({
|
|
13593
13865
|
diff,
|
|
13594
13866
|
paths: touchedPaths,
|
|
13595
|
-
block_on: "high-confidence",
|
|
13867
|
+
block_on: gate === "strict" ? "any" : "high-confidence",
|
|
13868
|
+
anchored_blocks: gate === "anchored",
|
|
13596
13869
|
semantic: true
|
|
13597
13870
|
}, { paths });
|
|
13598
13871
|
if (!result.should_block) {
|
|
@@ -13640,16 +13913,16 @@ async function cleanupRuntimeDir(runtimeDir) {
|
|
|
13640
13913
|
for (const entry of entries) {
|
|
13641
13914
|
if (entry.name === ".gitignore" || entry.name === "README.md") continue;
|
|
13642
13915
|
if (entry.name === "enforcement") {
|
|
13643
|
-
removed += await cleanupEnforcementDir(
|
|
13916
|
+
removed += await cleanupEnforcementDir(path50.join(runtimeDir, entry.name));
|
|
13644
13917
|
continue;
|
|
13645
13918
|
}
|
|
13646
|
-
await rm3(
|
|
13919
|
+
await rm3(path50.join(runtimeDir, entry.name), { recursive: true, force: true });
|
|
13647
13920
|
removed++;
|
|
13648
13921
|
}
|
|
13649
|
-
await writeFile33(
|
|
13650
|
-
if (!
|
|
13922
|
+
await writeFile33(path50.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
|
|
13923
|
+
if (!existsSync68(path50.join(runtimeDir, "README.md"))) {
|
|
13651
13924
|
await writeFile33(
|
|
13652
|
-
|
|
13925
|
+
path50.join(runtimeDir, "README.md"),
|
|
13653
13926
|
"# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. hAIve cleanup preserves briefing markers so enforcement state remains valid.\n",
|
|
13654
13927
|
"utf8"
|
|
13655
13928
|
);
|
|
@@ -13662,10 +13935,10 @@ async function cleanupCacheDir(cacheDir) {
|
|
|
13662
13935
|
const entries = await readdir6(cacheDir, { withFileTypes: true }).catch(() => []);
|
|
13663
13936
|
for (const entry of entries) {
|
|
13664
13937
|
if (entry.name === ".gitignore") continue;
|
|
13665
|
-
await rm3(
|
|
13938
|
+
await rm3(path50.join(cacheDir, entry.name), { recursive: true, force: true });
|
|
13666
13939
|
removed++;
|
|
13667
13940
|
}
|
|
13668
|
-
await writeFile33(
|
|
13941
|
+
await writeFile33(path50.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
|
|
13669
13942
|
return removed;
|
|
13670
13943
|
}
|
|
13671
13944
|
async function cleanupEnforcementDir(enforcementDir) {
|
|
@@ -13673,7 +13946,7 @@ async function cleanupEnforcementDir(enforcementDir) {
|
|
|
13673
13946
|
const entries = await readdir6(enforcementDir, { withFileTypes: true }).catch(() => []);
|
|
13674
13947
|
for (const entry of entries) {
|
|
13675
13948
|
if (entry.name === "briefings") continue;
|
|
13676
|
-
await rm3(
|
|
13949
|
+
await rm3(path50.join(enforcementDir, entry.name), { recursive: true, force: true });
|
|
13677
13950
|
removed++;
|
|
13678
13951
|
}
|
|
13679
13952
|
return removed;
|
|
@@ -13689,9 +13962,9 @@ async function inspectIntegrationVersions(root, expectedVersion) {
|
|
|
13689
13962
|
];
|
|
13690
13963
|
const findings = [];
|
|
13691
13964
|
for (const rel of files) {
|
|
13692
|
-
const file =
|
|
13693
|
-
if (!
|
|
13694
|
-
const text = await
|
|
13965
|
+
const file = path50.join(root, rel);
|
|
13966
|
+
if (!existsSync68(file)) continue;
|
|
13967
|
+
const text = await readFile21(file, "utf8").catch(() => "");
|
|
13695
13968
|
for (const bin of extractAbsoluteHaiveBins2(text)) {
|
|
13696
13969
|
const version = versionForBinary2(bin);
|
|
13697
13970
|
if (!version) {
|
|
@@ -13730,7 +14003,7 @@ function extractAbsoluteHaiveBins2(text) {
|
|
|
13730
14003
|
const p = match[2];
|
|
13731
14004
|
if (!p) continue;
|
|
13732
14005
|
try {
|
|
13733
|
-
if (
|
|
14006
|
+
if (statSync3(p).isDirectory()) continue;
|
|
13734
14007
|
} catch {
|
|
13735
14008
|
}
|
|
13736
14009
|
out.add(p);
|
|
@@ -13785,8 +14058,8 @@ function buildScore(findings, threshold = 80) {
|
|
|
13785
14058
|
};
|
|
13786
14059
|
}
|
|
13787
14060
|
async function installGitEnforcement(root) {
|
|
13788
|
-
const hooksDir =
|
|
13789
|
-
if (!
|
|
14061
|
+
const hooksDir = path50.join(root, ".git", "hooks");
|
|
14062
|
+
if (!existsSync68(path50.join(root, ".git"))) {
|
|
13790
14063
|
ui.warn("No .git directory found; git enforcement hooks skipped.");
|
|
13791
14064
|
return;
|
|
13792
14065
|
}
|
|
@@ -13808,9 +14081,9 @@ haive enforce check --stage pre-push --dir . || exit $?
|
|
|
13808
14081
|
}
|
|
13809
14082
|
];
|
|
13810
14083
|
for (const hook of hooks) {
|
|
13811
|
-
const file =
|
|
13812
|
-
if (
|
|
13813
|
-
const current = await
|
|
14084
|
+
const file = path50.join(hooksDir, hook.name);
|
|
14085
|
+
if (existsSync68(file)) {
|
|
14086
|
+
const current = await readFile21(file, "utf8").catch(() => "");
|
|
13814
14087
|
if (current.includes(ENFORCE_HOOK_MARKER)) {
|
|
13815
14088
|
await writeFile33(file, hook.body, "utf8");
|
|
13816
14089
|
} else {
|
|
@@ -13826,9 +14099,9 @@ ${hook.body}`, "utf8");
|
|
|
13826
14099
|
ui.success("Installed blocking git enforcement hooks: pre-commit, pre-push");
|
|
13827
14100
|
}
|
|
13828
14101
|
async function installCiEnforcement(root) {
|
|
13829
|
-
const workflowPath =
|
|
13830
|
-
await mkdir19(
|
|
13831
|
-
if (
|
|
14102
|
+
const workflowPath = path50.join(root, ".github", "workflows", "haive-enforcement.yml");
|
|
14103
|
+
await mkdir19(path50.dirname(workflowPath), { recursive: true });
|
|
14104
|
+
if (existsSync68(workflowPath)) {
|
|
13832
14105
|
ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
|
|
13833
14106
|
return;
|
|
13834
14107
|
}
|
|
@@ -13856,7 +14129,7 @@ jobs:
|
|
|
13856
14129
|
- name: Enforce hAIve policy
|
|
13857
14130
|
run: haive enforce ci
|
|
13858
14131
|
`, "utf8");
|
|
13859
|
-
ui.success(`Created ${
|
|
14132
|
+
ui.success(`Created ${path50.relative(root, workflowPath)}`);
|
|
13860
14133
|
}
|
|
13861
14134
|
function printReport(report, json, explain = false) {
|
|
13862
14135
|
if (json) {
|
|
@@ -13916,7 +14189,7 @@ async function readHookPayload() {
|
|
|
13916
14189
|
}
|
|
13917
14190
|
function resolveRoot(dir, payload) {
|
|
13918
14191
|
try {
|
|
13919
|
-
return
|
|
14192
|
+
return findProjectRoot49(dir ?? payload.cwd);
|
|
13920
14193
|
} catch {
|
|
13921
14194
|
return null;
|
|
13922
14195
|
}
|
|
@@ -13953,11 +14226,11 @@ function extractToolPaths(payload, root) {
|
|
|
13953
14226
|
}
|
|
13954
14227
|
function normalizeToolPath(file, root) {
|
|
13955
14228
|
const normalized = file.replace(/\\/g, "/");
|
|
13956
|
-
if (!
|
|
13957
|
-
return
|
|
14229
|
+
if (!path50.isAbsolute(normalized)) return normalized.replace(/^\.\//, "");
|
|
14230
|
+
return path50.relative(root, normalized).replace(/\\/g, "/");
|
|
13958
14231
|
}
|
|
13959
14232
|
async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
|
|
13960
|
-
if (!
|
|
14233
|
+
if (!existsSync68(paths.memoriesDir)) return [];
|
|
13961
14234
|
const marker = await readRecentBriefingMarker(paths, sessionId);
|
|
13962
14235
|
const consulted = new Set(marker?.memory_ids ?? []);
|
|
13963
14236
|
const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention", "attempt"]);
|
|
@@ -14030,8 +14303,8 @@ function registerRun(program2) {
|
|
|
14030
14303
|
}
|
|
14031
14304
|
|
|
14032
14305
|
// src/index.ts
|
|
14033
|
-
var program = new
|
|
14034
|
-
program.name("haive").description("hAIve \u2014 the memory and enforcement layer of your agent harness").version("0.9.
|
|
14306
|
+
var program = new Command52();
|
|
14307
|
+
program.name("haive").description("hAIve \u2014 the memory and enforcement layer of your agent harness").version("0.9.30").option("--advanced", "show maintenance and experimental commands in help");
|
|
14035
14308
|
registerInit(program);
|
|
14036
14309
|
registerWelcome(program);
|
|
14037
14310
|
registerResolveProject(program);
|
|
@@ -14065,6 +14338,7 @@ registerMemoryApprove(memory);
|
|
|
14065
14338
|
registerMemoryUpdate(memory);
|
|
14066
14339
|
registerMemoryHot(memory);
|
|
14067
14340
|
registerMemoryTried(memory);
|
|
14341
|
+
registerMemorySeed(memory);
|
|
14068
14342
|
registerMemoryImport(memory);
|
|
14069
14343
|
registerMemoryImportChangelog(memory);
|
|
14070
14344
|
registerMemoryDigest(memory);
|