@hiveai/cli 0.18.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +731 -654
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -262,6 +262,7 @@ import {
|
|
|
262
262
|
loadCodeMap,
|
|
263
263
|
loadMemoriesFromDir,
|
|
264
264
|
loadUsageIndex,
|
|
265
|
+
looksLikeGenericAdvice,
|
|
265
266
|
resolveHaivePaths,
|
|
266
267
|
serializeMemory,
|
|
267
268
|
specificityScore
|
|
@@ -310,7 +311,9 @@ async function lintMemoriesAsync(root, options = {}) {
|
|
|
310
311
|
message: "Record does not contain obvious action/rationale words. Add the concrete rule, why it exists, and what to do instead."
|
|
311
312
|
});
|
|
312
313
|
}
|
|
313
|
-
if (["decision", "gotcha", "convention", "architecture"].includes(fm.type) && fm.status !== "rejected" && naked.length >= 40 && specificityScore(naked) < 0.2
|
|
314
|
+
if (["decision", "gotcha", "convention", "architecture"].includes(fm.type) && fm.status !== "rejected" && naked.length >= 40 && specificityScore(naked) < 0.2 && // High-precision gate: only flag when there is positive evidence of generic advice.
|
|
315
|
+
// A low-density but arbitrary team policy (unguessable prose) must not be flagged.
|
|
316
|
+
looksLikeGenericAdvice(naked)) {
|
|
314
317
|
out.push({
|
|
315
318
|
file: filePath,
|
|
316
319
|
id: fm.id,
|
|
@@ -1378,14 +1381,15 @@ async function reportIndexStatus(root, paths, asJson) {
|
|
|
1378
1381
|
|
|
1379
1382
|
// src/commands/init.ts
|
|
1380
1383
|
import { execFile as execFile2 } from "child_process";
|
|
1381
|
-
import { mkdir as
|
|
1382
|
-
import { existsSync as
|
|
1383
|
-
import
|
|
1384
|
+
import { mkdir as mkdir6, readFile as readFile6, readdir as readdir2, writeFile as writeFile7 } from "fs/promises";
|
|
1385
|
+
import { existsSync as existsSync11 } from "fs";
|
|
1386
|
+
import path11 from "path";
|
|
1384
1387
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
1385
1388
|
import { promisify as promisify2 } from "util";
|
|
1386
1389
|
import "commander";
|
|
1387
1390
|
import {
|
|
1388
1391
|
AUTOPILOT_DEFAULTS as AUTOPILOT_DEFAULTS2,
|
|
1392
|
+
BRIDGE_TARGETS,
|
|
1389
1393
|
buildCodeMap as buildCodeMap3,
|
|
1390
1394
|
buildFrontmatter as buildFrontmatter2,
|
|
1391
1395
|
detectStacksFromManifests,
|
|
@@ -1397,20 +1401,101 @@ import {
|
|
|
1397
1401
|
serializeMemory as serializeMemory3
|
|
1398
1402
|
} from "@hiveai/core";
|
|
1399
1403
|
|
|
1404
|
+
// src/utils/bridge-files.ts
|
|
1405
|
+
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
1406
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1407
|
+
import path6 from "path";
|
|
1408
|
+
import {
|
|
1409
|
+
BRIDGE_MARKERS,
|
|
1410
|
+
generateBridges,
|
|
1411
|
+
isRetiredMemory,
|
|
1412
|
+
loadMemoriesFromDir as loadMemoriesFromDir4
|
|
1413
|
+
} from "@hiveai/core";
|
|
1414
|
+
async function writeBridgeFiles(root, paths, opts) {
|
|
1415
|
+
const result = { created: [], updated: [], unchanged: [] };
|
|
1416
|
+
if (!existsSync6(paths.memoriesDir)) return result;
|
|
1417
|
+
const allLoaded = await loadMemoriesFromDir4(paths.memoriesDir);
|
|
1418
|
+
const memories = allLoaded.map((l) => l.memory).filter((m) => !isRetiredMemory(m.frontmatter, m.body));
|
|
1419
|
+
const sensors = [];
|
|
1420
|
+
for (const m of memories) {
|
|
1421
|
+
const sensor = m.frontmatter.sensor;
|
|
1422
|
+
if (!sensor || sensor.severity !== "block") continue;
|
|
1423
|
+
sensors.push({
|
|
1424
|
+
id: m.frontmatter.id,
|
|
1425
|
+
severity: "block",
|
|
1426
|
+
message: sensor.message,
|
|
1427
|
+
...sensor.pattern ? { pattern: sensor.pattern } : {},
|
|
1428
|
+
paths: sensor.paths.length > 0 ? sensor.paths : m.frontmatter.anchor.paths
|
|
1429
|
+
});
|
|
1430
|
+
}
|
|
1431
|
+
const maxMemories = Math.max(1, opts.maxMemories ?? 8);
|
|
1432
|
+
const outputs = generateBridges(memories, sensors, { maxMemories, targets: opts.targets });
|
|
1433
|
+
for (const output of outputs) {
|
|
1434
|
+
const targetFile = path6.join(root, output.path);
|
|
1435
|
+
const fileExists = existsSync6(targetFile);
|
|
1436
|
+
if (opts.onlyExisting && !fileExists) continue;
|
|
1437
|
+
if (opts.dryRun) {
|
|
1438
|
+
(fileExists ? result.updated : result.created).push(output.path);
|
|
1439
|
+
continue;
|
|
1440
|
+
}
|
|
1441
|
+
await mkdir2(path6.dirname(targetFile), { recursive: true });
|
|
1442
|
+
if (!fileExists) {
|
|
1443
|
+
await writeFile3(targetFile, output.content, "utf8");
|
|
1444
|
+
result.created.push(output.path);
|
|
1445
|
+
continue;
|
|
1446
|
+
}
|
|
1447
|
+
let existing = (await readFile3(targetFile, "utf8")).replace(/\r\n/g, "\n");
|
|
1448
|
+
const withMemories = replaceMarkerBlock(
|
|
1449
|
+
existing,
|
|
1450
|
+
BRIDGE_MARKERS.memoriesStart,
|
|
1451
|
+
BRIDGE_MARKERS.memoriesEnd,
|
|
1452
|
+
extractMarkerBlock(output.content, BRIDGE_MARKERS.memoriesStart, BRIDGE_MARKERS.memoriesEnd)
|
|
1453
|
+
);
|
|
1454
|
+
const sensorsBlockContent = extractMarkerBlock(
|
|
1455
|
+
output.content,
|
|
1456
|
+
BRIDGE_MARKERS.sensorsStart,
|
|
1457
|
+
BRIDGE_MARKERS.sensorsEnd
|
|
1458
|
+
);
|
|
1459
|
+
const withSensors = sensorsBlockContent ? replaceMarkerBlock(withMemories, BRIDGE_MARKERS.sensorsStart, BRIDGE_MARKERS.sensorsEnd, sensorsBlockContent) : withMemories;
|
|
1460
|
+
if (withSensors === existing) {
|
|
1461
|
+
result.unchanged.push(output.path);
|
|
1462
|
+
continue;
|
|
1463
|
+
}
|
|
1464
|
+
await writeFile3(targetFile, withSensors, "utf8");
|
|
1465
|
+
result.updated.push(output.path);
|
|
1466
|
+
}
|
|
1467
|
+
return result;
|
|
1468
|
+
}
|
|
1469
|
+
function extractMarkerBlock(text, startMarker, endMarker) {
|
|
1470
|
+
const startIdx = text.indexOf(startMarker);
|
|
1471
|
+
const endIdx = text.indexOf(endMarker);
|
|
1472
|
+
if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) return null;
|
|
1473
|
+
return text.slice(startIdx, endIdx + endMarker.length);
|
|
1474
|
+
}
|
|
1475
|
+
function replaceMarkerBlock(existing, startMarker, endMarker, replacement) {
|
|
1476
|
+
if (!replacement) return existing;
|
|
1477
|
+
const startIdx = existing.indexOf(startMarker);
|
|
1478
|
+
const endIdx = existing.indexOf(endMarker);
|
|
1479
|
+
if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
|
|
1480
|
+
return existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + replacement + "\n";
|
|
1481
|
+
}
|
|
1482
|
+
return existing.slice(0, startIdx) + replacement + existing.slice(endIdx + endMarker.length);
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1400
1485
|
// src/commands/agent.ts
|
|
1401
1486
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1402
|
-
import { existsSync as
|
|
1403
|
-
import { mkdir as
|
|
1487
|
+
import { existsSync as existsSync8 } from "fs";
|
|
1488
|
+
import { mkdir as mkdir4, writeFile as writeFile5 } from "fs/promises";
|
|
1404
1489
|
import os2 from "os";
|
|
1405
|
-
import
|
|
1490
|
+
import path8 from "path";
|
|
1406
1491
|
import { createInterface } from "readline/promises";
|
|
1407
1492
|
import "commander";
|
|
1408
1493
|
import { findProjectRoot as findProjectRoot6, resolveHaivePaths as resolveHaivePaths5 } from "@hiveai/core";
|
|
1409
1494
|
|
|
1410
1495
|
// src/commands/init-mcp-setup.ts
|
|
1411
|
-
import { readFile as
|
|
1412
|
-
import { existsSync as
|
|
1413
|
-
import
|
|
1496
|
+
import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir3 } from "fs/promises";
|
|
1497
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1498
|
+
import path7 from "path";
|
|
1414
1499
|
import os from "os";
|
|
1415
1500
|
var HOME = os.homedir();
|
|
1416
1501
|
var HAIVE_MCP_ENTRY = {
|
|
@@ -1425,38 +1510,38 @@ function projectMcpEntry(root) {
|
|
|
1425
1510
|
};
|
|
1426
1511
|
}
|
|
1427
1512
|
function cursorMcpPath() {
|
|
1428
|
-
return
|
|
1513
|
+
return path7.join(HOME, ".cursor", "mcp.json");
|
|
1429
1514
|
}
|
|
1430
1515
|
async function configureCursor() {
|
|
1431
1516
|
const mcpPath = cursorMcpPath();
|
|
1432
|
-
const cursorDir =
|
|
1433
|
-
if (!
|
|
1517
|
+
const cursorDir = path7.join(HOME, ".cursor");
|
|
1518
|
+
if (!existsSync7(cursorDir)) return { client: "Cursor", status: "not_installed" };
|
|
1434
1519
|
let config = {};
|
|
1435
|
-
if (
|
|
1520
|
+
if (existsSync7(mcpPath)) {
|
|
1436
1521
|
try {
|
|
1437
|
-
config = JSON.parse(await
|
|
1522
|
+
config = JSON.parse(await readFile4(mcpPath, "utf8"));
|
|
1438
1523
|
} catch {
|
|
1439
1524
|
}
|
|
1440
1525
|
}
|
|
1441
1526
|
config.mcpServers ??= {};
|
|
1442
1527
|
if (config.mcpServers["haive"]) return { client: "Cursor", status: "already_configured" };
|
|
1443
1528
|
config.mcpServers["haive"] = HAIVE_MCP_ENTRY;
|
|
1444
|
-
await
|
|
1445
|
-
await
|
|
1529
|
+
await mkdir3(cursorDir, { recursive: true });
|
|
1530
|
+
await writeFile4(mcpPath, JSON.stringify(config, null, 2), "utf8");
|
|
1446
1531
|
return { client: "Cursor", status: "configured", path: mcpPath };
|
|
1447
1532
|
}
|
|
1448
1533
|
function vscodeMcpPath() {
|
|
1449
1534
|
const candidates = [
|
|
1450
|
-
|
|
1535
|
+
path7.join(HOME, ".config", "Code", "User", "mcp.json"),
|
|
1451
1536
|
// Linux
|
|
1452
|
-
|
|
1537
|
+
path7.join(HOME, "Library", "Application Support", "Code", "User", "mcp.json"),
|
|
1453
1538
|
// macOS
|
|
1454
|
-
|
|
1539
|
+
path7.join(HOME, "AppData", "Roaming", "Code", "User", "mcp.json"),
|
|
1455
1540
|
// Windows
|
|
1456
|
-
|
|
1541
|
+
path7.join(HOME, ".config", "Code - Insiders", "User", "mcp.json")
|
|
1457
1542
|
];
|
|
1458
1543
|
for (const c of candidates) {
|
|
1459
|
-
if (
|
|
1544
|
+
if (existsSync7(path7.dirname(c))) return c;
|
|
1460
1545
|
}
|
|
1461
1546
|
return null;
|
|
1462
1547
|
}
|
|
@@ -1464,51 +1549,51 @@ async function configureVSCode() {
|
|
|
1464
1549
|
const mcpPath = vscodeMcpPath();
|
|
1465
1550
|
if (!mcpPath) return { client: "VS Code", status: "not_installed" };
|
|
1466
1551
|
let config = {};
|
|
1467
|
-
if (
|
|
1552
|
+
if (existsSync7(mcpPath)) {
|
|
1468
1553
|
try {
|
|
1469
|
-
config = JSON.parse(await
|
|
1554
|
+
config = JSON.parse(await readFile4(mcpPath, "utf8"));
|
|
1470
1555
|
} catch {
|
|
1471
1556
|
}
|
|
1472
1557
|
}
|
|
1473
1558
|
config.servers ??= {};
|
|
1474
1559
|
if (config.servers["haive"]) return { client: "VS Code", status: "already_configured" };
|
|
1475
1560
|
config.servers["haive"] = { ...HAIVE_MCP_ENTRY, type: "stdio" };
|
|
1476
|
-
await
|
|
1477
|
-
await
|
|
1561
|
+
await mkdir3(path7.dirname(mcpPath), { recursive: true });
|
|
1562
|
+
await writeFile4(mcpPath, JSON.stringify(config, null, 2), "utf8");
|
|
1478
1563
|
return { client: "VS Code", status: "configured", path: mcpPath };
|
|
1479
1564
|
}
|
|
1480
1565
|
function claudeConfigPath() {
|
|
1481
|
-
const p =
|
|
1482
|
-
if (
|
|
1483
|
-
const p2 =
|
|
1484
|
-
if (
|
|
1566
|
+
const p = path7.join(HOME, ".claude.json");
|
|
1567
|
+
if (existsSync7(p)) return p;
|
|
1568
|
+
const p2 = path7.join(HOME, ".config", "claude", "claude.json");
|
|
1569
|
+
if (existsSync7(path7.dirname(p2))) return p2;
|
|
1485
1570
|
return null;
|
|
1486
1571
|
}
|
|
1487
1572
|
async function configureClaude() {
|
|
1488
|
-
const cfgPath = claudeConfigPath() ??
|
|
1489
|
-
if (!
|
|
1573
|
+
const cfgPath = claudeConfigPath() ?? path7.join(HOME, ".claude.json");
|
|
1574
|
+
if (!existsSync7(cfgPath) && !existsSync7(path7.join(HOME, ".claude"))) {
|
|
1490
1575
|
return { client: "Claude Code", status: "not_installed" };
|
|
1491
1576
|
}
|
|
1492
1577
|
let config = {};
|
|
1493
|
-
if (
|
|
1578
|
+
if (existsSync7(cfgPath)) {
|
|
1494
1579
|
try {
|
|
1495
|
-
config = JSON.parse(await
|
|
1580
|
+
config = JSON.parse(await readFile4(cfgPath, "utf8"));
|
|
1496
1581
|
} catch {
|
|
1497
1582
|
}
|
|
1498
1583
|
}
|
|
1499
1584
|
config.mcpServers ??= {};
|
|
1500
1585
|
if (config.mcpServers["haive"]) return { client: "Claude Code", status: "already_configured" };
|
|
1501
1586
|
config.mcpServers["haive"] = { ...HAIVE_MCP_ENTRY, type: "stdio" };
|
|
1502
|
-
await
|
|
1587
|
+
await writeFile4(cfgPath, JSON.stringify(config, null, 2), "utf8");
|
|
1503
1588
|
return { client: "Claude Code", status: "configured", path: cfgPath };
|
|
1504
1589
|
}
|
|
1505
1590
|
function windsurfMcpPath() {
|
|
1506
1591
|
const candidates = [
|
|
1507
|
-
|
|
1508
|
-
|
|
1592
|
+
path7.join(HOME, ".codeium", "windsurf", "mcp_config.json"),
|
|
1593
|
+
path7.join(HOME, ".windsurf", "mcp.json")
|
|
1509
1594
|
];
|
|
1510
1595
|
for (const c of candidates) {
|
|
1511
|
-
if (
|
|
1596
|
+
if (existsSync7(path7.dirname(c))) return c;
|
|
1512
1597
|
}
|
|
1513
1598
|
return null;
|
|
1514
1599
|
}
|
|
@@ -1516,17 +1601,17 @@ async function configureWindsurf() {
|
|
|
1516
1601
|
const mcpPath = windsurfMcpPath();
|
|
1517
1602
|
if (!mcpPath) return { client: "Windsurf", status: "not_installed" };
|
|
1518
1603
|
let config = {};
|
|
1519
|
-
if (
|
|
1604
|
+
if (existsSync7(mcpPath)) {
|
|
1520
1605
|
try {
|
|
1521
|
-
config = JSON.parse(await
|
|
1606
|
+
config = JSON.parse(await readFile4(mcpPath, "utf8"));
|
|
1522
1607
|
} catch {
|
|
1523
1608
|
}
|
|
1524
1609
|
}
|
|
1525
1610
|
config.mcpServers ??= {};
|
|
1526
1611
|
if (config.mcpServers["haive"]) return { client: "Windsurf", status: "already_configured" };
|
|
1527
1612
|
config.mcpServers["haive"] = HAIVE_MCP_ENTRY;
|
|
1528
|
-
await
|
|
1529
|
-
await
|
|
1613
|
+
await mkdir3(path7.dirname(mcpPath), { recursive: true });
|
|
1614
|
+
await writeFile4(mcpPath, JSON.stringify(config, null, 2), "utf8");
|
|
1530
1615
|
return { client: "Windsurf", status: "configured", path: mcpPath };
|
|
1531
1616
|
}
|
|
1532
1617
|
async function autoConfigureMcpClients() {
|
|
@@ -1546,51 +1631,51 @@ async function configureProjectMcpClients(root) {
|
|
|
1546
1631
|
const entry = projectMcpEntry(root);
|
|
1547
1632
|
const results = [];
|
|
1548
1633
|
try {
|
|
1549
|
-
const cursorPath =
|
|
1634
|
+
const cursorPath = path7.join(root, ".cursor", "mcp.json");
|
|
1550
1635
|
let config = {};
|
|
1551
|
-
if (
|
|
1636
|
+
if (existsSync7(cursorPath)) {
|
|
1552
1637
|
try {
|
|
1553
|
-
config = JSON.parse(await
|
|
1638
|
+
config = JSON.parse(await readFile4(cursorPath, "utf8"));
|
|
1554
1639
|
} catch {
|
|
1555
1640
|
}
|
|
1556
1641
|
}
|
|
1557
1642
|
config.mcpServers ??= {};
|
|
1558
1643
|
config.mcpServers["haive"] = entry;
|
|
1559
|
-
await
|
|
1560
|
-
await
|
|
1644
|
+
await mkdir3(path7.dirname(cursorPath), { recursive: true });
|
|
1645
|
+
await writeFile4(cursorPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
1561
1646
|
results.push({ client: "Cursor (project)", status: "configured", path: cursorPath });
|
|
1562
1647
|
} catch (err) {
|
|
1563
1648
|
results.push({ client: "Cursor (project)", status: "error", error: String(err) });
|
|
1564
1649
|
}
|
|
1565
1650
|
try {
|
|
1566
|
-
const vscodePath =
|
|
1651
|
+
const vscodePath = path7.join(root, ".vscode", "mcp.json");
|
|
1567
1652
|
let config = {};
|
|
1568
|
-
if (
|
|
1653
|
+
if (existsSync7(vscodePath)) {
|
|
1569
1654
|
try {
|
|
1570
|
-
config = JSON.parse(await
|
|
1655
|
+
config = JSON.parse(await readFile4(vscodePath, "utf8"));
|
|
1571
1656
|
} catch {
|
|
1572
1657
|
}
|
|
1573
1658
|
}
|
|
1574
1659
|
config.servers ??= {};
|
|
1575
1660
|
config.servers["haive"] = { ...entry, type: "stdio" };
|
|
1576
|
-
await
|
|
1577
|
-
await
|
|
1661
|
+
await mkdir3(path7.dirname(vscodePath), { recursive: true });
|
|
1662
|
+
await writeFile4(vscodePath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
1578
1663
|
results.push({ client: "VS Code (workspace)", status: "configured", path: vscodePath });
|
|
1579
1664
|
} catch (err) {
|
|
1580
1665
|
results.push({ client: "VS Code (workspace)", status: "error", error: String(err) });
|
|
1581
1666
|
}
|
|
1582
1667
|
try {
|
|
1583
|
-
const mcpPath =
|
|
1668
|
+
const mcpPath = path7.join(root, ".mcp.json");
|
|
1584
1669
|
let config = {};
|
|
1585
|
-
if (
|
|
1670
|
+
if (existsSync7(mcpPath)) {
|
|
1586
1671
|
try {
|
|
1587
|
-
config = JSON.parse(await
|
|
1672
|
+
config = JSON.parse(await readFile4(mcpPath, "utf8"));
|
|
1588
1673
|
} catch {
|
|
1589
1674
|
}
|
|
1590
1675
|
}
|
|
1591
1676
|
config.mcpServers ??= {};
|
|
1592
1677
|
config.mcpServers["haive"] = { ...entry, type: "stdio" };
|
|
1593
|
-
await
|
|
1678
|
+
await writeFile4(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
1594
1679
|
results.push({ client: "Claude Code (project)", status: "configured", path: mcpPath });
|
|
1595
1680
|
} catch (err) {
|
|
1596
1681
|
results.push({ client: "Claude Code (project)", status: "error", error: String(err) });
|
|
@@ -1656,9 +1741,9 @@ async function detectAgentMode(dir) {
|
|
|
1656
1741
|
const root = findProjectRoot6(dir);
|
|
1657
1742
|
const paths = resolveHaivePaths5(root);
|
|
1658
1743
|
const projectMcp = [
|
|
1659
|
-
{ client: "Claude Code", path:
|
|
1660
|
-
{ client: "Cursor", path:
|
|
1661
|
-
{ client: "VS Code", path:
|
|
1744
|
+
{ client: "Claude Code", path: path8.join(root, ".mcp.json"), present: existsSync8(path8.join(root, ".mcp.json")) },
|
|
1745
|
+
{ client: "Cursor", path: path8.join(root, ".cursor", "mcp.json"), present: existsSync8(path8.join(root, ".cursor", "mcp.json")) },
|
|
1746
|
+
{ client: "VS Code", path: path8.join(root, ".vscode", "mcp.json"), present: existsSync8(path8.join(root, ".vscode", "mcp.json")) }
|
|
1662
1747
|
];
|
|
1663
1748
|
const installedAgents = [
|
|
1664
1749
|
{ agent: "Codex", command: "codex", installed: commandExists("codex"), mcp_configured: codexMcpConfigured() },
|
|
@@ -1673,7 +1758,7 @@ async function detectAgentMode(dir) {
|
|
|
1673
1758
|
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 "..."';
|
|
1674
1759
|
return {
|
|
1675
1760
|
root,
|
|
1676
|
-
initialized:
|
|
1761
|
+
initialized: existsSync8(paths.haiveDir),
|
|
1677
1762
|
project_mcp: projectMcp,
|
|
1678
1763
|
installed_agents: installedAgents,
|
|
1679
1764
|
recommended_mode: recommendedMode,
|
|
@@ -1681,9 +1766,9 @@ async function detectAgentMode(dir) {
|
|
|
1681
1766
|
};
|
|
1682
1767
|
}
|
|
1683
1768
|
async function writeAgentModeRecord(paths, detection, skippedReason) {
|
|
1684
|
-
const dir =
|
|
1685
|
-
await
|
|
1686
|
-
const file =
|
|
1769
|
+
const dir = path8.join(paths.runtimeDir, "enforcement");
|
|
1770
|
+
await mkdir4(dir, { recursive: true });
|
|
1771
|
+
const file = path8.join(dir, "agent-mode.json");
|
|
1687
1772
|
const record = {
|
|
1688
1773
|
selected_mode: detection.recommended_mode,
|
|
1689
1774
|
recommended_command: detection.recommended_command,
|
|
@@ -1696,7 +1781,7 @@ async function writeAgentModeRecord(paths, detection, skippedReason) {
|
|
|
1696
1781
|
...skippedReason ? [skippedReason] : []
|
|
1697
1782
|
]
|
|
1698
1783
|
};
|
|
1699
|
-
await
|
|
1784
|
+
await writeFile5(file, JSON.stringify(record, null, 2) + "\n", "utf8");
|
|
1700
1785
|
return file;
|
|
1701
1786
|
}
|
|
1702
1787
|
async function confirmGlobalSetup() {
|
|
@@ -1724,7 +1809,7 @@ async function configureCodexIfAvailable(root) {
|
|
|
1724
1809
|
"mcp",
|
|
1725
1810
|
"--stdio"
|
|
1726
1811
|
], { encoding: "utf8" });
|
|
1727
|
-
if (result.status === 0) return { client: "Codex", status: "configured", path:
|
|
1812
|
+
if (result.status === 0) return { client: "Codex", status: "configured", path: path8.join(os2.homedir(), ".codex", "config.toml") };
|
|
1728
1813
|
return { client: "Codex", status: "error", error: result.stderr || result.stdout || "codex mcp add failed" };
|
|
1729
1814
|
}
|
|
1730
1815
|
function commandExists(command) {
|
|
@@ -1751,7 +1836,7 @@ function printDetection(detection, json) {
|
|
|
1751
1836
|
console.log(ui.dim(` root: ${detection.root}`));
|
|
1752
1837
|
console.log(`${detection.initialized ? ui.green("\u2713") : ui.red("\u2717")} project initialized`);
|
|
1753
1838
|
for (const cfg of detection.project_mcp) {
|
|
1754
|
-
console.log(`${cfg.present ? ui.green("\u2713") : ui.yellow("\u2022")} ${cfg.client} project MCP ${ui.dim(
|
|
1839
|
+
console.log(`${cfg.present ? ui.green("\u2713") : ui.yellow("\u2022")} ${cfg.client} project MCP ${ui.dim(path8.relative(detection.root, cfg.path))}`);
|
|
1755
1840
|
}
|
|
1756
1841
|
for (const agent of detection.installed_agents) {
|
|
1757
1842
|
const marker = agent.installed ? ui.green("\u2713") : ui.dim("\u2022");
|
|
@@ -1779,9 +1864,9 @@ function printSetupResult(result) {
|
|
|
1779
1864
|
}
|
|
1780
1865
|
|
|
1781
1866
|
// src/commands/init-bootstrap.ts
|
|
1782
|
-
import { readdir, readFile as
|
|
1783
|
-
import { existsSync as
|
|
1784
|
-
import
|
|
1867
|
+
import { readdir, readFile as readFile5 } from "fs/promises";
|
|
1868
|
+
import { existsSync as existsSync9, readdirSync } from "fs";
|
|
1869
|
+
import path9 from "path";
|
|
1785
1870
|
var IGNORE_DIRS = /* @__PURE__ */ new Set([
|
|
1786
1871
|
"node_modules",
|
|
1787
1872
|
"dist",
|
|
@@ -1867,12 +1952,12 @@ function detectKeyDeps(allDeps) {
|
|
|
1867
1952
|
return KEY_DEPS.filter((d) => allDeps[d] !== void 0);
|
|
1868
1953
|
}
|
|
1869
1954
|
function detectLanguage(root) {
|
|
1870
|
-
if (
|
|
1871
|
-
if (
|
|
1872
|
-
if (
|
|
1873
|
-
if (
|
|
1874
|
-
if (
|
|
1875
|
-
if (
|
|
1955
|
+
if (existsSync9(path9.join(root, "tsconfig.json"))) return "TypeScript";
|
|
1956
|
+
if (existsSync9(path9.join(root, "pyproject.toml")) || existsSync9(path9.join(root, "setup.py"))) return "Python";
|
|
1957
|
+
if (existsSync9(path9.join(root, "go.mod"))) return "Go";
|
|
1958
|
+
if (existsSync9(path9.join(root, "pom.xml")) || existsSync9(path9.join(root, "build.gradle"))) return "Java/Kotlin";
|
|
1959
|
+
if (existsSync9(path9.join(root, "Cargo.toml"))) return "Rust";
|
|
1960
|
+
if (existsSync9(path9.join(root, "package.json"))) {
|
|
1876
1961
|
return hasSourceWithExt(root, [".ts", ".tsx", ".mts", ".cts"]) ? "TypeScript" : "JavaScript";
|
|
1877
1962
|
}
|
|
1878
1963
|
return "Unknown";
|
|
@@ -1892,7 +1977,7 @@ function hasSourceWithExt(root, exts) {
|
|
|
1892
1977
|
if (depth <= 0) return false;
|
|
1893
1978
|
for (const entry of entries) {
|
|
1894
1979
|
if (entry.isDirectory() && !IGNORE_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
|
|
1895
|
-
if (scanDir(
|
|
1980
|
+
if (scanDir(path9.join(dir, entry.name), depth - 1)) return true;
|
|
1896
1981
|
}
|
|
1897
1982
|
}
|
|
1898
1983
|
return false;
|
|
@@ -1913,7 +1998,7 @@ function detectProjectType(frameworks, scripts, isMonorepo) {
|
|
|
1913
1998
|
if (frameworks.includes("Express") || frameworks.includes("Fastify") || frameworks.includes("Hono")) return "Backend API";
|
|
1914
1999
|
if (frameworks.includes("React") || frameworks.includes("Vue") || frameworks.includes("Svelte")) return "Frontend SPA";
|
|
1915
2000
|
if (scripts["build"] && !scripts["dev"]) return "CLI tool / library";
|
|
1916
|
-
if (
|
|
2001
|
+
if (existsSync9("pom.xml")) return "Java backend";
|
|
1917
2002
|
return "Application";
|
|
1918
2003
|
}
|
|
1919
2004
|
async function scanDirs(root, maxDepth = 2) {
|
|
@@ -1929,9 +2014,9 @@ async function scanDirs(root, maxDepth = 2) {
|
|
|
1929
2014
|
for (const entry of entries) {
|
|
1930
2015
|
if (!entry.isDirectory()) continue;
|
|
1931
2016
|
if (IGNORE_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
|
|
1932
|
-
const rel =
|
|
2017
|
+
const rel = path9.relative(root, path9.join(dir, entry.name));
|
|
1933
2018
|
results.push(rel);
|
|
1934
|
-
await walk(
|
|
2019
|
+
await walk(path9.join(dir, entry.name), depth + 1);
|
|
1935
2020
|
}
|
|
1936
2021
|
}
|
|
1937
2022
|
await walk(root, 0);
|
|
@@ -2048,10 +2133,10 @@ function readmeExcerpt(readme) {
|
|
|
2048
2133
|
}
|
|
2049
2134
|
async function generateBootstrapContext(root) {
|
|
2050
2135
|
let pkg = {};
|
|
2051
|
-
const pkgPath =
|
|
2052
|
-
if (
|
|
2136
|
+
const pkgPath = path9.join(root, "package.json");
|
|
2137
|
+
if (existsSync9(pkgPath)) {
|
|
2053
2138
|
try {
|
|
2054
|
-
pkg = JSON.parse(await
|
|
2139
|
+
pkg = JSON.parse(await readFile5(pkgPath, "utf8"));
|
|
2055
2140
|
} catch {
|
|
2056
2141
|
}
|
|
2057
2142
|
}
|
|
@@ -2061,14 +2146,14 @@ async function generateBootstrapContext(root) {
|
|
|
2061
2146
|
const language = detectLanguage(root);
|
|
2062
2147
|
const isMonorepo = pkg.workspaces !== void 0 && (Array.isArray(pkg.workspaces) ? pkg.workspaces.length > 0 : true);
|
|
2063
2148
|
const projectType = detectProjectType(frameworks, pkg.scripts ?? {}, isMonorepo);
|
|
2064
|
-
const projectName = pkg.name ??
|
|
2149
|
+
const projectName = pkg.name ?? path9.basename(root);
|
|
2065
2150
|
const projectDesc = pkg.description ?? "";
|
|
2066
2151
|
let readmeSummary = "";
|
|
2067
2152
|
for (const name of ["README.md", "readme.md", "README"]) {
|
|
2068
|
-
const p =
|
|
2069
|
-
if (
|
|
2153
|
+
const p = path9.join(root, name);
|
|
2154
|
+
if (existsSync9(p)) {
|
|
2070
2155
|
try {
|
|
2071
|
-
const content = await
|
|
2156
|
+
const content = await readFile5(p, "utf8");
|
|
2072
2157
|
readmeSummary = readmeExcerpt(content);
|
|
2073
2158
|
break;
|
|
2074
2159
|
} catch {
|
|
@@ -2132,9 +2217,9 @@ async function generateBootstrapContext(root) {
|
|
|
2132
2217
|
}
|
|
2133
2218
|
|
|
2134
2219
|
// src/commands/init-stack-packs.ts
|
|
2135
|
-
import { mkdir as
|
|
2136
|
-
import { existsSync as
|
|
2137
|
-
import
|
|
2220
|
+
import { mkdir as mkdir5, writeFile as writeFile6 } from "fs/promises";
|
|
2221
|
+
import { existsSync as existsSync10 } from "fs";
|
|
2222
|
+
import path10 from "path";
|
|
2138
2223
|
import {
|
|
2139
2224
|
buildFrontmatter,
|
|
2140
2225
|
memoryFilePath,
|
|
@@ -3213,7 +3298,7 @@ function autoDetectStacks(deps) {
|
|
|
3213
3298
|
async function seedStackPack(haivePaths, stack) {
|
|
3214
3299
|
const memories = PACKS[stack];
|
|
3215
3300
|
if (!memories) return { memories: 0, sensors: 0 };
|
|
3216
|
-
await
|
|
3301
|
+
await mkdir5(haivePaths.teamDir, { recursive: true });
|
|
3217
3302
|
let memCount = 0;
|
|
3218
3303
|
let sensorCount = 0;
|
|
3219
3304
|
for (const mem of memories) {
|
|
@@ -3227,9 +3312,10 @@ async function seedStackPack(haivePaths, stack) {
|
|
|
3227
3312
|
autogen: false,
|
|
3228
3313
|
last_fired: null
|
|
3229
3314
|
} : void 0;
|
|
3315
|
+
const combinedSlug = mem.slug === stack || mem.slug.startsWith(`${stack}-`) ? mem.slug : `${stack}-${mem.slug}`;
|
|
3230
3316
|
const fm = buildFrontmatter({
|
|
3231
3317
|
type: mem.type,
|
|
3232
|
-
slug:
|
|
3318
|
+
slug: combinedSlug,
|
|
3233
3319
|
scope: "team",
|
|
3234
3320
|
status: "validated",
|
|
3235
3321
|
// STACK_PACK_TAG marks this as generic seed knowledge so briefing ranking
|
|
@@ -3238,12 +3324,12 @@ async function seedStackPack(haivePaths, stack) {
|
|
|
3238
3324
|
...sensor ? { sensor } : {}
|
|
3239
3325
|
});
|
|
3240
3326
|
const filePath = memoryFilePath(haivePaths, "team", fm.id);
|
|
3241
|
-
if (
|
|
3327
|
+
if (existsSync10(filePath)) continue;
|
|
3242
3328
|
const content = serializeMemory2({ frontmatter: fm, body: `${mem.body}
|
|
3243
3329
|
|
|
3244
3330
|
${SEED_FOOTER(stack)}` });
|
|
3245
|
-
await
|
|
3246
|
-
await
|
|
3331
|
+
await mkdir5(path10.dirname(filePath), { recursive: true });
|
|
3332
|
+
await writeFile6(filePath, content, "utf8");
|
|
3247
3333
|
memCount++;
|
|
3248
3334
|
if (sensor) sensorCount++;
|
|
3249
3335
|
}
|
|
@@ -3252,7 +3338,7 @@ ${SEED_FOOTER(stack)}` });
|
|
|
3252
3338
|
|
|
3253
3339
|
// src/commands/init.ts
|
|
3254
3340
|
var execFileAsync = promisify2(execFile2);
|
|
3255
|
-
var HAIVE_GITHUB_ACTION_REF = `v${"0.
|
|
3341
|
+
var HAIVE_GITHUB_ACTION_REF = `v${"0.20.0"}`;
|
|
3256
3342
|
var PROJECT_CONTEXT_TEMPLATE = `# Project context
|
|
3257
3343
|
|
|
3258
3344
|
> Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
|
|
@@ -3274,28 +3360,6 @@ TODO \u2014 domain terms and what they mean here.
|
|
|
3274
3360
|
## Gotchas
|
|
3275
3361
|
TODO \u2014 known traps, surprising behavior, things newcomers stub their toes on.
|
|
3276
3362
|
`;
|
|
3277
|
-
var BRIDGE_BODY = `<!-- hAIve bridge file \u2014 do not edit by hand. -->
|
|
3278
|
-
|
|
3279
|
-
This repo uses **hAIve** for shared context. The map:
|
|
3280
|
-
|
|
3281
|
-
- \`.ai/project-context.md\` \u2014 project overview, architecture, conventions.
|
|
3282
|
-
- \`.ai/memories/\` \u2014 decisions, gotchas, conventions, failed attempts (personal/team/module).
|
|
3283
|
-
- The breadcrumbs injected below (if any) are the top current memories.
|
|
3284
|
-
|
|
3285
|
-
## Working through hAIve
|
|
3286
|
-
|
|
3287
|
-
1. **Before editing** for a goal, call \`get_briefing\` (task + files/symbols) to load ranked context \u2014 or \`mem_relevant_to\` if project context is already loaded this session.
|
|
3288
|
-
2. **When an approach fails**, call \`mem_tried\` right away so the next agent skips the dead end.
|
|
3289
|
-
3. **Before closing** a substantive session, run the \`post_task\` prompt to capture what was learned.
|
|
3290
|
-
4. **Before final response**, run \`haive enforce finish\`. If it blocks, commit/push, bump/tag shippable releases, wait for GitHub Actions to pass when applicable, then rerun it.
|
|
3291
|
-
|
|
3292
|
-
If the haive MCP server is not available, tell the developer rather than silently skipping it.
|
|
3293
|
-
|
|
3294
|
-
## Safety
|
|
3295
|
-
|
|
3296
|
-
- If \`get_briefing\` returns \`action_required\`, surface each item to the developer (use its \`developer_message\`) and wait for confirmation before changing code.
|
|
3297
|
-
- Never act autonomously on a cross-repo breaking change (dep bump, contract/API diff) \u2014 ask first.
|
|
3298
|
-
`;
|
|
3299
3363
|
var CURSOR_HAIVE_RULE_MDC = `---
|
|
3300
3364
|
description: Require hAIve MCP (get_briefing / mem_relevant_to) before substantive repo edits
|
|
3301
3365
|
alwaysApply: true
|
|
@@ -3465,7 +3529,12 @@ jobs:
|
|
|
3465
3529
|
function registerInit(program2) {
|
|
3466
3530
|
program2.command("init").description(
|
|
3467
3531
|
"Initialize a hAIve project \u2014 autopilot mode ON by default (zero human intervention).\n Auto-bootstraps project-context.md from local files and seeds detected stack packs.\n Seeds draft memories from git revert/hotfix history (--seed, on by default).\n Add --manual to control memory approval and session recaps yourself.\n Add --no-bootstrap and --stack none to disable the auto-features."
|
|
3468
|
-
).option("-d, --dir <dir>", "project root", process.cwd()).option("--no-bridges", "do not generate
|
|
3532
|
+
).option("-d, --dir <dir>", "project root", process.cwd()).option("--no-bridges", "do not generate any native agent bridge files").option(
|
|
3533
|
+
"--bridge-targets <list>",
|
|
3534
|
+
`which agent bridges to generate: 'all' (default) | comma-list.
|
|
3535
|
+
Available: ${BRIDGE_TARGETS.join(", ")}. Each carries top memories + block sensors.`,
|
|
3536
|
+
"all"
|
|
3537
|
+
).option("--with-ci", "write a GitHub Actions workflow (.github/workflows/haive-sync.yml) \u2014 included automatically in autopilot mode").option(
|
|
3469
3538
|
"--manual",
|
|
3470
3539
|
"opt out of autopilot: memories require manual approval, no auto-session recap, no auto-context"
|
|
3471
3540
|
).option(
|
|
@@ -3497,7 +3566,7 @@ function registerInit(program2) {
|
|
|
3497
3566
|
"approve user-level AI client configuration prompts during agent setup",
|
|
3498
3567
|
false
|
|
3499
3568
|
).option("--json", "emit a machine-readable summary on stdout (human logs go to stderr)", false).action(async (opts) => {
|
|
3500
|
-
const root =
|
|
3569
|
+
const root = path11.resolve(opts.dir);
|
|
3501
3570
|
const paths = resolveHaivePaths6(root);
|
|
3502
3571
|
const autopilot = opts.manual !== true;
|
|
3503
3572
|
const json = opts.json === true;
|
|
@@ -3513,35 +3582,36 @@ function registerInit(program2) {
|
|
|
3513
3582
|
gitSeedsWritten: 0,
|
|
3514
3583
|
gitCommitsScanned: 0,
|
|
3515
3584
|
gitRevertsFound: 0,
|
|
3516
|
-
gitRecurring: 0
|
|
3585
|
+
gitRecurring: 0,
|
|
3586
|
+
bridgesWritten: 0
|
|
3517
3587
|
};
|
|
3518
|
-
if (
|
|
3588
|
+
if (existsSync11(paths.haiveDir)) {
|
|
3519
3589
|
ui.warn(`.ai/ already exists at ${paths.haiveDir} \u2014 leaving existing files in place.`);
|
|
3520
3590
|
}
|
|
3521
|
-
await
|
|
3522
|
-
await
|
|
3523
|
-
await
|
|
3524
|
-
await
|
|
3591
|
+
await mkdir6(paths.personalDir, { recursive: true });
|
|
3592
|
+
await mkdir6(paths.teamDir, { recursive: true });
|
|
3593
|
+
await mkdir6(paths.moduleDir, { recursive: true });
|
|
3594
|
+
await mkdir6(paths.modulesContextDir, { recursive: true });
|
|
3525
3595
|
await ensureAiRuntimeLayout(paths.runtimeDir);
|
|
3526
|
-
await ensureAiCacheLayout(
|
|
3527
|
-
if (!
|
|
3596
|
+
await ensureAiCacheLayout(path11.join(paths.haiveDir, ".cache"));
|
|
3597
|
+
if (!existsSync11(paths.projectContext)) {
|
|
3528
3598
|
if (wantBootstrap) {
|
|
3529
3599
|
ui.info("Bootstrapping project context from local files\u2026");
|
|
3530
3600
|
try {
|
|
3531
3601
|
const context = await generateBootstrapContext(root);
|
|
3532
|
-
await
|
|
3602
|
+
await writeFile7(paths.projectContext, context, "utf8");
|
|
3533
3603
|
ui.success("Created .ai/project-context.md (auto-bootstrapped from local files)");
|
|
3534
3604
|
} catch (err) {
|
|
3535
3605
|
ui.warn(`Bootstrap failed (${String(err)}) \u2014 writing default template instead`);
|
|
3536
|
-
await
|
|
3606
|
+
await writeFile7(paths.projectContext, PROJECT_CONTEXT_TEMPLATE, "utf8");
|
|
3537
3607
|
}
|
|
3538
3608
|
} else {
|
|
3539
|
-
await
|
|
3540
|
-
ui.success(`Created ${
|
|
3609
|
+
await writeFile7(paths.projectContext, PROJECT_CONTEXT_TEMPLATE, "utf8");
|
|
3610
|
+
ui.success(`Created ${path11.relative(root, paths.projectContext)}`);
|
|
3541
3611
|
}
|
|
3542
3612
|
}
|
|
3543
|
-
const configExists =
|
|
3544
|
-
|
|
3613
|
+
const configExists = existsSync11(
|
|
3614
|
+
path11.join(paths.haiveDir, "haive.config.json")
|
|
3545
3615
|
);
|
|
3546
3616
|
if (!configExists) {
|
|
3547
3617
|
await saveConfig2(paths, autopilot ? AUTOPILOT_DEFAULTS2 : { autopilot: false });
|
|
@@ -3549,13 +3619,6 @@ function registerInit(program2) {
|
|
|
3549
3619
|
`Created .ai/haive.config.json (mode: ${autopilot ? "autopilot" : "standard"})`
|
|
3550
3620
|
);
|
|
3551
3621
|
}
|
|
3552
|
-
if (opts.bridges) {
|
|
3553
|
-
await writeBridge(root, "CLAUDE.md");
|
|
3554
|
-
await writeBridge(root, "AGENTS.md");
|
|
3555
|
-
await writeBridge(root, ".cursorrules");
|
|
3556
|
-
await writeBridge(root, path10.join(".github", "copilot-instructions.md"));
|
|
3557
|
-
await writeCursorHaiveRule(root);
|
|
3558
|
-
}
|
|
3559
3622
|
const stacksToSeed = await resolveStacksToSeed(root, wantStack);
|
|
3560
3623
|
if (stacksToSeed.length > 0) {
|
|
3561
3624
|
let totalSeeded = 0;
|
|
@@ -3597,15 +3660,31 @@ function registerInit(program2) {
|
|
|
3597
3660
|
ui.info("Git seeding: no revert/hotfix signals found \u2014 run `haive memory seed-git` later.");
|
|
3598
3661
|
}
|
|
3599
3662
|
}
|
|
3663
|
+
if (opts.bridges) {
|
|
3664
|
+
const targets = resolveBridgeTargets(opts.bridgeTargets);
|
|
3665
|
+
const res = await writeBridgeFiles(root, paths, { targets });
|
|
3666
|
+
await writeCursorHaiveRule(root);
|
|
3667
|
+
const made = res.created.length + res.updated.length;
|
|
3668
|
+
report.bridgesWritten = made;
|
|
3669
|
+
if (res.created.length > 0) {
|
|
3670
|
+
ui.success(`Generated ${res.created.length} agent bridge(s): ${res.created.join(", ")}`);
|
|
3671
|
+
}
|
|
3672
|
+
if (res.updated.length > 0) {
|
|
3673
|
+
ui.info(`Refreshed ${res.updated.length} existing bridge(s): ${res.updated.join(", ")}`);
|
|
3674
|
+
}
|
|
3675
|
+
if (made === 0) {
|
|
3676
|
+
ui.info("Bridges already up to date.");
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3600
3679
|
const wantCi = opts.withCi || autopilot;
|
|
3601
3680
|
if (wantCi) {
|
|
3602
|
-
const ciPath =
|
|
3603
|
-
if (
|
|
3681
|
+
const ciPath = path11.join(root, ".github", "workflows", "haive-sync.yml");
|
|
3682
|
+
if (existsSync11(ciPath)) {
|
|
3604
3683
|
ui.info("CI workflow already exists \u2014 skipped");
|
|
3605
3684
|
} else {
|
|
3606
|
-
await
|
|
3607
|
-
await
|
|
3608
|
-
ui.success(`Created ${
|
|
3685
|
+
await mkdir6(path11.dirname(ciPath), { recursive: true });
|
|
3686
|
+
await writeFile7(ciPath, CI_WORKFLOW, "utf8");
|
|
3687
|
+
ui.success(`Created ${path11.relative(root, ciPath)}`);
|
|
3609
3688
|
}
|
|
3610
3689
|
}
|
|
3611
3690
|
if (autopilot) {
|
|
@@ -3645,7 +3724,7 @@ function registerInit(program2) {
|
|
|
3645
3724
|
interactive: process.stdin.isTTY
|
|
3646
3725
|
});
|
|
3647
3726
|
for (const r of agentSetup.project_results) {
|
|
3648
|
-
if (r.status === "configured" && r.path) ui.success(`haive MCP project config written (${
|
|
3727
|
+
if (r.status === "configured" && r.path) ui.success(`haive MCP project config written (${path11.relative(root, r.path)})`);
|
|
3649
3728
|
else if (r.status === "error") ui.warn(`${r.client}: ${r.error}`);
|
|
3650
3729
|
}
|
|
3651
3730
|
for (const r of agentSetup.global_results) {
|
|
@@ -3682,6 +3761,7 @@ function registerInit(program2) {
|
|
|
3682
3761
|
git_seeds_written: report.gitSeedsWritten,
|
|
3683
3762
|
git_recurring: report.gitRecurring,
|
|
3684
3763
|
bridges: opts.bridges !== false,
|
|
3764
|
+
bridges_written: report.bridgesWritten,
|
|
3685
3765
|
ci: opts.withCi || autopilot
|
|
3686
3766
|
}, null, 2));
|
|
3687
3767
|
return;
|
|
@@ -3743,57 +3823,57 @@ async function autoDetectStacksFromRoot(root) {
|
|
|
3743
3823
|
let requirementsTxt;
|
|
3744
3824
|
let goMod;
|
|
3745
3825
|
let pomXml;
|
|
3746
|
-
const pkgPath =
|
|
3747
|
-
if (
|
|
3826
|
+
const pkgPath = path11.join(root, "package.json");
|
|
3827
|
+
if (existsSync11(pkgPath)) {
|
|
3748
3828
|
try {
|
|
3749
|
-
const pkg = JSON.parse(await
|
|
3829
|
+
const pkg = JSON.parse(await readFile6(pkgPath, "utf8"));
|
|
3750
3830
|
packageJsonDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
3751
3831
|
} catch {
|
|
3752
3832
|
}
|
|
3753
3833
|
}
|
|
3754
3834
|
for (const name of ["requirements.txt", "requirements/base.txt", "requirements/prod.txt"]) {
|
|
3755
|
-
const reqPath =
|
|
3756
|
-
if (
|
|
3835
|
+
const reqPath = path11.join(root, name);
|
|
3836
|
+
if (existsSync11(reqPath)) {
|
|
3757
3837
|
try {
|
|
3758
|
-
requirementsTxt = await
|
|
3838
|
+
requirementsTxt = await readFile6(reqPath, "utf8");
|
|
3759
3839
|
break;
|
|
3760
3840
|
} catch {
|
|
3761
3841
|
}
|
|
3762
3842
|
}
|
|
3763
3843
|
}
|
|
3764
|
-
const goModPath =
|
|
3765
|
-
if (
|
|
3844
|
+
const goModPath = path11.join(root, "go.mod");
|
|
3845
|
+
if (existsSync11(goModPath)) {
|
|
3766
3846
|
try {
|
|
3767
|
-
goMod = await
|
|
3847
|
+
goMod = await readFile6(goModPath, "utf8");
|
|
3768
3848
|
} catch {
|
|
3769
3849
|
}
|
|
3770
3850
|
}
|
|
3771
|
-
const pomPath =
|
|
3772
|
-
if (
|
|
3851
|
+
const pomPath = path11.join(root, "pom.xml");
|
|
3852
|
+
if (existsSync11(pomPath)) {
|
|
3773
3853
|
try {
|
|
3774
|
-
pomXml = await
|
|
3854
|
+
pomXml = await readFile6(pomPath, "utf8");
|
|
3775
3855
|
} catch {
|
|
3776
3856
|
}
|
|
3777
3857
|
}
|
|
3778
3858
|
let composerJson;
|
|
3779
|
-
const composerPath =
|
|
3780
|
-
if (
|
|
3859
|
+
const composerPath = path11.join(root, "composer.json");
|
|
3860
|
+
if (existsSync11(composerPath)) {
|
|
3781
3861
|
try {
|
|
3782
|
-
composerJson = await
|
|
3862
|
+
composerJson = await readFile6(composerPath, "utf8");
|
|
3783
3863
|
} catch {
|
|
3784
3864
|
}
|
|
3785
3865
|
}
|
|
3786
3866
|
let gemfile;
|
|
3787
|
-
const gemfilePath =
|
|
3788
|
-
if (
|
|
3867
|
+
const gemfilePath = path11.join(root, "Gemfile");
|
|
3868
|
+
if (existsSync11(gemfilePath)) {
|
|
3789
3869
|
try {
|
|
3790
|
-
gemfile = await
|
|
3870
|
+
gemfile = await readFile6(gemfilePath, "utf8");
|
|
3791
3871
|
} catch {
|
|
3792
3872
|
}
|
|
3793
3873
|
}
|
|
3794
|
-
const hasDockerfile =
|
|
3795
|
-
const hasTurboJson =
|
|
3796
|
-
const hasNxJson =
|
|
3874
|
+
const hasDockerfile = existsSync11(path11.join(root, "Dockerfile"));
|
|
3875
|
+
const hasTurboJson = existsSync11(path11.join(root, "turbo.json"));
|
|
3876
|
+
const hasNxJson = existsSync11(path11.join(root, "nx.json"));
|
|
3797
3877
|
let hasCsproj = false;
|
|
3798
3878
|
try {
|
|
3799
3879
|
const entries = await readdir2(root);
|
|
@@ -3845,9 +3925,9 @@ async function seedFromGitHistory(root, paths, limit) {
|
|
|
3845
3925
|
_Seeded from git ${p.kind} commit ${p.source_sha}. Review and validate (or delete) \u2014 not yet authoritative._
|
|
3846
3926
|
`;
|
|
3847
3927
|
const file = memoryFilePath2(paths, fm.scope, fm.id, fm.module);
|
|
3848
|
-
if (
|
|
3849
|
-
await
|
|
3850
|
-
await
|
|
3928
|
+
if (existsSync11(file)) continue;
|
|
3929
|
+
await mkdir6(path11.dirname(file), { recursive: true });
|
|
3930
|
+
await writeFile7(file, serializeMemory3({ frontmatter: fm, body }), "utf8");
|
|
3851
3931
|
written++;
|
|
3852
3932
|
}
|
|
3853
3933
|
return { scanned: commits.length, found: proposals.length, recurring, written };
|
|
@@ -3886,6 +3966,9 @@ function printInitReport(r) {
|
|
|
3886
3966
|
if (r.totalMemories > 0) {
|
|
3887
3967
|
lines.push(` Total ready : ${r.totalMemories} lesson(s), ${r.totalSensors} sensor(s) \u2014 0 written by hand`);
|
|
3888
3968
|
}
|
|
3969
|
+
if (r.bridgesWritten > 0) {
|
|
3970
|
+
lines.push(` Reach : ${r.bridgesWritten} agent bridge(s) generated (Cursor, Cline, Copilot, Roo, Gemini, \u2026)`);
|
|
3971
|
+
}
|
|
3889
3972
|
if (lines.length === 0) return;
|
|
3890
3973
|
const width = Math.max(...lines.map((l) => l.length), 44);
|
|
3891
3974
|
const bar = "\u2500".repeat(width + 2);
|
|
@@ -3900,29 +3983,30 @@ function printInitReport(r) {
|
|
|
3900
3983
|
console.log(ui.dim(" Review draft seeds: haive memory pending (validate or delete each one)"));
|
|
3901
3984
|
}
|
|
3902
3985
|
console.log(
|
|
3903
|
-
ui.dim(" Reach:
|
|
3986
|
+
ui.dim(" Reach: bridges auto-refresh on `haive sync`; regenerate any time with `haive bridges sync --all`.")
|
|
3904
3987
|
);
|
|
3905
3988
|
}
|
|
3906
3989
|
async function writeCursorHaiveRule(root) {
|
|
3907
3990
|
const relPath = ".cursor/rules/haive-mcp-required.mdc";
|
|
3908
|
-
const target =
|
|
3909
|
-
if (
|
|
3991
|
+
const target = path11.join(root, relPath);
|
|
3992
|
+
if (existsSync11(target)) {
|
|
3910
3993
|
ui.info(`Cursor rule ${relPath} already exists \u2014 skipped`);
|
|
3911
3994
|
return;
|
|
3912
3995
|
}
|
|
3913
|
-
await
|
|
3914
|
-
await
|
|
3996
|
+
await mkdir6(path11.dirname(target), { recursive: true });
|
|
3997
|
+
await writeFile7(target, CURSOR_HAIVE_RULE_MDC, "utf8");
|
|
3915
3998
|
ui.success(`Created Cursor rule ${relPath}`);
|
|
3916
3999
|
}
|
|
3917
|
-
|
|
3918
|
-
const
|
|
3919
|
-
if (
|
|
3920
|
-
|
|
3921
|
-
|
|
4000
|
+
function resolveBridgeTargets(opt) {
|
|
4001
|
+
const raw = (opt ?? "all").trim().toLowerCase();
|
|
4002
|
+
if (raw === "" || raw === "all") return [...BRIDGE_TARGETS];
|
|
4003
|
+
const requested = raw.split(",").map((t) => t.trim()).filter(Boolean);
|
|
4004
|
+
const valid = requested.filter((t) => BRIDGE_TARGETS.includes(t));
|
|
4005
|
+
const invalid = requested.filter((t) => !BRIDGE_TARGETS.includes(t));
|
|
4006
|
+
if (invalid.length > 0) {
|
|
4007
|
+
ui.warn(`Ignoring unknown bridge target(s): ${invalid.join(", ")}. Valid: ${BRIDGE_TARGETS.join(", ")}`);
|
|
3922
4008
|
}
|
|
3923
|
-
|
|
3924
|
-
await writeFile6(target, BRIDGE_BODY, "utf8");
|
|
3925
|
-
ui.success(`Created bridge ${relPath}`);
|
|
4009
|
+
return valid.length > 0 ? valid : [...BRIDGE_TARGETS];
|
|
3926
4010
|
}
|
|
3927
4011
|
var RUNTIME_README_BODY = `# .ai/.runtime \u2014 disposable local layer
|
|
3928
4012
|
|
|
@@ -3937,50 +4021,50 @@ var RUNTIME_GITIGNORE_BODY = `*
|
|
|
3937
4021
|
!README.md
|
|
3938
4022
|
`;
|
|
3939
4023
|
async function ensureAiRuntimeLayout(runtimeDir) {
|
|
3940
|
-
await
|
|
3941
|
-
const gi =
|
|
3942
|
-
if (!
|
|
3943
|
-
await
|
|
4024
|
+
await mkdir6(runtimeDir, { recursive: true });
|
|
4025
|
+
const gi = path11.join(runtimeDir, ".gitignore");
|
|
4026
|
+
if (!existsSync11(gi)) {
|
|
4027
|
+
await writeFile7(gi, RUNTIME_GITIGNORE_BODY, "utf8");
|
|
3944
4028
|
}
|
|
3945
|
-
const readme =
|
|
3946
|
-
if (!
|
|
3947
|
-
await
|
|
4029
|
+
const readme = path11.join(runtimeDir, "README.md");
|
|
4030
|
+
if (!existsSync11(readme)) {
|
|
4031
|
+
await writeFile7(readme, RUNTIME_README_BODY, "utf8");
|
|
3948
4032
|
}
|
|
3949
4033
|
}
|
|
3950
4034
|
async function ensureAiCacheLayout(cacheDir) {
|
|
3951
|
-
await
|
|
3952
|
-
const gi =
|
|
3953
|
-
if (!
|
|
3954
|
-
await
|
|
4035
|
+
await mkdir6(cacheDir, { recursive: true });
|
|
4036
|
+
const gi = path11.join(cacheDir, ".gitignore");
|
|
4037
|
+
if (!existsSync11(gi)) {
|
|
4038
|
+
await writeFile7(gi, "*\n!.gitignore\n", "utf8");
|
|
3955
4039
|
}
|
|
3956
4040
|
}
|
|
3957
4041
|
async function ensureGitignoreEntries(root, patterns) {
|
|
3958
4042
|
try {
|
|
3959
|
-
const gitignorePath =
|
|
4043
|
+
const gitignorePath = path11.join(root, ".gitignore");
|
|
3960
4044
|
let existing = "";
|
|
3961
|
-
if (
|
|
3962
|
-
existing = await
|
|
4045
|
+
if (existsSync11(gitignorePath)) {
|
|
4046
|
+
existing = await readFile6(gitignorePath, "utf8");
|
|
3963
4047
|
}
|
|
3964
4048
|
const lines = existing.split("\n");
|
|
3965
4049
|
const missing = patterns.filter((p) => !lines.some((l) => l.trim() === p));
|
|
3966
4050
|
if (missing.length === 0) return;
|
|
3967
4051
|
const toAppend = (existing.endsWith("\n") || existing === "" ? "" : "\n") + "# hAIve project-level MCP configs (machine-specific absolute paths)\n" + missing.join("\n") + "\n";
|
|
3968
|
-
await
|
|
4052
|
+
await writeFile7(gitignorePath, existing + toAppend, "utf8");
|
|
3969
4053
|
} catch {
|
|
3970
4054
|
}
|
|
3971
4055
|
}
|
|
3972
4056
|
|
|
3973
4057
|
// src/commands/install-hooks.ts
|
|
3974
|
-
import { mkdir as
|
|
3975
|
-
import { existsSync as
|
|
3976
|
-
import
|
|
4058
|
+
import { mkdir as mkdir8, writeFile as writeFile9, chmod, readFile as readFile8 } from "fs/promises";
|
|
4059
|
+
import { existsSync as existsSync13 } from "fs";
|
|
4060
|
+
import path13 from "path";
|
|
3977
4061
|
import "commander";
|
|
3978
4062
|
import { findProjectRoot as findProjectRoot8 } from "@hiveai/core";
|
|
3979
4063
|
|
|
3980
4064
|
// src/utils/claude-hooks.ts
|
|
3981
|
-
import { existsSync as
|
|
3982
|
-
import { mkdir as
|
|
3983
|
-
import
|
|
4065
|
+
import { existsSync as existsSync12 } from "fs";
|
|
4066
|
+
import { mkdir as mkdir7, readFile as readFile7, writeFile as writeFile8 } from "fs/promises";
|
|
4067
|
+
import path12 from "path";
|
|
3984
4068
|
var HAIVE_HOOK_TAG = "haive-enforcement";
|
|
3985
4069
|
var POST_TOOL_USE_GROUP = {
|
|
3986
4070
|
matcher: "Edit|Write|Bash",
|
|
@@ -4066,9 +4150,9 @@ function unpatchClaudeSettings(input) {
|
|
|
4066
4150
|
async function installClaudeHooksAtPath(settingsPath) {
|
|
4067
4151
|
let raw = null;
|
|
4068
4152
|
let created = false;
|
|
4069
|
-
if (
|
|
4153
|
+
if (existsSync12(settingsPath)) {
|
|
4070
4154
|
try {
|
|
4071
|
-
raw = JSON.parse(await
|
|
4155
|
+
raw = JSON.parse(await readFile7(settingsPath, "utf8"));
|
|
4072
4156
|
} catch {
|
|
4073
4157
|
throw new Error(`${settingsPath} exists but is not valid JSON. Fix it manually first.`);
|
|
4074
4158
|
}
|
|
@@ -4076,25 +4160,25 @@ async function installClaudeHooksAtPath(settingsPath) {
|
|
|
4076
4160
|
created = true;
|
|
4077
4161
|
}
|
|
4078
4162
|
const patched = patchClaudeSettings(raw);
|
|
4079
|
-
await
|
|
4080
|
-
await
|
|
4163
|
+
await mkdir7(path12.dirname(settingsPath), { recursive: true });
|
|
4164
|
+
await writeFile8(settingsPath, JSON.stringify(patched, null, 2) + "\n", "utf8");
|
|
4081
4165
|
return { settingsPath, created };
|
|
4082
4166
|
}
|
|
4083
4167
|
async function uninstallClaudeHooksAtPath(settingsPath) {
|
|
4084
|
-
if (!
|
|
4168
|
+
if (!existsSync12(settingsPath)) {
|
|
4085
4169
|
return { settingsPath, created: false };
|
|
4086
4170
|
}
|
|
4087
|
-
const raw = JSON.parse(await
|
|
4171
|
+
const raw = JSON.parse(await readFile7(settingsPath, "utf8"));
|
|
4088
4172
|
const cleaned = unpatchClaudeSettings(raw);
|
|
4089
|
-
await
|
|
4173
|
+
await writeFile8(settingsPath, JSON.stringify(cleaned, null, 2) + "\n", "utf8");
|
|
4090
4174
|
return { settingsPath, created: false };
|
|
4091
4175
|
}
|
|
4092
4176
|
function defaultClaudeSettingsPath(scope, projectRoot) {
|
|
4093
4177
|
if (scope === "user") {
|
|
4094
4178
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
4095
|
-
return
|
|
4179
|
+
return path12.join(home, ".claude", "settings.json");
|
|
4096
4180
|
}
|
|
4097
|
-
return
|
|
4181
|
+
return path12.join(projectRoot, ".claude", "settings.local.json");
|
|
4098
4182
|
}
|
|
4099
4183
|
|
|
4100
4184
|
// src/commands/install-hooks.ts
|
|
@@ -4151,27 +4235,27 @@ fi
|
|
|
4151
4235
|
];
|
|
4152
4236
|
async function installGitHooks(opts) {
|
|
4153
4237
|
const root = findProjectRoot8(opts.dir);
|
|
4154
|
-
const gitDir =
|
|
4155
|
-
if (!
|
|
4238
|
+
const gitDir = path13.join(root, ".git");
|
|
4239
|
+
if (!existsSync13(gitDir)) {
|
|
4156
4240
|
ui.error(`No .git directory at ${root}.`);
|
|
4157
4241
|
process.exitCode = 1;
|
|
4158
4242
|
return;
|
|
4159
4243
|
}
|
|
4160
|
-
const hooksDir =
|
|
4161
|
-
await
|
|
4244
|
+
const hooksDir = path13.join(gitDir, "hooks");
|
|
4245
|
+
await mkdir8(hooksDir, { recursive: true });
|
|
4162
4246
|
let installed = 0;
|
|
4163
4247
|
let skipped = 0;
|
|
4164
4248
|
for (const { name, body } of HOOKS) {
|
|
4165
|
-
const file =
|
|
4166
|
-
if (
|
|
4167
|
-
const existing = await
|
|
4249
|
+
const file = path13.join(hooksDir, name);
|
|
4250
|
+
if (existsSync13(file) && !opts.force) {
|
|
4251
|
+
const existing = await readFile8(file, "utf8");
|
|
4168
4252
|
if (!existing.includes(HOOK_MARKER)) {
|
|
4169
4253
|
ui.warn(`${name} already exists and was not written by hAIve. Re-run with --force to overwrite.`);
|
|
4170
4254
|
skipped++;
|
|
4171
4255
|
continue;
|
|
4172
4256
|
}
|
|
4173
4257
|
}
|
|
4174
|
-
await
|
|
4258
|
+
await writeFile9(file, body, "utf8");
|
|
4175
4259
|
await chmod(file, 493);
|
|
4176
4260
|
installed++;
|
|
4177
4261
|
}
|
|
@@ -4226,9 +4310,9 @@ function registerInstallHooks(program2) {
|
|
|
4226
4310
|
}
|
|
4227
4311
|
|
|
4228
4312
|
// src/commands/observe.ts
|
|
4229
|
-
import { appendFile, mkdir as
|
|
4230
|
-
import { existsSync as
|
|
4231
|
-
import
|
|
4313
|
+
import { appendFile, mkdir as mkdir9 } from "fs/promises";
|
|
4314
|
+
import { existsSync as existsSync14 } from "fs";
|
|
4315
|
+
import path14 from "path";
|
|
4232
4316
|
import "commander";
|
|
4233
4317
|
import { findProjectRoot as findProjectRoot9, resolveHaivePaths as resolveHaivePaths7 } from "@hiveai/core";
|
|
4234
4318
|
var MAX_STDIN_BYTES = 256 * 1024;
|
|
@@ -4335,7 +4419,7 @@ function registerObserve(program2) {
|
|
|
4335
4419
|
})();
|
|
4336
4420
|
if (!root) return;
|
|
4337
4421
|
const paths = resolveHaivePaths7(root);
|
|
4338
|
-
if (!
|
|
4422
|
+
if (!existsSync14(paths.haiveDir)) return;
|
|
4339
4423
|
const failureHint = detectFailure(payload);
|
|
4340
4424
|
const observation = {
|
|
4341
4425
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -4346,10 +4430,10 @@ function registerObserve(program2) {
|
|
|
4346
4430
|
files: extractFiles(payload),
|
|
4347
4431
|
...failureHint ? { failure_hint: true } : {}
|
|
4348
4432
|
};
|
|
4349
|
-
const cacheDir =
|
|
4350
|
-
await
|
|
4433
|
+
const cacheDir = path14.join(paths.haiveDir, ".cache");
|
|
4434
|
+
await mkdir9(cacheDir, { recursive: true });
|
|
4351
4435
|
await appendFile(
|
|
4352
|
-
|
|
4436
|
+
path14.join(cacheDir, "observations.jsonl"),
|
|
4353
4437
|
JSON.stringify(observation) + "\n",
|
|
4354
4438
|
"utf8"
|
|
4355
4439
|
);
|
|
@@ -4366,16 +4450,16 @@ import { findProjectRoot as findProjectRoot11 } from "@hiveai/core";
|
|
|
4366
4450
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4367
4451
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4368
4452
|
import { findProjectRoot as findProjectRoot10, resolveHaivePaths as resolveHaivePaths8 } from "@hiveai/core";
|
|
4369
|
-
import { mkdir as
|
|
4370
|
-
import { existsSync as
|
|
4371
|
-
import
|
|
4453
|
+
import { mkdir as mkdir10, writeFile as writeFile10 } from "fs/promises";
|
|
4454
|
+
import { existsSync as existsSync15 } from "fs";
|
|
4455
|
+
import path15 from "path";
|
|
4372
4456
|
import { z } from "zod";
|
|
4373
|
-
import { readFile as
|
|
4457
|
+
import { readFile as readFile9, readdir as readdir3 } from "fs/promises";
|
|
4374
4458
|
import { existsSync as existsSync22 } from "fs";
|
|
4375
4459
|
import path22 from "path";
|
|
4376
4460
|
import { z as z2 } from "zod";
|
|
4377
4461
|
import { existsSync as existsSync32 } from "fs";
|
|
4378
|
-
import { loadMemoriesFromDir as
|
|
4462
|
+
import { loadMemoriesFromDir as loadMemoriesFromDir5 } from "@hiveai/core";
|
|
4379
4463
|
import { z as z3 } from "zod";
|
|
4380
4464
|
import { createHash } from "crypto";
|
|
4381
4465
|
import { mkdir as mkdir22, writeFile as writeFile22 } from "fs/promises";
|
|
@@ -4416,7 +4500,7 @@ import { z as z6 } from "zod";
|
|
|
4416
4500
|
import { writeFile as writeFile42 } from "fs/promises";
|
|
4417
4501
|
import { existsSync as existsSync72 } from "fs";
|
|
4418
4502
|
import {
|
|
4419
|
-
loadMemoriesFromDir as
|
|
4503
|
+
loadMemoriesFromDir as loadMemoriesFromDir52,
|
|
4420
4504
|
loadUsageIndex as loadUsageIndex22,
|
|
4421
4505
|
recordRejection,
|
|
4422
4506
|
saveUsageIndex,
|
|
@@ -4486,7 +4570,7 @@ import {
|
|
|
4486
4570
|
} from "@hiveai/core";
|
|
4487
4571
|
import { z as z14 } from "zod";
|
|
4488
4572
|
import { mkdir as mkdir32, writeFile as writeFile82 } from "fs/promises";
|
|
4489
|
-
import { existsSync as
|
|
4573
|
+
import { existsSync as existsSync152 } from "fs";
|
|
4490
4574
|
import path52 from "path";
|
|
4491
4575
|
import {
|
|
4492
4576
|
buildFrontmatter as buildFrontmatter22,
|
|
@@ -4507,7 +4591,7 @@ import {
|
|
|
4507
4591
|
serializeMemory as serializeMemory8
|
|
4508
4592
|
} from "@hiveai/core";
|
|
4509
4593
|
import { z as z16 } from "zod";
|
|
4510
|
-
import { mkdir as mkdir52, writeFile as
|
|
4594
|
+
import { mkdir as mkdir52, writeFile as writeFile102 } from "fs/promises";
|
|
4511
4595
|
import { existsSync as existsSync17 } from "fs";
|
|
4512
4596
|
import path72 from "path";
|
|
4513
4597
|
import {
|
|
@@ -4552,7 +4636,7 @@ import {
|
|
|
4552
4636
|
inferModulesFromPaths as inferModulesFromPaths2,
|
|
4553
4637
|
isAutoPromoteEligible,
|
|
4554
4638
|
isDecaying,
|
|
4555
|
-
isRetiredMemory,
|
|
4639
|
+
isRetiredMemory as isRetiredMemory2,
|
|
4556
4640
|
literalMatchesAllTokens as literalMatchesAllTokens22,
|
|
4557
4641
|
literalMatchesAnyToken as literalMatchesAnyToken22,
|
|
4558
4642
|
loadCodeMap as loadCodeMap5,
|
|
@@ -4617,7 +4701,7 @@ import {
|
|
|
4617
4701
|
deriveConfidence as deriveConfidence6,
|
|
4618
4702
|
diffHasDistinctiveOverlap,
|
|
4619
4703
|
getUsage as getUsage8,
|
|
4620
|
-
isRetiredMemory as
|
|
4704
|
+
isRetiredMemory as isRetiredMemory22,
|
|
4621
4705
|
loadMemoriesFromDir as loadMemoriesFromDir19,
|
|
4622
4706
|
loadUsageIndex as loadUsageIndex10,
|
|
4623
4707
|
literalMatchesAnyToken as literalMatchesAnyToken3,
|
|
@@ -4703,15 +4787,15 @@ var BootstrapProjectSaveInputSchema = {
|
|
|
4703
4787
|
overwrite: z.boolean().default(false).describe("Overwrite an existing file instead of failing")
|
|
4704
4788
|
};
|
|
4705
4789
|
async function bootstrapProjectSave(input, ctx) {
|
|
4706
|
-
const target = input.module ?
|
|
4707
|
-
const exists =
|
|
4790
|
+
const target = input.module ? path15.join(ctx.paths.modulesContextDir, input.module, "context.md") : ctx.paths.projectContext;
|
|
4791
|
+
const exists = existsSync15(target);
|
|
4708
4792
|
if (exists && !input.overwrite) {
|
|
4709
4793
|
throw new Error(
|
|
4710
4794
|
`${target} already exists. Pass overwrite=true to replace it.`
|
|
4711
4795
|
);
|
|
4712
4796
|
}
|
|
4713
|
-
await
|
|
4714
|
-
await
|
|
4797
|
+
await mkdir10(path15.dirname(target), { recursive: true });
|
|
4798
|
+
await writeFile10(target, input.content, "utf8");
|
|
4715
4799
|
return {
|
|
4716
4800
|
file_path: target,
|
|
4717
4801
|
action: exists ? "overwritten" : "created"
|
|
@@ -4724,14 +4808,14 @@ var GetProjectContextInputSchema = {
|
|
|
4724
4808
|
async function getProjectContext(input, ctx) {
|
|
4725
4809
|
const out = { root_context: null };
|
|
4726
4810
|
if (existsSync22(ctx.paths.projectContext)) {
|
|
4727
|
-
out.root_context = await
|
|
4811
|
+
out.root_context = await readFile9(ctx.paths.projectContext, "utf8");
|
|
4728
4812
|
}
|
|
4729
4813
|
if (input.module) {
|
|
4730
4814
|
const modFile = path22.join(ctx.paths.modulesContextDir, input.module, "context.md");
|
|
4731
4815
|
if (existsSync22(modFile)) {
|
|
4732
4816
|
out.module_context = {
|
|
4733
4817
|
name: input.module,
|
|
4734
|
-
content: await
|
|
4818
|
+
content: await readFile9(modFile, "utf8")
|
|
4735
4819
|
};
|
|
4736
4820
|
}
|
|
4737
4821
|
}
|
|
@@ -4758,7 +4842,7 @@ async function memList(input, ctx) {
|
|
|
4758
4842
|
if (!existsSync32(ctx.paths.memoriesDir)) {
|
|
4759
4843
|
return { memories: [] };
|
|
4760
4844
|
}
|
|
4761
|
-
const all = await
|
|
4845
|
+
const all = await loadMemoriesFromDir5(ctx.paths.memoriesDir);
|
|
4762
4846
|
const filtered = all.filter(({ memory: memory2 }) => {
|
|
4763
4847
|
const fm = memory2.frontmatter;
|
|
4764
4848
|
if (input.scope && fm.scope !== input.scope) return false;
|
|
@@ -5252,7 +5336,7 @@ async function memReject(input, ctx) {
|
|
|
5252
5336
|
if (!existsSync72(ctx.paths.memoriesDir)) {
|
|
5253
5337
|
throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
|
|
5254
5338
|
}
|
|
5255
|
-
const memories = await
|
|
5339
|
+
const memories = await loadMemoriesFromDir52(ctx.paths.memoriesDir);
|
|
5256
5340
|
const loaded = memories.find((m) => m.memory.frontmatter.id === input.id);
|
|
5257
5341
|
if (!loaded) throw new Error(`No memory with id "${input.id}".`);
|
|
5258
5342
|
await writeFile42(
|
|
@@ -5645,7 +5729,7 @@ var MemTriedInputSchema = {
|
|
|
5645
5729
|
author: z15.string().optional().describe("Author handle or email")
|
|
5646
5730
|
};
|
|
5647
5731
|
async function memTried(input, ctx) {
|
|
5648
|
-
if (!
|
|
5732
|
+
if (!existsSync152(ctx.paths.haiveDir)) {
|
|
5649
5733
|
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
|
|
5650
5734
|
}
|
|
5651
5735
|
const slug = input.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 5).join("-");
|
|
@@ -5671,7 +5755,7 @@ async function memTried(input, ctx) {
|
|
|
5671
5755
|
}
|
|
5672
5756
|
const file = memoryFilePath22(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
5673
5757
|
await mkdir32(path52.dirname(file), { recursive: true });
|
|
5674
|
-
if (
|
|
5758
|
+
if (existsSync152(file)) {
|
|
5675
5759
|
throw new Error(`Memory already exists at ${file}`);
|
|
5676
5760
|
}
|
|
5677
5761
|
await writeFile82(file, serializeMemory7({ frontmatter, body }), "utf8");
|
|
@@ -5805,7 +5889,7 @@ async function memObserve(input, ctx) {
|
|
|
5805
5889
|
if (existsSync17(file)) {
|
|
5806
5890
|
throw new Error(`Memory already exists at ${file}`);
|
|
5807
5891
|
}
|
|
5808
|
-
await
|
|
5892
|
+
await writeFile102(file, serializeMemory9({ frontmatter, body }), "utf8");
|
|
5809
5893
|
return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
|
|
5810
5894
|
}
|
|
5811
5895
|
function pendingDistillPath(ctx) {
|
|
@@ -6257,7 +6341,7 @@ async function getBriefing(input, ctx) {
|
|
|
6257
6341
|
const s = memory2.frontmatter.status;
|
|
6258
6342
|
if (s === "rejected" || s === "deprecated") return false;
|
|
6259
6343
|
if (!input.include_stale && s === "stale") return false;
|
|
6260
|
-
if (!input.include_stale &&
|
|
6344
|
+
if (!input.include_stale && isRetiredMemory2(memory2.frontmatter, memory2.body)) return false;
|
|
6261
6345
|
if (memory2.frontmatter.type === "session_recap") return false;
|
|
6262
6346
|
return true;
|
|
6263
6347
|
});
|
|
@@ -7125,7 +7209,7 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
7125
7209
|
const t = memory2.frontmatter.type;
|
|
7126
7210
|
if (t !== "attempt" && t !== "gotcha") return false;
|
|
7127
7211
|
const s = memory2.frontmatter.status;
|
|
7128
|
-
return s !== "rejected" && s !== "deprecated" && s !== "stale" && !
|
|
7212
|
+
return s !== "rejected" && s !== "deprecated" && s !== "stale" && !isRetiredMemory22(memory2.frontmatter, memory2.body);
|
|
7129
7213
|
});
|
|
7130
7214
|
if (negative.length === 0) {
|
|
7131
7215
|
return { scanned: 0, warnings: [], notice: "No attempt/gotcha memories found yet." };
|
|
@@ -7814,8 +7898,17 @@ function classifyWarning(warning, paths, anchoredBlocks = false) {
|
|
|
7814
7898
|
repair_command: repairCommand
|
|
7815
7899
|
};
|
|
7816
7900
|
}
|
|
7901
|
+
if (warning.has_sensor && !warning.reasons.includes("sensor") && !warning.reasons.includes("anchor")) {
|
|
7902
|
+
return {
|
|
7903
|
+
...warning,
|
|
7904
|
+
level: "info",
|
|
7905
|
+
rationale: "memory has a deterministic sensor that did not fire and is not anchored to a touched file \u2014 treated as non-violation noise",
|
|
7906
|
+
affected_files: affectedFiles,
|
|
7907
|
+
repair_command: repairCommand
|
|
7908
|
+
};
|
|
7909
|
+
}
|
|
7817
7910
|
const corroborated = warning.reasons.includes("anchor") || warning.distinctive_literal === true;
|
|
7818
|
-
const semanticReviewFloor = corroborated ? 0.45 : 0.
|
|
7911
|
+
const semanticReviewFloor = corroborated ? 0.45 : 0.65;
|
|
7819
7912
|
if (hasSemantic && semanticScore >= semanticReviewFloor || highConfidence && warning.reasons.includes("anchor") && warning.reasons.includes("literal")) {
|
|
7820
7913
|
return {
|
|
7821
7914
|
...warning,
|
|
@@ -8491,7 +8584,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
8491
8584
|
};
|
|
8492
8585
|
}
|
|
8493
8586
|
var SERVER_NAME = "haive";
|
|
8494
|
-
var SERVER_VERSION = "0.
|
|
8587
|
+
var SERVER_VERSION = "0.20.0";
|
|
8495
8588
|
function jsonResult(data) {
|
|
8496
8589
|
return {
|
|
8497
8590
|
content: [
|
|
@@ -9523,8 +9616,8 @@ function registerMcp(program2) {
|
|
|
9523
9616
|
|
|
9524
9617
|
// src/commands/sync.ts
|
|
9525
9618
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
9526
|
-
import { readFile as readFile10, writeFile as
|
|
9527
|
-
import { existsSync as
|
|
9619
|
+
import { readFile as readFile10, writeFile as writeFile15, mkdir as mkdir11 } from "fs/promises";
|
|
9620
|
+
import { existsSync as existsSync33 } from "fs";
|
|
9528
9621
|
import path16 from "path";
|
|
9529
9622
|
import "commander";
|
|
9530
9623
|
import {
|
|
@@ -9537,7 +9630,7 @@ import {
|
|
|
9537
9630
|
isStackPackSeed,
|
|
9538
9631
|
loadCodeMap as loadCodeMap6,
|
|
9539
9632
|
loadConfig as loadConfig4,
|
|
9540
|
-
loadMemoriesFromDir as
|
|
9633
|
+
loadMemoriesFromDir as loadMemoriesFromDir25,
|
|
9541
9634
|
loadUsageIndex as loadUsageIndex13,
|
|
9542
9635
|
pullCrossRepoSources,
|
|
9543
9636
|
resolveHaivePaths as resolveHaivePaths9,
|
|
@@ -9547,90 +9640,7 @@ import {
|
|
|
9547
9640
|
verifyAnchor as verifyAnchor2,
|
|
9548
9641
|
watchContracts
|
|
9549
9642
|
} from "@hiveai/core";
|
|
9550
|
-
import { BRIDGE_TARGETS } from "@hiveai/core";
|
|
9551
|
-
|
|
9552
|
-
// src/utils/bridge-files.ts
|
|
9553
|
-
import { mkdir as mkdir10, readFile as readFile9, writeFile as writeFile15 } from "fs/promises";
|
|
9554
|
-
import { existsSync as existsSync33 } from "fs";
|
|
9555
|
-
import path15 from "path";
|
|
9556
|
-
import {
|
|
9557
|
-
BRIDGE_MARKERS,
|
|
9558
|
-
generateBridges,
|
|
9559
|
-
isRetiredMemory as isRetiredMemory3,
|
|
9560
|
-
loadMemoriesFromDir as loadMemoriesFromDir25
|
|
9561
|
-
} from "@hiveai/core";
|
|
9562
|
-
async function writeBridgeFiles(root, paths, opts) {
|
|
9563
|
-
const result = { created: [], updated: [], unchanged: [] };
|
|
9564
|
-
if (!existsSync33(paths.memoriesDir)) return result;
|
|
9565
|
-
const allLoaded = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
9566
|
-
const memories = allLoaded.map((l) => l.memory).filter((m) => !isRetiredMemory3(m.frontmatter, m.body));
|
|
9567
|
-
const sensors = [];
|
|
9568
|
-
for (const m of memories) {
|
|
9569
|
-
const sensor = m.frontmatter.sensor;
|
|
9570
|
-
if (!sensor || sensor.severity !== "block") continue;
|
|
9571
|
-
sensors.push({
|
|
9572
|
-
id: m.frontmatter.id,
|
|
9573
|
-
severity: "block",
|
|
9574
|
-
message: sensor.message,
|
|
9575
|
-
...sensor.pattern ? { pattern: sensor.pattern } : {},
|
|
9576
|
-
paths: sensor.paths.length > 0 ? sensor.paths : m.frontmatter.anchor.paths
|
|
9577
|
-
});
|
|
9578
|
-
}
|
|
9579
|
-
const maxMemories = Math.max(1, opts.maxMemories ?? 8);
|
|
9580
|
-
const outputs = generateBridges(memories, sensors, { maxMemories, targets: opts.targets });
|
|
9581
|
-
for (const output of outputs) {
|
|
9582
|
-
const targetFile = path15.join(root, output.path);
|
|
9583
|
-
const fileExists = existsSync33(targetFile);
|
|
9584
|
-
if (opts.onlyExisting && !fileExists) continue;
|
|
9585
|
-
if (opts.dryRun) {
|
|
9586
|
-
(fileExists ? result.updated : result.created).push(output.path);
|
|
9587
|
-
continue;
|
|
9588
|
-
}
|
|
9589
|
-
await mkdir10(path15.dirname(targetFile), { recursive: true });
|
|
9590
|
-
if (!fileExists) {
|
|
9591
|
-
await writeFile15(targetFile, output.content, "utf8");
|
|
9592
|
-
result.created.push(output.path);
|
|
9593
|
-
continue;
|
|
9594
|
-
}
|
|
9595
|
-
let existing = (await readFile9(targetFile, "utf8")).replace(/\r\n/g, "\n");
|
|
9596
|
-
const withMemories = replaceMarkerBlock(
|
|
9597
|
-
existing,
|
|
9598
|
-
BRIDGE_MARKERS.memoriesStart,
|
|
9599
|
-
BRIDGE_MARKERS.memoriesEnd,
|
|
9600
|
-
extractMarkerBlock(output.content, BRIDGE_MARKERS.memoriesStart, BRIDGE_MARKERS.memoriesEnd)
|
|
9601
|
-
);
|
|
9602
|
-
const sensorsBlockContent = extractMarkerBlock(
|
|
9603
|
-
output.content,
|
|
9604
|
-
BRIDGE_MARKERS.sensorsStart,
|
|
9605
|
-
BRIDGE_MARKERS.sensorsEnd
|
|
9606
|
-
);
|
|
9607
|
-
const withSensors = sensorsBlockContent ? replaceMarkerBlock(withMemories, BRIDGE_MARKERS.sensorsStart, BRIDGE_MARKERS.sensorsEnd, sensorsBlockContent) : withMemories;
|
|
9608
|
-
if (withSensors === existing) {
|
|
9609
|
-
result.unchanged.push(output.path);
|
|
9610
|
-
continue;
|
|
9611
|
-
}
|
|
9612
|
-
await writeFile15(targetFile, withSensors, "utf8");
|
|
9613
|
-
result.updated.push(output.path);
|
|
9614
|
-
}
|
|
9615
|
-
return result;
|
|
9616
|
-
}
|
|
9617
|
-
function extractMarkerBlock(text, startMarker, endMarker) {
|
|
9618
|
-
const startIdx = text.indexOf(startMarker);
|
|
9619
|
-
const endIdx = text.indexOf(endMarker);
|
|
9620
|
-
if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) return null;
|
|
9621
|
-
return text.slice(startIdx, endIdx + endMarker.length);
|
|
9622
|
-
}
|
|
9623
|
-
function replaceMarkerBlock(existing, startMarker, endMarker, replacement) {
|
|
9624
|
-
if (!replacement) return existing;
|
|
9625
|
-
const startIdx = existing.indexOf(startMarker);
|
|
9626
|
-
const endIdx = existing.indexOf(endMarker);
|
|
9627
|
-
if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
|
|
9628
|
-
return existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + replacement + "\n";
|
|
9629
|
-
}
|
|
9630
|
-
return existing.slice(0, startIdx) + replacement + existing.slice(endIdx + endMarker.length);
|
|
9631
|
-
}
|
|
9632
|
-
|
|
9633
|
-
// src/commands/sync.ts
|
|
9643
|
+
import { BRIDGE_TARGETS as BRIDGE_TARGETS2 } from "@hiveai/core";
|
|
9634
9644
|
var BRIDGE_START = "<!-- haive:memories-start -->";
|
|
9635
9645
|
var BRIDGE_END = "<!-- haive:memories-end -->";
|
|
9636
9646
|
function registerSync(program2) {
|
|
@@ -9645,7 +9655,7 @@ function registerSync(program2) {
|
|
|
9645
9655
|
).option("--bridge-file <path>", "bridge file to inject into (default: CLAUDE.md)").option("--bridge-max-memories <n>", "max memories to inject into bridge file", "5").option("--embed", "rebuild embeddings index after sync (requires @haive/embeddings)").option("--no-cross-repo", "skip cross-repo memory pull even if crossRepoSources is configured").option("--no-deps", "skip dependency version tracking").option("--no-contracts", "skip contract file diff checking").option("--no-bridges", "skip auto-refresh of existing native agent bridge files (.cursor/rules, .clinerules, \u2026)").option("--dry-run", "report what would change without writing any files").action(async (opts) => {
|
|
9646
9656
|
const root = findProjectRoot12(opts.dir);
|
|
9647
9657
|
const paths = resolveHaivePaths9(root);
|
|
9648
|
-
if (!
|
|
9658
|
+
if (!existsSync33(paths.memoriesDir)) {
|
|
9649
9659
|
if (!opts.quiet) ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
9650
9660
|
process.exitCode = 1;
|
|
9651
9661
|
return;
|
|
@@ -9664,12 +9674,12 @@ function registerSync(program2) {
|
|
|
9664
9674
|
let promoted = 0;
|
|
9665
9675
|
let autoApproved = 0;
|
|
9666
9676
|
if (opts.verify !== false) {
|
|
9667
|
-
const memories = await
|
|
9677
|
+
const memories = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
9668
9678
|
for (const { memory: memory2, filePath } of memories) {
|
|
9669
9679
|
if (memory2.frontmatter.type === "session_recap") {
|
|
9670
9680
|
if (memory2.frontmatter.status === "stale") {
|
|
9671
9681
|
if (!dryRun) {
|
|
9672
|
-
await
|
|
9682
|
+
await writeFile15(
|
|
9673
9683
|
filePath,
|
|
9674
9684
|
serializeMemory13({
|
|
9675
9685
|
frontmatter: {
|
|
@@ -9694,7 +9704,7 @@ function registerSync(program2) {
|
|
|
9694
9704
|
if (result.stale) {
|
|
9695
9705
|
if (memory2.frontmatter.status !== "stale") {
|
|
9696
9706
|
if (!dryRun) {
|
|
9697
|
-
await
|
|
9707
|
+
await writeFile15(
|
|
9698
9708
|
filePath,
|
|
9699
9709
|
serializeMemory13({
|
|
9700
9710
|
frontmatter: {
|
|
@@ -9712,7 +9722,7 @@ function registerSync(program2) {
|
|
|
9712
9722
|
}
|
|
9713
9723
|
} else if (memory2.frontmatter.status === "stale") {
|
|
9714
9724
|
if (!dryRun) {
|
|
9715
|
-
await
|
|
9725
|
+
await writeFile15(
|
|
9716
9726
|
filePath,
|
|
9717
9727
|
serializeMemory13({
|
|
9718
9728
|
frontmatter: {
|
|
@@ -9731,7 +9741,7 @@ function registerSync(program2) {
|
|
|
9731
9741
|
}
|
|
9732
9742
|
}
|
|
9733
9743
|
if (opts.promote !== false) {
|
|
9734
|
-
const memories = await
|
|
9744
|
+
const memories = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
9735
9745
|
const usage = await loadUsageIndex13(paths);
|
|
9736
9746
|
const nowMs = Date.now();
|
|
9737
9747
|
for (const { memory: memory2, filePath } of memories) {
|
|
@@ -9742,7 +9752,7 @@ function registerSync(program2) {
|
|
|
9742
9752
|
maxRejections: DEFAULT_AUTO_PROMOTE_RULE2.maxRejections
|
|
9743
9753
|
})) {
|
|
9744
9754
|
if (!dryRun) {
|
|
9745
|
-
await
|
|
9755
|
+
await writeFile15(
|
|
9746
9756
|
filePath,
|
|
9747
9757
|
serializeMemory13({ frontmatter: { ...fm, status: "validated" }, body: memory2.body }),
|
|
9748
9758
|
"utf8"
|
|
@@ -9755,7 +9765,7 @@ function registerSync(program2) {
|
|
|
9755
9765
|
const ageHours = (nowMs - new Date(fm.created_at).getTime()) / (1e3 * 60 * 60);
|
|
9756
9766
|
if (ageHours >= autoApproveDelayHours) {
|
|
9757
9767
|
if (!dryRun) {
|
|
9758
|
-
await
|
|
9768
|
+
await writeFile15(
|
|
9759
9769
|
filePath,
|
|
9760
9770
|
serializeMemory13({
|
|
9761
9771
|
frontmatter: {
|
|
@@ -9783,7 +9793,7 @@ function registerSync(program2) {
|
|
|
9783
9793
|
for (const repair of repairs) log(ui.dim(`autopilot: ${repair.message}`));
|
|
9784
9794
|
}
|
|
9785
9795
|
const sinceReport = opts.since ? collectSinceChanges(root, opts.since) : null;
|
|
9786
|
-
const draftMemories = (await
|
|
9796
|
+
const draftMemories = (await loadMemoriesFromDir25(paths.memoriesDir)).filter(
|
|
9787
9797
|
(m) => m.memory.frontmatter.status === "draft"
|
|
9788
9798
|
);
|
|
9789
9799
|
const draftCount = draftMemories.length;
|
|
@@ -9806,14 +9816,14 @@ function registerSync(program2) {
|
|
|
9806
9816
|
} else {
|
|
9807
9817
|
const agentsMd = path16.join(root, "AGENTS.md");
|
|
9808
9818
|
bridgeTargets = [path16.join(root, "CLAUDE.md")];
|
|
9809
|
-
if (
|
|
9819
|
+
if (existsSync33(agentsMd)) bridgeTargets.push(agentsMd);
|
|
9810
9820
|
}
|
|
9811
9821
|
for (const bridgeFile of bridgeTargets) {
|
|
9812
9822
|
await injectBridge(bridgeFile, paths.memoriesDir, maxInject, root, opts.quiet);
|
|
9813
9823
|
}
|
|
9814
9824
|
}
|
|
9815
9825
|
if (opts.noBridges !== true) {
|
|
9816
|
-
const nativeTargets =
|
|
9826
|
+
const nativeTargets = BRIDGE_TARGETS2.filter(
|
|
9817
9827
|
(t) => t !== "claude" && t !== "agents"
|
|
9818
9828
|
);
|
|
9819
9829
|
try {
|
|
@@ -9849,7 +9859,7 @@ function registerSync(program2) {
|
|
|
9849
9859
|
}
|
|
9850
9860
|
}
|
|
9851
9861
|
if (!opts.quiet) {
|
|
9852
|
-
const allForDecay = await
|
|
9862
|
+
const allForDecay = await loadMemoriesFromDir25(paths.memoriesDir);
|
|
9853
9863
|
const usageForDecay = await loadUsageIndex13(paths);
|
|
9854
9864
|
const decaying = allForDecay.filter(({ memory: memory2 }) => {
|
|
9855
9865
|
const fm = memory2.frontmatter;
|
|
@@ -9939,7 +9949,7 @@ Wait for **explicit confirmation** before acting.
|
|
|
9939
9949
|
if (!dryRun) {
|
|
9940
9950
|
const teamDir = path16.join(paths.memoriesDir, "team");
|
|
9941
9951
|
await mkdir11(teamDir, { recursive: true });
|
|
9942
|
-
await
|
|
9952
|
+
await writeFile15(
|
|
9943
9953
|
path16.join(teamDir, `${fm.id}.md`),
|
|
9944
9954
|
serializeMemory13({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
9945
9955
|
"utf8"
|
|
@@ -10008,7 +10018,7 @@ Wait for **explicit confirmation** before acting.
|
|
|
10008
10018
|
if (!dryRun) {
|
|
10009
10019
|
const teamDir = path16.join(paths.memoriesDir, "team");
|
|
10010
10020
|
await mkdir11(teamDir, { recursive: true });
|
|
10011
|
-
await
|
|
10021
|
+
await writeFile15(
|
|
10012
10022
|
path16.join(teamDir, `${fm.id}.md`),
|
|
10013
10023
|
serializeMemory13({ frontmatter: { ...fm, requires_human_approval: true }, body }),
|
|
10014
10024
|
"utf8"
|
|
@@ -10100,8 +10110,8 @@ function bridgeSummaryLine(body) {
|
|
|
10100
10110
|
return oneLine.length > 140 ? oneLine.slice(0, 137) + "\u2026" : oneLine;
|
|
10101
10111
|
}
|
|
10102
10112
|
async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
|
|
10103
|
-
if (!
|
|
10104
|
-
const all = await
|
|
10113
|
+
if (!existsSync33(memoriesDir)) return;
|
|
10114
|
+
const all = await loadMemoriesFromDir25(memoriesDir);
|
|
10105
10115
|
const top = all.filter(({ memory: memory2 }) => {
|
|
10106
10116
|
const s = memory2.frontmatter.status;
|
|
10107
10117
|
if (memory2.frontmatter.type === "session_recap") return false;
|
|
@@ -10126,7 +10136,7 @@ async function injectBridge(bridgeFile, memoriesDir, maxMemories, root, quiet) {
|
|
|
10126
10136
|
` + block + `
|
|
10127
10137
|
|
|
10128
10138
|
${BRIDGE_END}`;
|
|
10129
|
-
const fileExists =
|
|
10139
|
+
const fileExists = existsSync33(bridgeFile);
|
|
10130
10140
|
let existing = fileExists ? await readFile10(bridgeFile, "utf8") : "";
|
|
10131
10141
|
existing = existing.replace(/\r\n/g, "\n");
|
|
10132
10142
|
const startIdx = existing.indexOf(BRIDGE_START);
|
|
@@ -10148,7 +10158,7 @@ ${BRIDGE_END}`;
|
|
|
10148
10158
|
}
|
|
10149
10159
|
updated = existing + (existing.endsWith("\n") ? "" : "\n") + "\n" + injected + "\n";
|
|
10150
10160
|
}
|
|
10151
|
-
await
|
|
10161
|
+
await writeFile15(bridgeFile, updated, "utf8");
|
|
10152
10162
|
if (!quiet) {
|
|
10153
10163
|
console.log(
|
|
10154
10164
|
ui.dim(`bridge: injected ${top.length} memor${top.length === 1 ? "y" : "ies"} into ${path16.relative(root, bridgeFile)}`)
|
|
@@ -10176,8 +10186,8 @@ function collectSinceChanges(root, ref) {
|
|
|
10176
10186
|
|
|
10177
10187
|
// src/commands/memory-add.ts
|
|
10178
10188
|
import { createHash as createHash2 } from "crypto";
|
|
10179
|
-
import { mkdir as mkdir12, readFile as readFile11, writeFile as
|
|
10180
|
-
import { existsSync as
|
|
10189
|
+
import { mkdir as mkdir12, readFile as readFile11, writeFile as writeFile16 } from "fs/promises";
|
|
10190
|
+
import { existsSync as existsSync34 } from "fs";
|
|
10181
10191
|
import path17 from "path";
|
|
10182
10192
|
import "commander";
|
|
10183
10193
|
import {
|
|
@@ -10185,7 +10195,7 @@ import {
|
|
|
10185
10195
|
findProjectRoot as findProjectRoot13,
|
|
10186
10196
|
inferModulesFromPaths as inferModulesFromPaths3,
|
|
10187
10197
|
loadConfig as loadConfig5,
|
|
10188
|
-
loadMemoriesFromDir as
|
|
10198
|
+
loadMemoriesFromDir as loadMemoriesFromDir26,
|
|
10189
10199
|
memoryFilePath as memoryFilePath7,
|
|
10190
10200
|
resolveHaivePaths as resolveHaivePaths10,
|
|
10191
10201
|
serializeMemory as serializeMemory14,
|
|
@@ -10219,7 +10229,7 @@ function registerMemoryAdd(memory2) {
|
|
|
10219
10229
|
).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("--activation-keyword <csv>", "skill only: comma-separated keywords that trigger progressive disclosure of this skill").option("--activation-glob <csv>", "skill only: comma-separated path globs that trigger this skill").option("--activation-always", "skill only: always surface this skill (no triggers needed)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10220
10230
|
const root = findProjectRoot13(opts.dir);
|
|
10221
10231
|
const paths = resolveHaivePaths10(root);
|
|
10222
|
-
if (!
|
|
10232
|
+
if (!existsSync34(paths.haiveDir)) {
|
|
10223
10233
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
10224
10234
|
process.exitCode = 1;
|
|
10225
10235
|
return;
|
|
@@ -10236,7 +10246,7 @@ function registerMemoryAdd(memory2) {
|
|
|
10236
10246
|
const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
|
|
10237
10247
|
const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
|
|
10238
10248
|
if (anchorPaths.length > 0) {
|
|
10239
|
-
const missing = anchorPaths.filter((p) => !
|
|
10249
|
+
const missing = anchorPaths.filter((p) => !existsSync34(path17.resolve(root, p)));
|
|
10240
10250
|
if (missing.length > 0) {
|
|
10241
10251
|
ui.warn(`Anchor path${missing.length > 1 ? "s" : ""} not found in project:`);
|
|
10242
10252
|
for (const p of missing) ui.warn(` \u2717 ${p}`);
|
|
@@ -10249,7 +10259,7 @@ function registerMemoryAdd(memory2) {
|
|
|
10249
10259
|
const slug = slugify(opts.slug ?? opts.title ?? opts.topic ?? opts.body ?? `${opts.type}-memory`);
|
|
10250
10260
|
let body;
|
|
10251
10261
|
if (opts.bodyFile !== void 0) {
|
|
10252
|
-
if (!
|
|
10262
|
+
if (!existsSync34(opts.bodyFile)) {
|
|
10253
10263
|
ui.error(`--body-file not found: ${opts.bodyFile}`);
|
|
10254
10264
|
process.exitCode = 1;
|
|
10255
10265
|
return;
|
|
@@ -10265,9 +10275,9 @@ TODO \u2014 write the memory body.
|
|
|
10265
10275
|
`;
|
|
10266
10276
|
}
|
|
10267
10277
|
const scope = opts.scope ?? config.defaultScope ?? "personal";
|
|
10268
|
-
if (
|
|
10278
|
+
if (existsSync34(paths.memoriesDir)) {
|
|
10269
10279
|
const incomingHash = createHash2("sha256").update(body.trim()).digest("hex").slice(0, 12);
|
|
10270
|
-
const allForHash = await
|
|
10280
|
+
const allForHash = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
10271
10281
|
const hashDup = allForHash.find(
|
|
10272
10282
|
({ memory: memory3 }) => createHash2("sha256").update(memory3.body.trim()).digest("hex").slice(0, 12) === incomingHash && memory3.frontmatter.scope === scope
|
|
10273
10283
|
);
|
|
@@ -10278,8 +10288,8 @@ TODO \u2014 write the memory body.
|
|
|
10278
10288
|
return;
|
|
10279
10289
|
}
|
|
10280
10290
|
}
|
|
10281
|
-
if (opts.topic &&
|
|
10282
|
-
const existing = await
|
|
10291
|
+
if (opts.topic && existsSync34(paths.memoriesDir)) {
|
|
10292
|
+
const existing = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
10283
10293
|
const topicMatch = existing.find(
|
|
10284
10294
|
({ memory: memory3 }) => memory3.frontmatter.topic === opts.topic && memory3.frontmatter.scope === scope && (!opts.module || memory3.frontmatter.module === opts.module)
|
|
10285
10295
|
);
|
|
@@ -10299,7 +10309,7 @@ TODO \u2014 write the memory body.
|
|
|
10299
10309
|
};
|
|
10300
10310
|
const suggestedSensor = !newFrontmatter.sensor ? suggestSensorForCliMemory(opts.type, body, newFrontmatter.anchor.paths) : null;
|
|
10301
10311
|
if (suggestedSensor) newFrontmatter.sensor = suggestedSensor;
|
|
10302
|
-
await
|
|
10312
|
+
await writeFile16(topicMatch.filePath, serializeMemory14({ frontmatter: newFrontmatter, body }), "utf8");
|
|
10303
10313
|
ui.success(`Updated (topic upsert) ${path17.relative(root, topicMatch.filePath)}`);
|
|
10304
10314
|
ui.info(`id=${fm.id} revision=${revisionCount}`);
|
|
10305
10315
|
if (suggestedSensor) ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(suggestedSensor.pattern)}`);
|
|
@@ -10325,13 +10335,13 @@ TODO \u2014 write the memory body.
|
|
|
10325
10335
|
});
|
|
10326
10336
|
const file = memoryFilePath7(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
10327
10337
|
await mkdir12(path17.dirname(file), { recursive: true });
|
|
10328
|
-
if (
|
|
10338
|
+
if (existsSync34(file)) {
|
|
10329
10339
|
ui.error(`Memory already exists at ${file}`);
|
|
10330
10340
|
process.exitCode = 1;
|
|
10331
10341
|
return;
|
|
10332
10342
|
}
|
|
10333
|
-
if (
|
|
10334
|
-
const existing = await
|
|
10343
|
+
if (existsSync34(paths.memoriesDir)) {
|
|
10344
|
+
const existing = await loadMemoriesFromDir26(paths.memoriesDir);
|
|
10335
10345
|
const slugTokens = slug.toLowerCase().split(/[-_\s]+/).filter(Boolean);
|
|
10336
10346
|
const similar = existing.filter(({ memory: memory3 }) => {
|
|
10337
10347
|
const id = memory3.frontmatter.id.toLowerCase();
|
|
@@ -10342,7 +10352,7 @@ TODO \u2014 write the memory body.
|
|
|
10342
10352
|
ui.warn("Consider updating one of these with `haive memory update` instead.");
|
|
10343
10353
|
}
|
|
10344
10354
|
}
|
|
10345
|
-
await
|
|
10355
|
+
await writeFile16(file, serializeMemory14({ frontmatter, body }), "utf8");
|
|
10346
10356
|
ui.success(`Created ${path17.relative(root, file)}`);
|
|
10347
10357
|
ui.info(`id=${frontmatter.id} scope=${frontmatter.scope} status=${frontmatter.status}`);
|
|
10348
10358
|
if (frontmatter.sensor?.autogen) {
|
|
@@ -10422,14 +10432,14 @@ function slugify(value) {
|
|
|
10422
10432
|
}
|
|
10423
10433
|
|
|
10424
10434
|
// src/commands/memory-list.ts
|
|
10425
|
-
import { existsSync as
|
|
10435
|
+
import { existsSync as existsSync35 } from "fs";
|
|
10426
10436
|
import path18 from "path";
|
|
10427
10437
|
import "commander";
|
|
10428
10438
|
import { findProjectRoot as findProjectRoot14, resolveHaivePaths as resolveHaivePaths11 } from "@hiveai/core";
|
|
10429
10439
|
|
|
10430
10440
|
// src/utils/fs.ts
|
|
10431
10441
|
import {
|
|
10432
|
-
loadMemoriesFromDir as
|
|
10442
|
+
loadMemoriesFromDir as loadMemoriesFromDir27,
|
|
10433
10443
|
loadMemory,
|
|
10434
10444
|
listMarkdownFilesRecursive
|
|
10435
10445
|
} from "@hiveai/core";
|
|
@@ -10439,12 +10449,12 @@ function registerMemoryList(memory2) {
|
|
|
10439
10449
|
memory2.command("list").description("List memories with optional filters").option("--scope <scope>", "personal | team | module").option("--type <type>", "filter by type").option("--tag <tag>", "filter by tag").option("--module <name>", "filter by module name").option("--status <csv>", "filter by status (draft,proposed,validated,stale,rejected,deprecated)").option("--show-rejected", "include rejected memories (hidden by default)").option("--limit <n>", "max memories to display").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10440
10450
|
const root = findProjectRoot14(opts.dir);
|
|
10441
10451
|
const paths = resolveHaivePaths11(root);
|
|
10442
|
-
if (!
|
|
10452
|
+
if (!existsSync35(paths.memoriesDir)) {
|
|
10443
10453
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
10444
10454
|
process.exitCode = 1;
|
|
10445
10455
|
return;
|
|
10446
10456
|
}
|
|
10447
|
-
const all = await
|
|
10457
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10448
10458
|
const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
|
|
10449
10459
|
const limit = opts.limit ? Math.max(1, parseInt(opts.limit, 10)) : void 0;
|
|
10450
10460
|
const filtered = all.filter((m) => {
|
|
@@ -10513,8 +10523,8 @@ function matchesFilters(loaded, opts) {
|
|
|
10513
10523
|
}
|
|
10514
10524
|
|
|
10515
10525
|
// src/commands/memory-promote.ts
|
|
10516
|
-
import { mkdir as mkdir13, unlink as unlink2, writeFile as
|
|
10517
|
-
import { existsSync as
|
|
10526
|
+
import { mkdir as mkdir13, unlink as unlink2, writeFile as writeFile17 } from "fs/promises";
|
|
10527
|
+
import { existsSync as existsSync36 } from "fs";
|
|
10518
10528
|
import path19 from "path";
|
|
10519
10529
|
import "commander";
|
|
10520
10530
|
import {
|
|
@@ -10527,12 +10537,12 @@ function registerMemoryPromote(memory2) {
|
|
|
10527
10537
|
memory2.command("promote <id>").description("Promote a personal memory to team scope (status -> proposed)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
10528
10538
|
const root = findProjectRoot15(opts.dir);
|
|
10529
10539
|
const paths = resolveHaivePaths12(root);
|
|
10530
|
-
if (!
|
|
10540
|
+
if (!existsSync36(paths.memoriesDir)) {
|
|
10531
10541
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
10532
10542
|
process.exitCode = 1;
|
|
10533
10543
|
return;
|
|
10534
10544
|
}
|
|
10535
|
-
const teamAndModule = await
|
|
10545
|
+
const teamAndModule = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10536
10546
|
const alreadyShared = teamAndModule.find(
|
|
10537
10547
|
(m) => m.memory.frontmatter.id === id && (m.memory.frontmatter.scope === "team" || m.memory.frontmatter.scope === "module")
|
|
10538
10548
|
);
|
|
@@ -10546,7 +10556,7 @@ function registerMemoryPromote(memory2) {
|
|
|
10546
10556
|
}
|
|
10547
10557
|
return;
|
|
10548
10558
|
}
|
|
10549
|
-
const all = await
|
|
10559
|
+
const all = await loadMemoriesFromDir27(paths.personalDir);
|
|
10550
10560
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
10551
10561
|
if (!found) {
|
|
10552
10562
|
ui.error(`No personal memory with id "${id}". (Promotion only applies to personal scope.)`);
|
|
@@ -10563,7 +10573,7 @@ function registerMemoryPromote(memory2) {
|
|
|
10563
10573
|
};
|
|
10564
10574
|
const newPath = memoryFilePath8(paths, "team", updated.frontmatter.id);
|
|
10565
10575
|
await mkdir13(path19.dirname(newPath), { recursive: true });
|
|
10566
|
-
await
|
|
10576
|
+
await writeFile17(newPath, serializeMemory15(updated), "utf8");
|
|
10567
10577
|
await unlink2(found.filePath);
|
|
10568
10578
|
ui.success(`Promoted ${id} to team scope (status=proposed)`);
|
|
10569
10579
|
ui.info(`Now at ${path19.relative(root, newPath)}`);
|
|
@@ -10572,8 +10582,8 @@ function registerMemoryPromote(memory2) {
|
|
|
10572
10582
|
}
|
|
10573
10583
|
|
|
10574
10584
|
// src/commands/memory-approve.ts
|
|
10575
|
-
import { existsSync as
|
|
10576
|
-
import { writeFile as
|
|
10585
|
+
import { existsSync as existsSync37 } from "fs";
|
|
10586
|
+
import { writeFile as writeFile18 } from "fs/promises";
|
|
10577
10587
|
import path20 from "path";
|
|
10578
10588
|
import "commander";
|
|
10579
10589
|
import {
|
|
@@ -10585,12 +10595,12 @@ function registerMemoryApprove(memory2) {
|
|
|
10585
10595
|
memory2.command("approve [id]").description("Mark a memory as 'validated'. Use --all to bulk-approve all proposed/draft memories.").option("--all", "approve all proposed and draft memories at once").option("--pending", "approve all memories with status 'proposed'").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
10586
10596
|
const root = findProjectRoot16(opts.dir);
|
|
10587
10597
|
const paths = resolveHaivePaths13(root);
|
|
10588
|
-
if (!
|
|
10598
|
+
if (!existsSync37(paths.memoriesDir)) {
|
|
10589
10599
|
ui.error(`No .ai/memories at ${root}.`);
|
|
10590
10600
|
process.exitCode = 1;
|
|
10591
10601
|
return;
|
|
10592
10602
|
}
|
|
10593
|
-
const all = await
|
|
10603
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10594
10604
|
if (opts.all || opts.pending) {
|
|
10595
10605
|
const candidates = all.filter((m) => {
|
|
10596
10606
|
const s = m.memory.frontmatter.status;
|
|
@@ -10607,7 +10617,7 @@ function registerMemoryApprove(memory2) {
|
|
|
10607
10617
|
frontmatter: { ...found2.memory.frontmatter, status: "validated" },
|
|
10608
10618
|
body: found2.memory.body
|
|
10609
10619
|
};
|
|
10610
|
-
await
|
|
10620
|
+
await writeFile18(found2.filePath, serializeMemory16(next2), "utf8");
|
|
10611
10621
|
count++;
|
|
10612
10622
|
}
|
|
10613
10623
|
ui.success(`Approved ${count} memor${count === 1 ? "y" : "ies"} (status=validated)`);
|
|
@@ -10636,15 +10646,15 @@ function registerMemoryApprove(memory2) {
|
|
|
10636
10646
|
frontmatter: { ...found.memory.frontmatter, status: "validated" },
|
|
10637
10647
|
body: found.memory.body
|
|
10638
10648
|
};
|
|
10639
|
-
await
|
|
10649
|
+
await writeFile18(found.filePath, serializeMemory16(next), "utf8");
|
|
10640
10650
|
ui.success(`Approved ${id} (status=validated)`);
|
|
10641
10651
|
ui.info(path20.relative(root, found.filePath));
|
|
10642
10652
|
});
|
|
10643
10653
|
}
|
|
10644
10654
|
|
|
10645
10655
|
// src/commands/memory-update.ts
|
|
10646
|
-
import { readFile as readFile12, writeFile as
|
|
10647
|
-
import { existsSync as
|
|
10656
|
+
import { readFile as readFile12, writeFile as writeFile19 } from "fs/promises";
|
|
10657
|
+
import { existsSync as existsSync38 } from "fs";
|
|
10648
10658
|
import path21 from "path";
|
|
10649
10659
|
import "commander";
|
|
10650
10660
|
import {
|
|
@@ -10656,12 +10666,12 @@ function registerMemoryUpdate(memory2) {
|
|
|
10656
10666
|
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) => {
|
|
10657
10667
|
const root = findProjectRoot17(opts.dir);
|
|
10658
10668
|
const paths = resolveHaivePaths14(root);
|
|
10659
|
-
if (!
|
|
10669
|
+
if (!existsSync38(paths.memoriesDir)) {
|
|
10660
10670
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
10661
10671
|
process.exitCode = 1;
|
|
10662
10672
|
return;
|
|
10663
10673
|
}
|
|
10664
|
-
const memories = await
|
|
10674
|
+
const memories = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10665
10675
|
const loaded = memories.find((m) => m.memory.frontmatter.id === id);
|
|
10666
10676
|
if (!loaded) {
|
|
10667
10677
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -10698,7 +10708,7 @@ function registerMemoryUpdate(memory2) {
|
|
|
10698
10708
|
if (opts.author !== void 0) updated.push("author");
|
|
10699
10709
|
let newBody;
|
|
10700
10710
|
if (opts.bodyFile !== void 0) {
|
|
10701
|
-
if (!
|
|
10711
|
+
if (!existsSync38(opts.bodyFile)) {
|
|
10702
10712
|
ui.error(`--body-file not found: ${opts.bodyFile}`);
|
|
10703
10713
|
process.exitCode = 1;
|
|
10704
10714
|
return;
|
|
@@ -10719,7 +10729,7 @@ function registerMemoryUpdate(memory2) {
|
|
|
10719
10729
|
ui.warn("Nothing to update \u2014 provide at least one option.");
|
|
10720
10730
|
return;
|
|
10721
10731
|
}
|
|
10722
|
-
await
|
|
10732
|
+
await writeFile19(
|
|
10723
10733
|
loaded.filePath,
|
|
10724
10734
|
serializeMemory17({ frontmatter: newFrontmatter, body: newBody }),
|
|
10725
10735
|
"utf8"
|
|
@@ -10743,8 +10753,8 @@ function parseCsv3(value) {
|
|
|
10743
10753
|
}
|
|
10744
10754
|
|
|
10745
10755
|
// src/commands/memory-auto-promote.ts
|
|
10746
|
-
import { writeFile as
|
|
10747
|
-
import { existsSync as
|
|
10756
|
+
import { writeFile as writeFile20 } from "fs/promises";
|
|
10757
|
+
import { existsSync as existsSync39 } from "fs";
|
|
10748
10758
|
import path23 from "path";
|
|
10749
10759
|
import "commander";
|
|
10750
10760
|
import {
|
|
@@ -10764,7 +10774,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
10764
10774
|
).option("--apply", "actually write status=validated to disk (default: dry-run)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
10765
10775
|
const root = findProjectRoot18(opts.dir);
|
|
10766
10776
|
const paths = resolveHaivePaths15(root);
|
|
10767
|
-
if (!
|
|
10777
|
+
if (!existsSync39(paths.memoriesDir)) {
|
|
10768
10778
|
ui.error(`No .ai/memories at ${root}.`);
|
|
10769
10779
|
process.exitCode = 1;
|
|
10770
10780
|
return;
|
|
@@ -10773,7 +10783,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
10773
10783
|
minReads: Number(opts.minReads ?? DEFAULT_AUTO_PROMOTE_RULE3.minReads),
|
|
10774
10784
|
maxRejections: Number(opts.maxRejections ?? DEFAULT_AUTO_PROMOTE_RULE3.maxRejections)
|
|
10775
10785
|
};
|
|
10776
|
-
const memories = await
|
|
10786
|
+
const memories = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10777
10787
|
const usage = await loadUsageIndex14(paths);
|
|
10778
10788
|
const eligible = memories.filter(
|
|
10779
10789
|
({ memory: memory3 }) => isAutoPromoteEligible3(memory3.frontmatter, getUsage12(usage, memory3.frontmatter.id), rule)
|
|
@@ -10796,7 +10806,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
10796
10806
|
frontmatter: { ...mem.frontmatter, status: "validated" },
|
|
10797
10807
|
body: mem.body
|
|
10798
10808
|
};
|
|
10799
|
-
await
|
|
10809
|
+
await writeFile20(filePath, serializeMemory18(next), "utf8");
|
|
10800
10810
|
written++;
|
|
10801
10811
|
}
|
|
10802
10812
|
}
|
|
@@ -10807,7 +10817,7 @@ function registerMemoryAutoPromote(memory2) {
|
|
|
10807
10817
|
|
|
10808
10818
|
// src/commands/memory-edit.ts
|
|
10809
10819
|
import { spawn as spawn3 } from "child_process";
|
|
10810
|
-
import { existsSync as
|
|
10820
|
+
import { existsSync as existsSync40 } from "fs";
|
|
10811
10821
|
import { readFile as readFile13 } from "fs/promises";
|
|
10812
10822
|
import path24 from "path";
|
|
10813
10823
|
import "commander";
|
|
@@ -10820,12 +10830,12 @@ function registerMemoryEdit(memory2) {
|
|
|
10820
10830
|
memory2.command("edit <id>").description("Open a memory in $EDITOR and re-validate when you save").option("-e, --editor <cmd>", "editor command (defaults to $EDITOR or 'vi')").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
10821
10831
|
const root = findProjectRoot19(opts.dir);
|
|
10822
10832
|
const paths = resolveHaivePaths16(root);
|
|
10823
|
-
if (!
|
|
10833
|
+
if (!existsSync40(paths.memoriesDir)) {
|
|
10824
10834
|
ui.error(`No .ai/memories at ${root}.`);
|
|
10825
10835
|
process.exitCode = 1;
|
|
10826
10836
|
return;
|
|
10827
10837
|
}
|
|
10828
|
-
const all = await
|
|
10838
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10829
10839
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
10830
10840
|
if (!found) {
|
|
10831
10841
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -10860,7 +10870,7 @@ function runEditor(editor, file) {
|
|
|
10860
10870
|
}
|
|
10861
10871
|
|
|
10862
10872
|
// src/commands/memory-for-files.ts
|
|
10863
|
-
import { existsSync as
|
|
10873
|
+
import { existsSync as existsSync41 } from "fs";
|
|
10864
10874
|
import path25 from "path";
|
|
10865
10875
|
import "commander";
|
|
10866
10876
|
import {
|
|
@@ -10876,12 +10886,12 @@ function registerMemoryForFiles(memory2) {
|
|
|
10876
10886
|
memory2.command("for-files <files...>").description("Show memories relevant to the given files (anchor overlap, module, domain)").option("-d, --dir <dir>", "project root").action(async (files, opts) => {
|
|
10877
10887
|
const root = findProjectRoot20(opts.dir);
|
|
10878
10888
|
const paths = resolveHaivePaths17(root);
|
|
10879
|
-
if (!
|
|
10889
|
+
if (!existsSync41(paths.memoriesDir)) {
|
|
10880
10890
|
ui.error(`No .ai/memories at ${root}.`);
|
|
10881
10891
|
process.exitCode = 1;
|
|
10882
10892
|
return;
|
|
10883
10893
|
}
|
|
10884
|
-
const all = await
|
|
10894
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
10885
10895
|
const usage = await loadUsageIndex15(paths);
|
|
10886
10896
|
const inferred = inferModulesFromPaths4(files);
|
|
10887
10897
|
const byAnchor = [];
|
|
@@ -10988,7 +10998,7 @@ function printGroup(root, label, loaded, usage) {
|
|
|
10988
10998
|
}
|
|
10989
10999
|
|
|
10990
11000
|
// src/commands/memory-hot.ts
|
|
10991
|
-
import { existsSync as
|
|
11001
|
+
import { existsSync as existsSync43 } from "fs";
|
|
10992
11002
|
import path26 from "path";
|
|
10993
11003
|
import "commander";
|
|
10994
11004
|
import {
|
|
@@ -11003,13 +11013,13 @@ function registerMemoryHot(memory2) {
|
|
|
11003
11013
|
).option("--threshold <n>", "minimum read_count to qualify (default: 3)", "3").option("--status <status>", "limit to one status (default: draft + proposed)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11004
11014
|
const root = findProjectRoot21(opts.dir);
|
|
11005
11015
|
const paths = resolveHaivePaths18(root);
|
|
11006
|
-
if (!
|
|
11016
|
+
if (!existsSync43(paths.memoriesDir)) {
|
|
11007
11017
|
ui.error(`No .ai/memories at ${root}.`);
|
|
11008
11018
|
process.exitCode = 1;
|
|
11009
11019
|
return;
|
|
11010
11020
|
}
|
|
11011
11021
|
const threshold = Math.max(1, Number(opts.threshold ?? 3));
|
|
11012
|
-
const all = await
|
|
11022
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
11013
11023
|
const usage = await loadUsageIndex16(paths);
|
|
11014
11024
|
const candidates = all.filter(({ memory: mem }) => {
|
|
11015
11025
|
const fm = mem.frontmatter;
|
|
@@ -11041,8 +11051,8 @@ function registerMemoryHot(memory2) {
|
|
|
11041
11051
|
}
|
|
11042
11052
|
|
|
11043
11053
|
// src/commands/memory-tried.ts
|
|
11044
|
-
import { mkdir as mkdir14, writeFile as
|
|
11045
|
-
import { existsSync as
|
|
11054
|
+
import { mkdir as mkdir14, writeFile as writeFile21 } from "fs/promises";
|
|
11055
|
+
import { existsSync as existsSync44 } from "fs";
|
|
11046
11056
|
import path27 from "path";
|
|
11047
11057
|
import "commander";
|
|
11048
11058
|
import {
|
|
@@ -11072,7 +11082,7 @@ function registerMemoryTried(memory2) {
|
|
|
11072
11082
|
).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) => {
|
|
11073
11083
|
const root = findProjectRoot22(opts.dir);
|
|
11074
11084
|
const paths = resolveHaivePaths19(root);
|
|
11075
|
-
if (!
|
|
11085
|
+
if (!existsSync44(paths.haiveDir)) {
|
|
11076
11086
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
11077
11087
|
process.exitCode = 1;
|
|
11078
11088
|
return;
|
|
@@ -11100,12 +11110,12 @@ function registerMemoryTried(memory2) {
|
|
|
11100
11110
|
}
|
|
11101
11111
|
const file = memoryFilePath9(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
11102
11112
|
await mkdir14(path27.dirname(file), { recursive: true });
|
|
11103
|
-
if (
|
|
11113
|
+
if (existsSync44(file)) {
|
|
11104
11114
|
ui.error(`Memory already exists at ${file}`);
|
|
11105
11115
|
process.exitCode = 1;
|
|
11106
11116
|
return;
|
|
11107
11117
|
}
|
|
11108
|
-
await
|
|
11118
|
+
await writeFile21(file, serializeMemory19({ frontmatter, body }), "utf8");
|
|
11109
11119
|
ui.success(`Recorded: ${path27.relative(root, file)}`);
|
|
11110
11120
|
ui.info(`id=${frontmatter.id} type=attempt status=validated (auto-approved)`);
|
|
11111
11121
|
if (sensor) ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(sensor.pattern)}`);
|
|
@@ -11118,7 +11128,7 @@ function parseCsv4(value) {
|
|
|
11118
11128
|
|
|
11119
11129
|
// src/commands/memory-seed.ts
|
|
11120
11130
|
import { readFile as readFile14 } from "fs/promises";
|
|
11121
|
-
import { existsSync as
|
|
11131
|
+
import { existsSync as existsSync45 } from "fs";
|
|
11122
11132
|
import path28 from "path";
|
|
11123
11133
|
import "commander";
|
|
11124
11134
|
import {
|
|
@@ -11158,7 +11168,7 @@ function registerMemorySeed(memory2) {
|
|
|
11158
11168
|
}
|
|
11159
11169
|
return;
|
|
11160
11170
|
}
|
|
11161
|
-
if (!
|
|
11171
|
+
if (!existsSync45(paths.haiveDir)) {
|
|
11162
11172
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
11163
11173
|
process.exitCode = 1;
|
|
11164
11174
|
return;
|
|
@@ -11214,7 +11224,7 @@ function registerMemorySeed(memory2) {
|
|
|
11214
11224
|
}
|
|
11215
11225
|
|
|
11216
11226
|
// src/commands/memory-pending.ts
|
|
11217
|
-
import { existsSync as
|
|
11227
|
+
import { existsSync as existsSync46 } from "fs";
|
|
11218
11228
|
import path29 from "path";
|
|
11219
11229
|
import "commander";
|
|
11220
11230
|
import {
|
|
@@ -11227,12 +11237,12 @@ function registerMemoryPending(memory2) {
|
|
|
11227
11237
|
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) => {
|
|
11228
11238
|
const root = findProjectRoot24(opts.dir);
|
|
11229
11239
|
const paths = resolveHaivePaths21(root);
|
|
11230
|
-
if (!
|
|
11240
|
+
if (!existsSync46(paths.memoriesDir)) {
|
|
11231
11241
|
ui.error(`No .ai/memories at ${root}.`);
|
|
11232
11242
|
process.exitCode = 1;
|
|
11233
11243
|
return;
|
|
11234
11244
|
}
|
|
11235
|
-
const all = await
|
|
11245
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
11236
11246
|
const usage = await loadUsageIndex17(paths);
|
|
11237
11247
|
const filterFn = ({ memory: mem }) => {
|
|
11238
11248
|
if (mem.frontmatter.status !== "proposed" && mem.frontmatter.status !== "draft") return false;
|
|
@@ -11285,7 +11295,7 @@ function registerMemoryPending(memory2) {
|
|
|
11285
11295
|
}
|
|
11286
11296
|
|
|
11287
11297
|
// src/commands/memory-query.ts
|
|
11288
|
-
import { existsSync as
|
|
11298
|
+
import { existsSync as existsSync47 } from "fs";
|
|
11289
11299
|
import path30 from "path";
|
|
11290
11300
|
import "commander";
|
|
11291
11301
|
import {
|
|
@@ -11302,7 +11312,7 @@ function registerMemoryQuery(memory2) {
|
|
|
11302
11312
|
memory2.command("search <text>").alias("query").description("Search memories by id, tag, or substring (AND, OR fallback). Mirrors MCP mem_search. Alias: query").option("-d, --dir <dir>", "project root").option("--limit <n>", "max results", "20").option("--scope <scope>", "personal | team | module").option("--status <csv>", "filter by status (draft,proposed,validated,stale,rejected)").option("--show-rejected", "include rejected memories (hidden by default)").action(async (text, opts) => {
|
|
11303
11313
|
const root = findProjectRoot25(opts.dir);
|
|
11304
11314
|
const paths = resolveHaivePaths22(root);
|
|
11305
|
-
if (!
|
|
11315
|
+
if (!existsSync47(paths.memoriesDir)) {
|
|
11306
11316
|
ui.error(`No memories directory at ${paths.memoriesDir}. Run \`haive init\` first.`);
|
|
11307
11317
|
process.exitCode = 1;
|
|
11308
11318
|
return;
|
|
@@ -11313,7 +11323,7 @@ function registerMemoryQuery(memory2) {
|
|
|
11313
11323
|
return;
|
|
11314
11324
|
}
|
|
11315
11325
|
const statusFilter = opts.status ? opts.status.split(",").map((s) => s.trim()) : null;
|
|
11316
|
-
const all = await
|
|
11326
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
11317
11327
|
const passesFilters2 = (mem) => {
|
|
11318
11328
|
const fm = mem.frontmatter;
|
|
11319
11329
|
if (opts.scope && fm.scope !== opts.scope) return false;
|
|
@@ -11360,8 +11370,8 @@ ${top.length} of ${matches.length} match${matches.length === 1 ? "" : "es"}`)
|
|
|
11360
11370
|
}
|
|
11361
11371
|
|
|
11362
11372
|
// src/commands/memory-reject.ts
|
|
11363
|
-
import { writeFile as
|
|
11364
|
-
import { existsSync as
|
|
11373
|
+
import { writeFile as writeFile23 } from "fs/promises";
|
|
11374
|
+
import { existsSync as existsSync48 } from "fs";
|
|
11365
11375
|
import "commander";
|
|
11366
11376
|
import {
|
|
11367
11377
|
findProjectRoot as findProjectRoot26,
|
|
@@ -11375,19 +11385,19 @@ function registerMemoryReject(memory2) {
|
|
|
11375
11385
|
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) => {
|
|
11376
11386
|
const root = findProjectRoot26(opts.dir);
|
|
11377
11387
|
const paths = resolveHaivePaths23(root);
|
|
11378
|
-
if (!
|
|
11388
|
+
if (!existsSync48(paths.memoriesDir)) {
|
|
11379
11389
|
ui.error(`No .ai/memories at ${root}.`);
|
|
11380
11390
|
process.exitCode = 1;
|
|
11381
11391
|
return;
|
|
11382
11392
|
}
|
|
11383
|
-
const memories = await
|
|
11393
|
+
const memories = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
11384
11394
|
const loaded = memories.find((m) => m.memory.frontmatter.id === id);
|
|
11385
11395
|
if (!loaded) {
|
|
11386
11396
|
ui.error(`No memory with id "${id}".`);
|
|
11387
11397
|
process.exitCode = 1;
|
|
11388
11398
|
return;
|
|
11389
11399
|
}
|
|
11390
|
-
await
|
|
11400
|
+
await writeFile23(
|
|
11391
11401
|
loaded.filePath,
|
|
11392
11402
|
serializeMemory20({
|
|
11393
11403
|
frontmatter: {
|
|
@@ -11411,7 +11421,7 @@ function registerMemoryReject(memory2) {
|
|
|
11411
11421
|
}
|
|
11412
11422
|
|
|
11413
11423
|
// src/commands/memory-rm.ts
|
|
11414
|
-
import { existsSync as
|
|
11424
|
+
import { existsSync as existsSync49 } from "fs";
|
|
11415
11425
|
import { unlink as unlink3 } from "fs/promises";
|
|
11416
11426
|
import path31 from "path";
|
|
11417
11427
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
@@ -11426,12 +11436,12 @@ function registerMemoryRm(memory2) {
|
|
|
11426
11436
|
memory2.command("delete <id>").alias("rm").description("Delete a memory file (and its usage entry by default). Mirrors MCP mem_delete. Alias: rm").option("-y, --yes", "skip the confirmation prompt").option("--keep-usage", "do not remove the usage.json entry").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
11427
11437
|
const root = findProjectRoot27(opts.dir);
|
|
11428
11438
|
const paths = resolveHaivePaths24(root);
|
|
11429
|
-
if (!
|
|
11439
|
+
if (!existsSync49(paths.memoriesDir)) {
|
|
11430
11440
|
ui.error(`No .ai/memories at ${root}.`);
|
|
11431
11441
|
process.exitCode = 1;
|
|
11432
11442
|
return;
|
|
11433
11443
|
}
|
|
11434
|
-
const all = await
|
|
11444
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
11435
11445
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
11436
11446
|
if (!found) {
|
|
11437
11447
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -11462,7 +11472,7 @@ function registerMemoryRm(memory2) {
|
|
|
11462
11472
|
}
|
|
11463
11473
|
|
|
11464
11474
|
// src/commands/memory-show.ts
|
|
11465
|
-
import { existsSync as
|
|
11475
|
+
import { existsSync as existsSync50 } from "fs";
|
|
11466
11476
|
import { readFile as readFile15 } from "fs/promises";
|
|
11467
11477
|
import path33 from "path";
|
|
11468
11478
|
import "commander";
|
|
@@ -11477,12 +11487,12 @@ function registerMemoryShow(memory2) {
|
|
|
11477
11487
|
memory2.command("get <id>").alias("show").description("Print a memory's frontmatter, body, and confidence/usage. Mirrors MCP mem_get. Alias: show").option("--raw", "print the raw file contents instead of a summary").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
11478
11488
|
const root = findProjectRoot28(opts.dir);
|
|
11479
11489
|
const paths = resolveHaivePaths25(root);
|
|
11480
|
-
if (!
|
|
11490
|
+
if (!existsSync50(paths.memoriesDir)) {
|
|
11481
11491
|
ui.error(`No .ai/memories at ${root}.`);
|
|
11482
11492
|
process.exitCode = 1;
|
|
11483
11493
|
return;
|
|
11484
11494
|
}
|
|
11485
|
-
const all = await
|
|
11495
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
11486
11496
|
const found = all.find((m) => m.memory.frontmatter.id === id);
|
|
11487
11497
|
if (!found) {
|
|
11488
11498
|
ui.error(`No memory with id "${id}".`);
|
|
@@ -11521,7 +11531,7 @@ function registerMemoryShow(memory2) {
|
|
|
11521
11531
|
}
|
|
11522
11532
|
|
|
11523
11533
|
// src/commands/memory-stats.ts
|
|
11524
|
-
import { existsSync as
|
|
11534
|
+
import { existsSync as existsSync51 } from "fs";
|
|
11525
11535
|
import path34 from "path";
|
|
11526
11536
|
import "commander";
|
|
11527
11537
|
import {
|
|
@@ -11535,12 +11545,12 @@ function registerMemoryStats(memory2) {
|
|
|
11535
11545
|
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) => {
|
|
11536
11546
|
const root = findProjectRoot29(opts.dir);
|
|
11537
11547
|
const paths = resolveHaivePaths26(root);
|
|
11538
|
-
if (!
|
|
11548
|
+
if (!existsSync51(paths.memoriesDir)) {
|
|
11539
11549
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
11540
11550
|
process.exitCode = 1;
|
|
11541
11551
|
return;
|
|
11542
11552
|
}
|
|
11543
|
-
const all = await
|
|
11553
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
11544
11554
|
const usage = await loadUsageIndex21(paths);
|
|
11545
11555
|
const target = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
|
|
11546
11556
|
if (target.length === 0) {
|
|
@@ -11566,7 +11576,7 @@ function registerMemoryStats(memory2) {
|
|
|
11566
11576
|
}
|
|
11567
11577
|
|
|
11568
11578
|
// src/commands/memory-impact.ts
|
|
11569
|
-
import { existsSync as
|
|
11579
|
+
import { existsSync as existsSync53 } from "fs";
|
|
11570
11580
|
import "commander";
|
|
11571
11581
|
import {
|
|
11572
11582
|
compareImpact,
|
|
@@ -11583,12 +11593,12 @@ function registerMemoryImpact(memory2) {
|
|
|
11583
11593
|
).option("--id <id>", "show impact for a single memory id").option("--prune", "list only prune candidates (dead weight worth reviewing)", false).option("--tier <tier>", "filter to a tier: high | medium | low | dormant").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
11584
11594
|
const root = findProjectRoot30(opts.dir);
|
|
11585
11595
|
const paths = resolveHaivePaths27(root);
|
|
11586
|
-
if (!
|
|
11596
|
+
if (!existsSync53(paths.memoriesDir)) {
|
|
11587
11597
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
11588
11598
|
process.exitCode = 1;
|
|
11589
11599
|
return;
|
|
11590
11600
|
}
|
|
11591
|
-
const all = await
|
|
11601
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
11592
11602
|
const usageIndex = await loadUsageIndex23(paths);
|
|
11593
11603
|
let rows = all.filter((m) => !opts.id || m.memory.frontmatter.id === opts.id).map(({ memory: mem }) => {
|
|
11594
11604
|
const fm = mem.frontmatter;
|
|
@@ -11654,8 +11664,8 @@ function pad(value, width) {
|
|
|
11654
11664
|
}
|
|
11655
11665
|
|
|
11656
11666
|
// src/commands/memory-feedback.ts
|
|
11657
|
-
import { existsSync as
|
|
11658
|
-
import { writeFile as
|
|
11667
|
+
import { existsSync as existsSync54 } from "fs";
|
|
11668
|
+
import { writeFile as writeFile24 } from "fs/promises";
|
|
11659
11669
|
import "commander";
|
|
11660
11670
|
import {
|
|
11661
11671
|
applyFeedbackAdjustment as applyFeedbackAdjustment2,
|
|
@@ -11681,12 +11691,12 @@ function registerMemoryFeedback(memory2) {
|
|
|
11681
11691
|
}
|
|
11682
11692
|
const root = findProjectRoot31(opts.dir);
|
|
11683
11693
|
const paths = resolveHaivePaths28(root);
|
|
11684
|
-
if (!
|
|
11694
|
+
if (!existsSync54(paths.memoriesDir)) {
|
|
11685
11695
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
11686
11696
|
process.exitCode = 1;
|
|
11687
11697
|
return;
|
|
11688
11698
|
}
|
|
11689
|
-
const all = await
|
|
11699
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
11690
11700
|
const target = all.find((m) => m.memory.frontmatter.id === id);
|
|
11691
11701
|
if (!target) {
|
|
11692
11702
|
ui.error(`No memory with id '${id}'.`);
|
|
@@ -11703,7 +11713,7 @@ function registerMemoryFeedback(memory2) {
|
|
|
11703
11713
|
const adjustedFrontmatter = applyFeedbackAdjustment2(target.memory.frontmatter, adjustment);
|
|
11704
11714
|
if (adjustedFrontmatter !== target.memory.frontmatter) {
|
|
11705
11715
|
target.memory.frontmatter = adjustedFrontmatter;
|
|
11706
|
-
await
|
|
11716
|
+
await writeFile24(target.filePath, serializeMemory21(target.memory), "utf8");
|
|
11707
11717
|
}
|
|
11708
11718
|
const impact = computeImpact4(target.memory.frontmatter, usage);
|
|
11709
11719
|
if (opts.json) {
|
|
@@ -11721,8 +11731,8 @@ function registerMemoryFeedback(memory2) {
|
|
|
11721
11731
|
}
|
|
11722
11732
|
|
|
11723
11733
|
// src/commands/memory-verify.ts
|
|
11724
|
-
import { writeFile as
|
|
11725
|
-
import { existsSync as
|
|
11734
|
+
import { writeFile as writeFile25 } from "fs/promises";
|
|
11735
|
+
import { existsSync as existsSync55 } from "fs";
|
|
11726
11736
|
import path35 from "path";
|
|
11727
11737
|
import "commander";
|
|
11728
11738
|
import {
|
|
@@ -11737,7 +11747,7 @@ function registerMemoryVerify(memory2) {
|
|
|
11737
11747
|
).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) => {
|
|
11738
11748
|
const root = findProjectRoot32(opts.dir);
|
|
11739
11749
|
const paths = resolveHaivePaths29(root);
|
|
11740
|
-
if (!
|
|
11750
|
+
if (!existsSync55(paths.memoriesDir)) {
|
|
11741
11751
|
if (opts.json) {
|
|
11742
11752
|
console.log(JSON.stringify({ error: "not-initialized", root }, null, 2));
|
|
11743
11753
|
} else {
|
|
@@ -11746,7 +11756,7 @@ function registerMemoryVerify(memory2) {
|
|
|
11746
11756
|
process.exitCode = 1;
|
|
11747
11757
|
return;
|
|
11748
11758
|
}
|
|
11749
|
-
const all = await
|
|
11759
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
11750
11760
|
const targets = opts.id ? all.filter((m) => m.memory.frontmatter.id === opts.id) : all;
|
|
11751
11761
|
if (opts.id && targets.length === 0) {
|
|
11752
11762
|
if (opts.json) {
|
|
@@ -11795,7 +11805,7 @@ function registerMemoryVerify(memory2) {
|
|
|
11795
11805
|
}
|
|
11796
11806
|
if (opts.update) {
|
|
11797
11807
|
const next = applyVerification2(mem, result);
|
|
11798
|
-
await
|
|
11808
|
+
await writeFile25(filePath, serializeMemory23(next), "utf8");
|
|
11799
11809
|
updated++;
|
|
11800
11810
|
}
|
|
11801
11811
|
}
|
|
@@ -11858,7 +11868,7 @@ function applyVerification2(mem, result) {
|
|
|
11858
11868
|
|
|
11859
11869
|
// src/commands/memory-import.ts
|
|
11860
11870
|
import { readFile as readFile16 } from "fs/promises";
|
|
11861
|
-
import { existsSync as
|
|
11871
|
+
import { existsSync as existsSync56 } from "fs";
|
|
11862
11872
|
import "commander";
|
|
11863
11873
|
import {
|
|
11864
11874
|
findProjectRoot as findProjectRoot33,
|
|
@@ -11870,12 +11880,12 @@ function registerMemoryImport(memory2) {
|
|
|
11870
11880
|
).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) => {
|
|
11871
11881
|
const root = findProjectRoot33(opts.dir);
|
|
11872
11882
|
const paths = resolveHaivePaths30(root);
|
|
11873
|
-
if (!
|
|
11883
|
+
if (!existsSync56(paths.haiveDir)) {
|
|
11874
11884
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
11875
11885
|
process.exitCode = 1;
|
|
11876
11886
|
return;
|
|
11877
11887
|
}
|
|
11878
|
-
if (!
|
|
11888
|
+
if (!existsSync56(opts.from)) {
|
|
11879
11889
|
ui.error(`File not found: ${opts.from}`);
|
|
11880
11890
|
process.exitCode = 1;
|
|
11881
11891
|
return;
|
|
@@ -11908,8 +11918,8 @@ function registerMemoryImport(memory2) {
|
|
|
11908
11918
|
}
|
|
11909
11919
|
|
|
11910
11920
|
// src/commands/memory-import-changelog.ts
|
|
11911
|
-
import { existsSync as
|
|
11912
|
-
import { readFile as readFile17, mkdir as mkdir15, writeFile as
|
|
11921
|
+
import { existsSync as existsSync57 } from "fs";
|
|
11922
|
+
import { readFile as readFile17, mkdir as mkdir15, writeFile as writeFile26 } from "fs/promises";
|
|
11913
11923
|
import path36 from "path";
|
|
11914
11924
|
import "commander";
|
|
11915
11925
|
import {
|
|
@@ -11983,7 +11993,7 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
11983
11993
|
const root = findProjectRoot34(opts.dir);
|
|
11984
11994
|
const paths = resolveHaivePaths31(root);
|
|
11985
11995
|
const changelogPath = path36.resolve(root, opts.fromChangelog);
|
|
11986
|
-
if (!
|
|
11996
|
+
if (!existsSync57(changelogPath)) {
|
|
11987
11997
|
ui.error(`CHANGELOG not found: ${changelogPath}`);
|
|
11988
11998
|
process.exitCode = 1;
|
|
11989
11999
|
return;
|
|
@@ -12046,7 +12056,7 @@ function registerMemoryImportChangelog(memory2) {
|
|
|
12046
12056
|
paths: [path36.relative(root, changelogPath)],
|
|
12047
12057
|
topic: `changelog-${pkgName}-${entry.version}`
|
|
12048
12058
|
});
|
|
12049
|
-
await
|
|
12059
|
+
await writeFile26(
|
|
12050
12060
|
path36.join(teamDir, `${fm.id}.md`),
|
|
12051
12061
|
serializeMemory24({ frontmatter: fm, body: lines.join("\n") }),
|
|
12052
12062
|
"utf8"
|
|
@@ -12070,15 +12080,15 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
|
|
|
12070
12080
|
}
|
|
12071
12081
|
|
|
12072
12082
|
// src/commands/memory-digest.ts
|
|
12073
|
-
import { existsSync as
|
|
12074
|
-
import { writeFile as
|
|
12083
|
+
import { existsSync as existsSync58 } from "fs";
|
|
12084
|
+
import { writeFile as writeFile27 } from "fs/promises";
|
|
12075
12085
|
import path37 from "path";
|
|
12076
12086
|
import "commander";
|
|
12077
12087
|
import {
|
|
12078
12088
|
deriveConfidence as deriveConfidence12,
|
|
12079
12089
|
findProjectRoot as findProjectRoot35,
|
|
12080
12090
|
getUsage as getUsage20,
|
|
12081
|
-
loadMemoriesFromDir as
|
|
12091
|
+
loadMemoriesFromDir as loadMemoriesFromDir28,
|
|
12082
12092
|
loadUsageIndex as loadUsageIndex25,
|
|
12083
12093
|
resolveHaivePaths as resolveHaivePaths32
|
|
12084
12094
|
} from "@hiveai/core";
|
|
@@ -12095,7 +12105,7 @@ function registerMemoryDigest(program2) {
|
|
|
12095
12105
|
).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) => {
|
|
12096
12106
|
const root = findProjectRoot35(opts.dir);
|
|
12097
12107
|
const paths = resolveHaivePaths32(root);
|
|
12098
|
-
if (!
|
|
12108
|
+
if (!existsSync58(paths.memoriesDir)) {
|
|
12099
12109
|
ui.error("No .ai/memories found. Run `haive init` first.");
|
|
12100
12110
|
process.exitCode = 1;
|
|
12101
12111
|
return;
|
|
@@ -12103,7 +12113,7 @@ function registerMemoryDigest(program2) {
|
|
|
12103
12113
|
const days = Math.max(1, Number(opts.days ?? 7));
|
|
12104
12114
|
const scopeFilter = opts.scope ?? "team";
|
|
12105
12115
|
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
|
|
12106
|
-
const all = await
|
|
12116
|
+
const all = await loadMemoriesFromDir28(paths.memoriesDir);
|
|
12107
12117
|
const usage = await loadUsageIndex25(paths);
|
|
12108
12118
|
const recent = all.filter(({ memory: mem }) => {
|
|
12109
12119
|
const fm = mem.frontmatter;
|
|
@@ -12168,7 +12178,7 @@ function registerMemoryDigest(program2) {
|
|
|
12168
12178
|
const digest = lines.join("\n");
|
|
12169
12179
|
if (opts.out) {
|
|
12170
12180
|
const outPath = path37.resolve(process.cwd(), opts.out);
|
|
12171
|
-
await
|
|
12181
|
+
await writeFile27(outPath, digest, "utf8");
|
|
12172
12182
|
ui.success(`Digest written to ${opts.out} (${recent.length} memor${recent.length === 1 ? "y" : "ies"})`);
|
|
12173
12183
|
} else {
|
|
12174
12184
|
console.log(digest);
|
|
@@ -12177,15 +12187,15 @@ function registerMemoryDigest(program2) {
|
|
|
12177
12187
|
}
|
|
12178
12188
|
|
|
12179
12189
|
// src/commands/session-end.ts
|
|
12180
|
-
import { writeFile as
|
|
12181
|
-
import { existsSync as
|
|
12190
|
+
import { writeFile as writeFile28, mkdir as mkdir16, readFile as readFile18, rm as rm2 } from "fs/promises";
|
|
12191
|
+
import { existsSync as existsSync59 } from "fs";
|
|
12182
12192
|
import { spawn as spawn4 } from "child_process";
|
|
12183
12193
|
import path38 from "path";
|
|
12184
12194
|
import "commander";
|
|
12185
12195
|
import {
|
|
12186
12196
|
buildFrontmatter as buildFrontmatter10,
|
|
12187
12197
|
findProjectRoot as findProjectRoot36,
|
|
12188
|
-
loadMemoriesFromDir as
|
|
12198
|
+
loadMemoriesFromDir as loadMemoriesFromDir29,
|
|
12189
12199
|
loadPreventionEvents as loadPreventionEvents2,
|
|
12190
12200
|
loadUsageIndex as loadUsageIndex26,
|
|
12191
12201
|
memoryFilePath as memoryFilePath10,
|
|
@@ -12196,7 +12206,7 @@ import {
|
|
|
12196
12206
|
} from "@hiveai/core";
|
|
12197
12207
|
async function buildAutoRecap(paths) {
|
|
12198
12208
|
const obsFile = path38.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
12199
|
-
if (!
|
|
12209
|
+
if (!existsSync59(obsFile)) return await buildGitAutoRecap(paths);
|
|
12200
12210
|
const raw = await readFile18(obsFile, "utf8").catch(() => "");
|
|
12201
12211
|
if (!raw.trim()) return await buildGitAutoRecap(paths);
|
|
12202
12212
|
const lines = raw.split("\n").filter(Boolean);
|
|
@@ -12365,7 +12375,7 @@ function runGit(cwd, args) {
|
|
|
12365
12375
|
}
|
|
12366
12376
|
async function observationStart(paths) {
|
|
12367
12377
|
const obsFile = path38.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
12368
|
-
if (!
|
|
12378
|
+
if (!existsSync59(obsFile)) return null;
|
|
12369
12379
|
const raw = await readFile18(obsFile, "utf8").catch(() => "");
|
|
12370
12380
|
let first = null;
|
|
12371
12381
|
for (const line of raw.split("\n")) {
|
|
@@ -12381,7 +12391,7 @@ async function observationStart(paths) {
|
|
|
12381
12391
|
}
|
|
12382
12392
|
async function printCaughtForYou(paths, since, quiet) {
|
|
12383
12393
|
if (quiet) return;
|
|
12384
|
-
const memories =
|
|
12394
|
+
const memories = existsSync59(paths.memoriesDir) ? await loadMemoriesFromDir29(paths.memoriesDir) : [];
|
|
12385
12395
|
const usage = await loadUsageIndex26(paths);
|
|
12386
12396
|
const events = await loadPreventionEvents2(paths);
|
|
12387
12397
|
const summary = summarizeCaughtForYou(events, memories, usage, {
|
|
@@ -12419,7 +12429,7 @@ function registerSessionEnd(session2) {
|
|
|
12419
12429
|
).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) => {
|
|
12420
12430
|
const root = findProjectRoot36(opts.dir);
|
|
12421
12431
|
const paths = resolveHaivePaths33(root);
|
|
12422
|
-
if (!
|
|
12432
|
+
if (!existsSync59(paths.haiveDir)) {
|
|
12423
12433
|
if (opts.auto || opts.quiet) return;
|
|
12424
12434
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
12425
12435
|
process.exitCode = 1;
|
|
@@ -12453,7 +12463,7 @@ function registerSessionEnd(session2) {
|
|
|
12453
12463
|
});
|
|
12454
12464
|
const topic = recapTopic2(scope, opts.module);
|
|
12455
12465
|
const filesTouched = parseCsv5(resolvedFiles).map((p) => normalizeAnchorPath(root, p));
|
|
12456
|
-
const missingPaths = filesTouched.filter((p) => !
|
|
12466
|
+
const missingPaths = filesTouched.filter((p) => !existsSync59(path38.resolve(root, p)));
|
|
12457
12467
|
if (missingPaths.length > 0 && !opts.quiet) {
|
|
12458
12468
|
ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
|
|
12459
12469
|
for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
|
|
@@ -12461,11 +12471,11 @@ function registerSessionEnd(session2) {
|
|
|
12461
12471
|
const cleanupObservations = async () => {
|
|
12462
12472
|
if (!opts.auto) return;
|
|
12463
12473
|
const obsFile = path38.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
12464
|
-
if (
|
|
12474
|
+
if (existsSync59(obsFile)) await rm2(obsFile).catch(() => {
|
|
12465
12475
|
});
|
|
12466
12476
|
};
|
|
12467
|
-
if (
|
|
12468
|
-
const existing = await
|
|
12477
|
+
if (existsSync59(paths.memoriesDir)) {
|
|
12478
|
+
const existing = await loadMemoriesFromDir29(paths.memoriesDir);
|
|
12469
12479
|
const topicMatch = existing.find(
|
|
12470
12480
|
({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
|
|
12471
12481
|
);
|
|
@@ -12481,7 +12491,7 @@ function registerSessionEnd(session2) {
|
|
|
12481
12491
|
paths: filesTouched.length ? filesTouched : fm.anchor.paths
|
|
12482
12492
|
}
|
|
12483
12493
|
};
|
|
12484
|
-
await
|
|
12494
|
+
await writeFile28(topicMatch.filePath, serializeMemory25({ frontmatter: newFrontmatter, body }), "utf8");
|
|
12485
12495
|
await cleanupObservations();
|
|
12486
12496
|
if (!opts.quiet) {
|
|
12487
12497
|
ui.success(`Session recap updated (revision #${revisionCount})`);
|
|
@@ -12504,7 +12514,7 @@ function registerSessionEnd(session2) {
|
|
|
12504
12514
|
});
|
|
12505
12515
|
const file = memoryFilePath10(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
|
|
12506
12516
|
await mkdir16(path38.dirname(file), { recursive: true });
|
|
12507
|
-
await
|
|
12517
|
+
await writeFile28(file, serializeMemory25({ frontmatter, body }), "utf8");
|
|
12508
12518
|
await cleanupObservations();
|
|
12509
12519
|
if (!opts.quiet) {
|
|
12510
12520
|
ui.success(`Session recap created`);
|
|
@@ -12528,7 +12538,7 @@ function normalizeAnchorPath(root, filePath) {
|
|
|
12528
12538
|
}
|
|
12529
12539
|
|
|
12530
12540
|
// src/commands/snapshot.ts
|
|
12531
|
-
import { existsSync as
|
|
12541
|
+
import { existsSync as existsSync60 } from "fs";
|
|
12532
12542
|
import { readdir as readdir4 } from "fs/promises";
|
|
12533
12543
|
import path39 from "path";
|
|
12534
12544
|
import "commander";
|
|
@@ -12563,14 +12573,14 @@ function registerSnapshot(program2) {
|
|
|
12563
12573
|
).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) => {
|
|
12564
12574
|
const root = findProjectRoot37(opts.dir);
|
|
12565
12575
|
const paths = resolveHaivePaths34(root);
|
|
12566
|
-
if (!
|
|
12576
|
+
if (!existsSync60(paths.haiveDir)) {
|
|
12567
12577
|
ui.error("No .ai/ found. Run `haive init` first.");
|
|
12568
12578
|
process.exitCode = 1;
|
|
12569
12579
|
return;
|
|
12570
12580
|
}
|
|
12571
12581
|
if (opts.list) {
|
|
12572
12582
|
const contractsDir = path39.join(paths.haiveDir, "contracts");
|
|
12573
|
-
if (!
|
|
12583
|
+
if (!existsSync60(contractsDir)) {
|
|
12574
12584
|
console.log(ui.dim("No contract snapshots found."));
|
|
12575
12585
|
return;
|
|
12576
12586
|
}
|
|
@@ -12694,15 +12704,15 @@ function detectFormat(filePath) {
|
|
|
12694
12704
|
}
|
|
12695
12705
|
|
|
12696
12706
|
// src/commands/hub.ts
|
|
12697
|
-
import { existsSync as
|
|
12698
|
-
import { mkdir as mkdir17, readFile as readFile19, writeFile as
|
|
12707
|
+
import { existsSync as existsSync61 } from "fs";
|
|
12708
|
+
import { mkdir as mkdir17, readFile as readFile19, writeFile as writeFile29, copyFile } from "fs/promises";
|
|
12699
12709
|
import path40 from "path";
|
|
12700
12710
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
12701
12711
|
import "commander";
|
|
12702
12712
|
import {
|
|
12703
12713
|
findProjectRoot as findProjectRoot38,
|
|
12704
12714
|
loadConfig as loadConfig8,
|
|
12705
|
-
loadMemoriesFromDir as
|
|
12715
|
+
loadMemoriesFromDir as loadMemoriesFromDir30,
|
|
12706
12716
|
resolveHaivePaths as resolveHaivePaths35,
|
|
12707
12717
|
saveConfig as saveConfig3,
|
|
12708
12718
|
serializeMemory as serializeMemory26
|
|
@@ -12728,7 +12738,7 @@ function registerHub(program2) {
|
|
|
12728
12738
|
}
|
|
12729
12739
|
const sharedDir = path40.join(absPath, ".ai", "memories", "shared");
|
|
12730
12740
|
await mkdir17(sharedDir, { recursive: true });
|
|
12731
|
-
await
|
|
12741
|
+
await writeFile29(
|
|
12732
12742
|
path40.join(absPath, ".ai", "README.md"),
|
|
12733
12743
|
`# hAIve Team Knowledge Hub
|
|
12734
12744
|
|
|
@@ -12750,7 +12760,7 @@ haive hub pull # import into a project
|
|
|
12750
12760
|
`,
|
|
12751
12761
|
"utf8"
|
|
12752
12762
|
);
|
|
12753
|
-
await
|
|
12763
|
+
await writeFile29(
|
|
12754
12764
|
path40.join(absPath, ".gitignore"),
|
|
12755
12765
|
".ai/.cache/\n.ai/memories/personal/\n",
|
|
12756
12766
|
"utf8"
|
|
@@ -12796,7 +12806,7 @@ Next steps:
|
|
|
12796
12806
|
return;
|
|
12797
12807
|
}
|
|
12798
12808
|
const hubRoot = path40.resolve(root, config.hubPath);
|
|
12799
|
-
if (!
|
|
12809
|
+
if (!existsSync61(hubRoot)) {
|
|
12800
12810
|
ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
|
|
12801
12811
|
process.exitCode = 1;
|
|
12802
12812
|
return;
|
|
@@ -12804,7 +12814,7 @@ Next steps:
|
|
|
12804
12814
|
const projectName = path40.basename(root);
|
|
12805
12815
|
const destDir = path40.join(hubRoot, ".ai", "memories", "shared", projectName);
|
|
12806
12816
|
await mkdir17(destDir, { recursive: true });
|
|
12807
|
-
const all = await
|
|
12817
|
+
const all = await loadMemoriesFromDir30(paths.memoriesDir);
|
|
12808
12818
|
const shared = all.filter(
|
|
12809
12819
|
({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && // Don't push imported memories (avoid echo loops)
|
|
12810
12820
|
!memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
|
|
@@ -12822,7 +12832,7 @@ Next steps:
|
|
|
12822
12832
|
const fm = memory2.frontmatter;
|
|
12823
12833
|
const fileName = `${fm.id}.md`;
|
|
12824
12834
|
const destPath = path40.join(destDir, fileName);
|
|
12825
|
-
await
|
|
12835
|
+
await writeFile29(destPath, serializeMemory26(memory2), "utf8");
|
|
12826
12836
|
pushed++;
|
|
12827
12837
|
}
|
|
12828
12838
|
console.log(ui.green(`\u2713 Pushed ${pushed} shared memor${pushed === 1 ? "y" : "ies"} to hub`));
|
|
@@ -12866,7 +12876,7 @@ Next steps:
|
|
|
12866
12876
|
}
|
|
12867
12877
|
const hubRoot = path40.resolve(root, config.hubPath);
|
|
12868
12878
|
const hubSharedDir = path40.join(hubRoot, ".ai", "memories", "shared");
|
|
12869
|
-
if (!
|
|
12879
|
+
if (!existsSync61(hubSharedDir)) {
|
|
12870
12880
|
ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
|
|
12871
12881
|
return;
|
|
12872
12882
|
}
|
|
@@ -12921,7 +12931,7 @@ Next steps:
|
|
|
12921
12931
|
` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
|
|
12922
12932
|
);
|
|
12923
12933
|
const sharedDir = path40.join(paths.memoriesDir, "shared");
|
|
12924
|
-
if (
|
|
12934
|
+
if (existsSync61(sharedDir)) {
|
|
12925
12935
|
const { readdir: readdir7 } = await import("fs/promises");
|
|
12926
12936
|
const sources = (await readdir7(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
12927
12937
|
console.log(`
|
|
@@ -12933,7 +12943,7 @@ Next steps:
|
|
|
12933
12943
|
} else {
|
|
12934
12944
|
console.log(ui.dim(" No imported shared memories yet."));
|
|
12935
12945
|
}
|
|
12936
|
-
const all = await
|
|
12946
|
+
const all = await loadMemoriesFromDir30(paths.memoriesDir);
|
|
12937
12947
|
const outgoing = all.filter(
|
|
12938
12948
|
({ memory: memory2 }) => memory2.frontmatter.scope === "shared" && !memory2.frontmatter.tags.some((t) => t.startsWith("cross-repo:"))
|
|
12939
12949
|
);
|
|
@@ -12943,20 +12953,20 @@ Next steps:
|
|
|
12943
12953
|
console.log(ui.dim(" Run `haive hub push` to publish them to the hub."));
|
|
12944
12954
|
}
|
|
12945
12955
|
void readFile19;
|
|
12946
|
-
void
|
|
12956
|
+
void writeFile29;
|
|
12947
12957
|
void saveConfig3;
|
|
12948
12958
|
});
|
|
12949
12959
|
}
|
|
12950
12960
|
|
|
12951
12961
|
// src/commands/stats.ts
|
|
12952
12962
|
import "commander";
|
|
12953
|
-
import { existsSync as
|
|
12954
|
-
import { mkdir as mkdir18, writeFile as
|
|
12963
|
+
import { existsSync as existsSync63 } from "fs";
|
|
12964
|
+
import { mkdir as mkdir18, writeFile as writeFile30 } from "fs/promises";
|
|
12955
12965
|
import path41 from "path";
|
|
12956
12966
|
import {
|
|
12957
12967
|
aggregateUsage,
|
|
12958
12968
|
findProjectRoot as findProjectRoot39,
|
|
12959
|
-
loadMemoriesFromDir as
|
|
12969
|
+
loadMemoriesFromDir as loadMemoriesFromDir31,
|
|
12960
12970
|
loadUsageIndex as loadUsageIndex27,
|
|
12961
12971
|
parseSince,
|
|
12962
12972
|
readUsageEvents as readUsageEvents2,
|
|
@@ -13028,8 +13038,8 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
13028
13038
|
const size = await usageLogSize(paths);
|
|
13029
13039
|
let events = await readUsageEvents2(paths);
|
|
13030
13040
|
let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
|
|
13031
|
-
if (
|
|
13032
|
-
const mems = await
|
|
13041
|
+
if (existsSync63(paths.memoriesDir)) {
|
|
13042
|
+
const mems = await loadMemoriesFromDir31(paths.memoriesDir);
|
|
13033
13043
|
for (const { memory: memory2 } of mems) {
|
|
13034
13044
|
const fm = memory2.frontmatter;
|
|
13035
13045
|
if (fm.type === "session_recap") memoryCount.total_skipped_session++;
|
|
@@ -13071,7 +13081,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
|
13071
13081
|
top_memory_reads: memoryHitsLeader,
|
|
13072
13082
|
roi_hints: roiHints
|
|
13073
13083
|
};
|
|
13074
|
-
await
|
|
13084
|
+
await writeFile30(outAbs, JSON.stringify(payload, null, 2), "utf8");
|
|
13075
13085
|
ui.success(`Wrote ROI / usage rollup \u2192 ${outAbs}`);
|
|
13076
13086
|
}
|
|
13077
13087
|
async function renderMemoryHits(paths, opts) {
|
|
@@ -13248,8 +13258,8 @@ function summarize(name, t0, payload, notes) {
|
|
|
13248
13258
|
}
|
|
13249
13259
|
|
|
13250
13260
|
// src/commands/benchmark.ts
|
|
13251
|
-
import { existsSync as
|
|
13252
|
-
import { readdir as readdir5, readFile as readFile20, writeFile as
|
|
13261
|
+
import { existsSync as existsSync64 } from "fs";
|
|
13262
|
+
import { readdir as readdir5, readFile as readFile20, writeFile as writeFile31 } from "fs/promises";
|
|
13253
13263
|
import path43 from "path";
|
|
13254
13264
|
import "commander";
|
|
13255
13265
|
import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot41 } from "@hiveai/core";
|
|
@@ -13266,7 +13276,7 @@ function registerBenchmark(program2) {
|
|
|
13266
13276
|
const markdown = renderMarkdown(root, summary, rows);
|
|
13267
13277
|
if (opts.out) {
|
|
13268
13278
|
const outFile = path43.isAbsolute(opts.out) ? opts.out : path43.join(root, opts.out);
|
|
13269
|
-
await
|
|
13279
|
+
await writeFile31(outFile, markdown, "utf8");
|
|
13270
13280
|
ui.success(`wrote ${path43.relative(process.cwd(), outFile)}`);
|
|
13271
13281
|
return;
|
|
13272
13282
|
}
|
|
@@ -13296,14 +13306,14 @@ function resolveBenchmarkRoot(dir) {
|
|
|
13296
13306
|
return path43.join(projectRoot, candidate);
|
|
13297
13307
|
}
|
|
13298
13308
|
async function collectRows(root) {
|
|
13299
|
-
if (!
|
|
13309
|
+
if (!existsSync64(root)) throw new Error(`Benchmark directory not found: ${root}`);
|
|
13300
13310
|
const entries = await readdir5(root, { withFileTypes: true });
|
|
13301
13311
|
const rows = [];
|
|
13302
13312
|
for (const entry of entries) {
|
|
13303
13313
|
if (!entry.isDirectory()) continue;
|
|
13304
13314
|
const fixtureDir = path43.join(root, entry.name);
|
|
13305
13315
|
const reportFile = path43.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
|
|
13306
|
-
if (!
|
|
13316
|
+
if (!existsSync64(reportFile)) continue;
|
|
13307
13317
|
const report = await readFile20(reportFile, "utf8");
|
|
13308
13318
|
rows.push(parseAgentReport(entry.name, report));
|
|
13309
13319
|
}
|
|
@@ -13393,8 +13403,8 @@ function escapeRegExp(value) {
|
|
|
13393
13403
|
}
|
|
13394
13404
|
|
|
13395
13405
|
// src/commands/eval.ts
|
|
13396
|
-
import { mkdir as mkdir19, readFile as readFile21, writeFile as
|
|
13397
|
-
import { existsSync as
|
|
13406
|
+
import { mkdir as mkdir19, readFile as readFile21, writeFile as writeFile33 } from "fs/promises";
|
|
13407
|
+
import { existsSync as existsSync65 } from "fs";
|
|
13398
13408
|
import path44 from "path";
|
|
13399
13409
|
import "commander";
|
|
13400
13410
|
import {
|
|
@@ -13422,7 +13432,7 @@ function registerEval(program2) {
|
|
|
13422
13432
|
).option("--spec <file>", "JSON eval spec ({ retrieval: [...], sensors: [...] })").option("--semantic-only", "self-eval probes by title alone (no anchor files) \u2014 harder retrieval", false).option("-k, --top <n>", "briefing top-k considered a hit", "8").option("--json", "emit JSON", false).option("--out <file>", "write a Markdown report").option("--fail-under <score>", "exit non-zero if the overall score is below this (0\u2013100) \u2014 for CI gates").option("--fail-under-catch-rate <pct>", "exit non-zero if sensor catch-rate is below this percentage").option("--fail-under-gate-precision <pct>", "exit non-zero if gate precision is below this percentage").option("--baseline", "save this run as the baseline (.ai/eval/baseline.json) for future --compare", false).option("--compare", "diff this run against the saved baseline and print the delta", false).option("--baseline-file <path>", "baseline file to read/write (default: .ai/eval/baseline.json)").option("--fail-on-regression", "with --compare, exit non-zero if the score dropped vs the baseline", false).option("--regression-gate", "CI-safe gate: compare against the baseline IF one exists (fail on regression), else no-op", false).option("--record", "append this run's score to .ai/.cache/eval-history.jsonl (trend the harness over time)", false).option("--trend", "print the recorded score trend (sparkline + latest/best/delta) and exit", false).option("--ref <ref>", "version/commit label stored with a --record run").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
13423
13433
|
const root = findProjectRoot42(opts.dir);
|
|
13424
13434
|
const paths = resolveHaivePaths38(root);
|
|
13425
|
-
if (!
|
|
13435
|
+
if (!existsSync65(paths.memoriesDir)) {
|
|
13426
13436
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
13427
13437
|
process.exitCode = 1;
|
|
13428
13438
|
return;
|
|
@@ -13501,13 +13511,13 @@ function registerEval(program2) {
|
|
|
13501
13511
|
gate_precision: gatePrecision
|
|
13502
13512
|
};
|
|
13503
13513
|
await mkdir19(path44.dirname(baselineFile), { recursive: true });
|
|
13504
|
-
await
|
|
13514
|
+
await writeFile33(baselineFile, JSON.stringify(snapshot, null, 2), "utf8");
|
|
13505
13515
|
if (!opts.json) ui.success(`Saved baseline (score ${report.score}/100) \u2192 ${path44.relative(root, baselineFile)}`);
|
|
13506
13516
|
}
|
|
13507
13517
|
let delta = null;
|
|
13508
13518
|
let gateDelta = null;
|
|
13509
13519
|
if (opts.compare || opts.regressionGate) {
|
|
13510
|
-
if (!
|
|
13520
|
+
if (!existsSync65(baselineFile)) {
|
|
13511
13521
|
if (opts.regressionGate) {
|
|
13512
13522
|
if (!opts.json) ui.info(`No baseline at ${path44.relative(root, baselineFile)} \u2014 regression gate skipped. Run \`haive eval --baseline\` to enable it.`);
|
|
13513
13523
|
} else {
|
|
@@ -13545,7 +13555,7 @@ function registerEval(program2) {
|
|
|
13545
13555
|
const md = renderMarkdown2(root, k, resolvedSpec.source, report, gatePrecision);
|
|
13546
13556
|
if (opts.out) {
|
|
13547
13557
|
const outFile = path44.isAbsolute(opts.out) ? opts.out : path44.join(root, opts.out);
|
|
13548
|
-
await
|
|
13558
|
+
await writeFile33(outFile, md, "utf8");
|
|
13549
13559
|
ui.success(`wrote ${path44.relative(process.cwd(), outFile)}`);
|
|
13550
13560
|
} else {
|
|
13551
13561
|
console.log(md);
|
|
@@ -13632,10 +13642,10 @@ async function resolveSpec(opts, root, memoriesDir) {
|
|
|
13632
13642
|
return { spec: JSON.parse(raw), source: file };
|
|
13633
13643
|
}
|
|
13634
13644
|
const defaultSpec = path44.join(root, ".ai", "eval", "spec.json");
|
|
13635
|
-
if (
|
|
13645
|
+
if (existsSync65(defaultSpec)) {
|
|
13636
13646
|
const raw = await readFile21(defaultSpec, "utf8");
|
|
13637
13647
|
const explicit = JSON.parse(raw);
|
|
13638
|
-
const memories2 = await
|
|
13648
|
+
const memories2 = await loadMemoriesFromDir27(memoriesDir);
|
|
13639
13649
|
const synthesized = synthesizeSelfEvalCases(memories2, { includeFiles: !opts.semanticOnly });
|
|
13640
13650
|
return {
|
|
13641
13651
|
spec: {
|
|
@@ -13645,7 +13655,7 @@ async function resolveSpec(opts, root, memoriesDir) {
|
|
|
13645
13655
|
source: ".ai/eval/spec.json + synthesized anchored retrieval"
|
|
13646
13656
|
};
|
|
13647
13657
|
}
|
|
13648
|
-
const memories = await
|
|
13658
|
+
const memories = await loadMemoriesFromDir27(memoriesDir);
|
|
13649
13659
|
return {
|
|
13650
13660
|
spec: { retrieval: synthesizeSelfEvalCases(memories, { includeFiles: !opts.semanticOnly }) },
|
|
13651
13661
|
source: "synthesized anchored retrieval"
|
|
@@ -13743,8 +13753,8 @@ function renderMarkdown2(root, k, source, report, gatePrecision) {
|
|
|
13743
13753
|
}
|
|
13744
13754
|
|
|
13745
13755
|
// src/commands/memory-suggest.ts
|
|
13746
|
-
import { mkdir as mkdir20, writeFile as
|
|
13747
|
-
import { existsSync as
|
|
13756
|
+
import { mkdir as mkdir20, writeFile as writeFile34 } from "fs/promises";
|
|
13757
|
+
import { existsSync as existsSync66 } from "fs";
|
|
13748
13758
|
import path45 from "path";
|
|
13749
13759
|
import "commander";
|
|
13750
13760
|
import {
|
|
@@ -13752,7 +13762,7 @@ import {
|
|
|
13752
13762
|
buildFrontmatter as buildFrontmatter11,
|
|
13753
13763
|
findProjectRoot as findProjectRoot43,
|
|
13754
13764
|
loadConfig as loadConfig10,
|
|
13755
|
-
loadMemoriesFromDir as
|
|
13765
|
+
loadMemoriesFromDir as loadMemoriesFromDir33,
|
|
13756
13766
|
memoryFilePath as memoryFilePath11,
|
|
13757
13767
|
parseSince as parseSince2,
|
|
13758
13768
|
readUsageEvents as readUsageEvents3,
|
|
@@ -13823,7 +13833,7 @@ function registerMemorySuggest(memory2) {
|
|
|
13823
13833
|
}
|
|
13824
13834
|
const created = [];
|
|
13825
13835
|
const skipped = [];
|
|
13826
|
-
const existing =
|
|
13836
|
+
const existing = existsSync66(paths.memoriesDir) ? await loadMemoriesFromDir33(paths.memoriesDir) : [];
|
|
13827
13837
|
for (const s of top) {
|
|
13828
13838
|
const slug = slugify2(s.query);
|
|
13829
13839
|
if (!slug) {
|
|
@@ -13847,11 +13857,11 @@ function registerMemorySuggest(memory2) {
|
|
|
13847
13857
|
const body = renderTemplate(s, fm.id, status);
|
|
13848
13858
|
const file = memoryFilePath11(paths, fm.scope, fm.id, fm.module);
|
|
13849
13859
|
await mkdir20(path45.dirname(file), { recursive: true });
|
|
13850
|
-
if (
|
|
13860
|
+
if (existsSync66(file)) {
|
|
13851
13861
|
skipped.push({ query: s.query, reason: `file already exists at ${path45.relative(root, file)}` });
|
|
13852
13862
|
continue;
|
|
13853
13863
|
}
|
|
13854
|
-
await
|
|
13864
|
+
await writeFile34(file, serializeMemory27({ frontmatter: fm, body }), "utf8");
|
|
13855
13865
|
created.push({ id: fm.id, file: path45.relative(root, file), query: s.query });
|
|
13856
13866
|
}
|
|
13857
13867
|
if (opts.json) {
|
|
@@ -13950,8 +13960,8 @@ function truncate2(text, max) {
|
|
|
13950
13960
|
}
|
|
13951
13961
|
|
|
13952
13962
|
// src/commands/memory-archive.ts
|
|
13953
|
-
import { existsSync as
|
|
13954
|
-
import { writeFile as
|
|
13963
|
+
import { existsSync as existsSync67 } from "fs";
|
|
13964
|
+
import { writeFile as writeFile35 } from "fs/promises";
|
|
13955
13965
|
import path46 from "path";
|
|
13956
13966
|
import "commander";
|
|
13957
13967
|
import {
|
|
@@ -13959,7 +13969,7 @@ import {
|
|
|
13959
13969
|
getUsage as getUsage21,
|
|
13960
13970
|
retirementSignal as retirementSignal2,
|
|
13961
13971
|
loadConfig as loadConfig11,
|
|
13962
|
-
loadMemoriesFromDir as
|
|
13972
|
+
loadMemoriesFromDir as loadMemoriesFromDir34,
|
|
13963
13973
|
loadUsageIndex as loadUsageIndex29,
|
|
13964
13974
|
resolveHaivePaths as resolveHaivePaths40,
|
|
13965
13975
|
serializeMemory as serializeMemory28
|
|
@@ -13971,7 +13981,7 @@ function registerMemoryArchive(memory2) {
|
|
|
13971
13981
|
).option("--since <window>", "minimum age since last read (e.g. '180d', '6m'). Default: enforcement.decayAfterDays or 180d").option("--type <type>", "limit to a memory type (default 'attempt'). Pass 'all' to scan all types.", "attempt").option("--unread", "decay by unread-age ALONE (ignore anchor status) \u2014 more aggressive corpus hygiene", false).option("--apply", "actually rewrite files (default: dry run)", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
13972
13982
|
const root = findProjectRoot44(opts.dir);
|
|
13973
13983
|
const paths = resolveHaivePaths40(root);
|
|
13974
|
-
if (!
|
|
13984
|
+
if (!existsSync67(paths.memoriesDir)) {
|
|
13975
13985
|
ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
13976
13986
|
process.exitCode = 1;
|
|
13977
13987
|
return;
|
|
@@ -13985,7 +13995,7 @@ function registerMemoryArchive(memory2) {
|
|
|
13985
13995
|
return;
|
|
13986
13996
|
}
|
|
13987
13997
|
const cutoff = Date.now() - minDays * MS_PER_DAY2;
|
|
13988
|
-
const all = await
|
|
13998
|
+
const all = await loadMemoriesFromDir34(paths.memoriesDir);
|
|
13989
13999
|
const usage = await loadUsageIndex29(paths);
|
|
13990
14000
|
const typeFilter = opts.type === "all" ? null : opts.type ?? "attempt";
|
|
13991
14001
|
const candidates = [];
|
|
@@ -13996,7 +14006,7 @@ function registerMemoryArchive(memory2) {
|
|
|
13996
14006
|
if (fm.status === "deprecated" || fm.status === "rejected") continue;
|
|
13997
14007
|
const retired = retirementSignal2(fm, mem.body);
|
|
13998
14008
|
const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
|
|
13999
|
-
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !
|
|
14009
|
+
const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync67(path46.join(paths.root, p)));
|
|
14000
14010
|
const isAnchorless = !hasAnyAnchor;
|
|
14001
14011
|
if (!retired.retired && !opts.unread && !isAnchorless && !allPathsGone) continue;
|
|
14002
14012
|
const u = getUsage21(usage, fm.id);
|
|
@@ -14045,7 +14055,7 @@ function registerMemoryArchive(memory2) {
|
|
|
14045
14055
|
if (!found) continue;
|
|
14046
14056
|
const fm = { ...found.memory.frontmatter, status: "deprecated" };
|
|
14047
14057
|
try {
|
|
14048
|
-
await
|
|
14058
|
+
await writeFile35(c.filePath, serializeMemory28({ frontmatter: fm, body: found.memory.body }), "utf8");
|
|
14049
14059
|
archived++;
|
|
14050
14060
|
} catch (err) {
|
|
14051
14061
|
if (!opts.json) {
|
|
@@ -14071,8 +14081,8 @@ function parseDays(input) {
|
|
|
14071
14081
|
}
|
|
14072
14082
|
|
|
14073
14083
|
// src/commands/doctor.ts
|
|
14074
|
-
import { existsSync as
|
|
14075
|
-
import { readFile as readFile23, stat, writeFile as
|
|
14084
|
+
import { existsSync as existsSync68, statSync as statSync2 } from "fs";
|
|
14085
|
+
import { readFile as readFile23, stat, writeFile as writeFile36 } from "fs/promises";
|
|
14076
14086
|
import path47 from "path";
|
|
14077
14087
|
import { execFileSync, execSync as execSync3 } from "child_process";
|
|
14078
14088
|
import "commander";
|
|
@@ -14083,7 +14093,7 @@ import {
|
|
|
14083
14093
|
isStackPackSeed as isStackPackSeed2,
|
|
14084
14094
|
loadCodeMap as loadCodeMap7,
|
|
14085
14095
|
loadConfig as loadConfig12,
|
|
14086
|
-
loadMemoriesFromDir as
|
|
14096
|
+
loadMemoriesFromDir as loadMemoriesFromDir35,
|
|
14087
14097
|
loadUsageIndex as loadUsageIndex30,
|
|
14088
14098
|
readUsageEvents as readUsageEvents4,
|
|
14089
14099
|
resolveHaivePaths as resolveHaivePaths41
|
|
@@ -14098,7 +14108,7 @@ function registerDoctor(program2) {
|
|
|
14098
14108
|
const findings = [];
|
|
14099
14109
|
const repairs = [];
|
|
14100
14110
|
const config = await loadConfig12(paths);
|
|
14101
|
-
if (!
|
|
14111
|
+
if (!existsSync68(paths.haiveDir)) {
|
|
14102
14112
|
findings.push({
|
|
14103
14113
|
severity: "error",
|
|
14104
14114
|
code: "not-initialized",
|
|
@@ -14119,7 +14129,7 @@ function registerDoctor(program2) {
|
|
|
14119
14129
|
})
|
|
14120
14130
|
);
|
|
14121
14131
|
}
|
|
14122
|
-
if (!
|
|
14132
|
+
if (!existsSync68(paths.projectContext)) {
|
|
14123
14133
|
findings.push({
|
|
14124
14134
|
severity: "warn",
|
|
14125
14135
|
code: "no-project-context",
|
|
@@ -14148,7 +14158,7 @@ function registerDoctor(program2) {
|
|
|
14148
14158
|
});
|
|
14149
14159
|
}
|
|
14150
14160
|
}
|
|
14151
|
-
const memories =
|
|
14161
|
+
const memories = existsSync68(paths.memoriesDir) ? await loadMemoriesFromDir35(paths.memoriesDir) : [];
|
|
14152
14162
|
const now = Date.now();
|
|
14153
14163
|
if (memories.length === 0) {
|
|
14154
14164
|
findings.push({
|
|
@@ -14300,7 +14310,7 @@ function registerDoctor(program2) {
|
|
|
14300
14310
|
if (config.enforcement?.requireBriefingFirst) {
|
|
14301
14311
|
const claudeSettings = path47.join(root, ".claude", "settings.local.json");
|
|
14302
14312
|
let hasClaudeEnforcement = false;
|
|
14303
|
-
if (
|
|
14313
|
+
if (existsSync68(claudeSettings)) {
|
|
14304
14314
|
try {
|
|
14305
14315
|
const { readFile: readFile28 } = await import("fs/promises");
|
|
14306
14316
|
const raw = await readFile28(claudeSettings, "utf8");
|
|
@@ -14326,7 +14336,7 @@ function registerDoctor(program2) {
|
|
|
14326
14336
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
|
|
14327
14337
|
});
|
|
14328
14338
|
}
|
|
14329
|
-
findings.push(...await collectInstallFindings(root, "0.
|
|
14339
|
+
findings.push(...await collectInstallFindings(root, "0.20.0"));
|
|
14330
14340
|
findings.push(...await collectToolchainFindings(root));
|
|
14331
14341
|
try {
|
|
14332
14342
|
const legacyRaw = execSync3("haive-mcp --version", {
|
|
@@ -14334,7 +14344,7 @@ function registerDoctor(program2) {
|
|
|
14334
14344
|
timeout: 3e3,
|
|
14335
14345
|
stdio: ["ignore", "pipe", "ignore"]
|
|
14336
14346
|
}).trim();
|
|
14337
|
-
const cliVersion = "0.
|
|
14347
|
+
const cliVersion = "0.20.0";
|
|
14338
14348
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
14339
14349
|
findings.push({
|
|
14340
14350
|
severity: "warn",
|
|
@@ -14356,14 +14366,14 @@ npm uninstall -g @hiveai/mcp`
|
|
|
14356
14366
|
];
|
|
14357
14367
|
const staleConfigs = [];
|
|
14358
14368
|
for (const cfgPath of configPaths) {
|
|
14359
|
-
if (!
|
|
14369
|
+
if (!existsSync68(cfgPath)) continue;
|
|
14360
14370
|
try {
|
|
14361
14371
|
const raw = await readFile23(cfgPath, "utf8");
|
|
14362
14372
|
if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
|
|
14363
14373
|
staleConfigs.push(path47.relative(root, cfgPath));
|
|
14364
14374
|
if (opts.fix && !opts.dryRun) {
|
|
14365
14375
|
const updated = raw.replace(/"command"\s*:\s*"haive-mcp"/g, '"command": "haive"').replace(/"args"\s*:\s*\[\]/g, '"args": ["mcp", "--stdio"]');
|
|
14366
|
-
await
|
|
14376
|
+
await writeFile36(cfgPath, updated, "utf8");
|
|
14367
14377
|
}
|
|
14368
14378
|
}
|
|
14369
14379
|
} catch {
|
|
@@ -14652,7 +14662,7 @@ which -a haive`
|
|
|
14652
14662
|
];
|
|
14653
14663
|
for (const rel of integrationFiles) {
|
|
14654
14664
|
const file = path47.join(root, rel);
|
|
14655
|
-
if (!
|
|
14665
|
+
if (!existsSync68(file)) continue;
|
|
14656
14666
|
const text = await readFile23(file, "utf8").catch(() => "");
|
|
14657
14667
|
for (const bin of extractAbsoluteHaiveBins(text)) {
|
|
14658
14668
|
const version = versionForBinary(bin);
|
|
@@ -14700,7 +14710,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
|
14700
14710
|
const isHaiveWorkspace = (await readJson(path47.join(root, "package.json")))?.name === "haive-monorepo";
|
|
14701
14711
|
if (!isHaiveWorkspace) return findings;
|
|
14702
14712
|
const cliDist = path47.join(root, "packages/cli/dist/index.js");
|
|
14703
|
-
if (!
|
|
14713
|
+
if (!existsSync68(cliDist)) {
|
|
14704
14714
|
findings.push({
|
|
14705
14715
|
severity: "warn",
|
|
14706
14716
|
code: "workspace-dist-missing",
|
|
@@ -14724,7 +14734,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
|
14724
14734
|
"packages/core/src/index.ts",
|
|
14725
14735
|
"packages/mcp/src/server.ts",
|
|
14726
14736
|
"packages/cli/src/index.ts"
|
|
14727
|
-
].map((rel) => path47.join(root, rel)).filter(
|
|
14737
|
+
].map((rel) => path47.join(root, rel)).filter(existsSync68);
|
|
14728
14738
|
if (sourceFiles.length > 0) {
|
|
14729
14739
|
const distMtime = statSync2(cliDist).mtimeMs;
|
|
14730
14740
|
const newestSource = Math.max(...sourceFiles.map((file) => statSync2(file).mtimeMs));
|
|
@@ -14813,7 +14823,7 @@ function collectGlobalHivemoduleFindings(expectedVersion) {
|
|
|
14813
14823
|
}
|
|
14814
14824
|
}
|
|
14815
14825
|
async function readJson(file) {
|
|
14816
|
-
if (!
|
|
14826
|
+
if (!existsSync68(file)) return null;
|
|
14817
14827
|
try {
|
|
14818
14828
|
return JSON.parse(await readFile23(file, "utf8"));
|
|
14819
14829
|
} catch {
|
|
@@ -14884,11 +14894,11 @@ function extractAbsoluteHaiveBins(text) {
|
|
|
14884
14894
|
}
|
|
14885
14895
|
|
|
14886
14896
|
// src/commands/playback.ts
|
|
14887
|
-
import { existsSync as
|
|
14897
|
+
import { existsSync as existsSync69 } from "fs";
|
|
14888
14898
|
import "commander";
|
|
14889
14899
|
import {
|
|
14890
14900
|
findProjectRoot as findProjectRoot46,
|
|
14891
|
-
loadMemoriesFromDir as
|
|
14901
|
+
loadMemoriesFromDir as loadMemoriesFromDir36,
|
|
14892
14902
|
parseSince as parseSince3,
|
|
14893
14903
|
readUsageEvents as readUsageEvents5,
|
|
14894
14904
|
resolveHaivePaths as resolveHaivePaths42
|
|
@@ -14914,7 +14924,7 @@ function registerPlayback(program2) {
|
|
|
14914
14924
|
const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
|
|
14915
14925
|
const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
|
|
14916
14926
|
const sessions = bucketSessions(filtered, gapMs);
|
|
14917
|
-
const all =
|
|
14927
|
+
const all = existsSync69(paths.memoriesDir) ? await loadMemoriesFromDir36(paths.memoriesDir) : [];
|
|
14918
14928
|
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);
|
|
14919
14929
|
const enriched = sessions.map((s, i) => {
|
|
14920
14930
|
const startMs = Date.parse(s.start);
|
|
@@ -15156,11 +15166,11 @@ function runCommand3(cmd, args, cwd) {
|
|
|
15156
15166
|
}
|
|
15157
15167
|
|
|
15158
15168
|
// src/commands/welcome.ts
|
|
15159
|
-
import { existsSync as
|
|
15169
|
+
import { existsSync as existsSync70 } from "fs";
|
|
15160
15170
|
import "commander";
|
|
15161
15171
|
import {
|
|
15162
15172
|
findProjectRoot as findProjectRoot48,
|
|
15163
|
-
loadMemoriesFromDir as
|
|
15173
|
+
loadMemoriesFromDir as loadMemoriesFromDir37,
|
|
15164
15174
|
resolveHaivePaths as resolveHaivePaths44
|
|
15165
15175
|
} from "@hiveai/core";
|
|
15166
15176
|
var TYPE_RANK = {
|
|
@@ -15178,12 +15188,12 @@ function registerWelcome(program2) {
|
|
|
15178
15188
|
).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
15179
15189
|
const root = findProjectRoot48(opts.dir);
|
|
15180
15190
|
const paths = resolveHaivePaths44(root);
|
|
15181
|
-
if (!
|
|
15191
|
+
if (!existsSync70(paths.memoriesDir)) {
|
|
15182
15192
|
ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
|
|
15183
15193
|
process.exitCode = 1;
|
|
15184
15194
|
return;
|
|
15185
15195
|
}
|
|
15186
|
-
const all = await
|
|
15196
|
+
const all = await loadMemoriesFromDir37(paths.memoriesDir);
|
|
15187
15197
|
const team = all.filter(
|
|
15188
15198
|
({ memory: memory2 }) => memory2.frontmatter.scope === "team" && memory2.frontmatter.status !== "rejected" && memory2.frontmatter.status !== "deprecated" && memory2.frontmatter.status !== "stale" && memory2.frontmatter.type !== "session_recap"
|
|
15189
15199
|
);
|
|
@@ -15269,7 +15279,7 @@ function registerResolveProject(program2) {
|
|
|
15269
15279
|
}
|
|
15270
15280
|
|
|
15271
15281
|
// src/commands/runtime-journal.ts
|
|
15272
|
-
import { existsSync as
|
|
15282
|
+
import { existsSync as existsSync71 } from "fs";
|
|
15273
15283
|
import path49 from "path";
|
|
15274
15284
|
import "commander";
|
|
15275
15285
|
import {
|
|
@@ -15295,7 +15305,7 @@ function registerRuntime(program2) {
|
|
|
15295
15305
|
const root = path49.resolve(opts.dir ?? process.cwd());
|
|
15296
15306
|
const paths = resolveHaivePaths45(findProjectRoot49(root));
|
|
15297
15307
|
const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
|
|
15298
|
-
if (!
|
|
15308
|
+
if (!existsSync71(paths.haiveDir)) {
|
|
15299
15309
|
ui.error("No .ai/ \u2014 run `haive init` first.");
|
|
15300
15310
|
process.exitCode = 1;
|
|
15301
15311
|
return;
|
|
@@ -15310,7 +15320,7 @@ function registerRuntime(program2) {
|
|
|
15310
15320
|
}
|
|
15311
15321
|
|
|
15312
15322
|
// src/commands/memory-timeline.ts
|
|
15313
|
-
import { existsSync as
|
|
15323
|
+
import { existsSync as existsSync73 } from "fs";
|
|
15314
15324
|
import path50 from "path";
|
|
15315
15325
|
import "commander";
|
|
15316
15326
|
import {
|
|
@@ -15329,13 +15339,13 @@ function registerMemoryTimeline(memory2) {
|
|
|
15329
15339
|
}
|
|
15330
15340
|
const root = path50.resolve(opts.dir ?? process.cwd());
|
|
15331
15341
|
const paths = resolveHaivePaths46(findProjectRoot50(root));
|
|
15332
|
-
if (!
|
|
15342
|
+
if (!existsSync73(paths.memoriesDir)) {
|
|
15333
15343
|
ui.error("No memories \u2014 run `haive init`.");
|
|
15334
15344
|
process.exitCode = 1;
|
|
15335
15345
|
return;
|
|
15336
15346
|
}
|
|
15337
15347
|
const limit = Math.min(100, Math.max(1, parseInt(opts.limit, 10) || 30));
|
|
15338
|
-
const all = await
|
|
15348
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
15339
15349
|
const { entries, notice } = collectTimelineEntries2(all, {
|
|
15340
15350
|
memoryId: opts.id,
|
|
15341
15351
|
topic: opts.topic,
|
|
@@ -15347,7 +15357,7 @@ function registerMemoryTimeline(memory2) {
|
|
|
15347
15357
|
}
|
|
15348
15358
|
|
|
15349
15359
|
// src/commands/memory-conflict-candidates.ts
|
|
15350
|
-
import { existsSync as
|
|
15360
|
+
import { existsSync as existsSync74 } from "fs";
|
|
15351
15361
|
import path51 from "path";
|
|
15352
15362
|
import "commander";
|
|
15353
15363
|
import {
|
|
@@ -15372,7 +15382,7 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
15372
15382
|
).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) => {
|
|
15373
15383
|
const root = path51.resolve(opts.dir ?? process.cwd());
|
|
15374
15384
|
const paths = resolveHaivePaths47(findProjectRoot51(root));
|
|
15375
|
-
if (!
|
|
15385
|
+
if (!existsSync74(paths.memoriesDir)) {
|
|
15376
15386
|
ui.error("No memories \u2014 run `haive init`.");
|
|
15377
15387
|
process.exitCode = 1;
|
|
15378
15388
|
return;
|
|
@@ -15382,7 +15392,7 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
15382
15392
|
const maxPairs = Math.min(100, Math.max(1, parseInt(opts.maxPairs, 10) || 20));
|
|
15383
15393
|
const maxScan = Math.min(2e3, Math.max(1, parseInt(opts.maxScan, 10) || 500));
|
|
15384
15394
|
const maxTopicPairs = Math.min(100, Math.max(1, parseInt(opts.maxTopicPairs, 10) || 20));
|
|
15385
|
-
const all = await
|
|
15395
|
+
const all = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
15386
15396
|
const lexical = findLexicalConflictPairs2(all, {
|
|
15387
15397
|
sinceDays,
|
|
15388
15398
|
types: parseTypes(opts.types),
|
|
@@ -15408,23 +15418,27 @@ function registerMemoryConflictCandidates(memory2) {
|
|
|
15408
15418
|
|
|
15409
15419
|
// src/commands/enforce.ts
|
|
15410
15420
|
import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
|
|
15411
|
-
import { existsSync as
|
|
15412
|
-
import { chmod as chmod2, mkdir as mkdir21, readFile as readFile24, readdir as readdir6, rm as rm3, writeFile as
|
|
15421
|
+
import { existsSync as existsSync75, statSync as statSync3 } from "fs";
|
|
15422
|
+
import { chmod as chmod2, mkdir as mkdir21, readFile as readFile24, readdir as readdir6, rm as rm3, writeFile as writeFile37 } from "fs/promises";
|
|
15413
15423
|
import path53 from "path";
|
|
15414
15424
|
import "commander";
|
|
15415
15425
|
import {
|
|
15416
15426
|
antiPatternGateParams as antiPatternGateParams2,
|
|
15427
|
+
BRIDGE_TARGET_PATH as BRIDGE_TARGET_PATH2,
|
|
15417
15428
|
findProjectRoot as findProjectRoot52,
|
|
15418
15429
|
findUncapturedFailures,
|
|
15419
15430
|
hasRecentBriefingMarker as hasRecentBriefingMarker2,
|
|
15420
15431
|
isFreshIsoDate,
|
|
15432
|
+
isRetiredMemory as isRetiredMemory3,
|
|
15421
15433
|
loadConfig as loadConfig14,
|
|
15422
|
-
loadMemoriesFromDir as
|
|
15434
|
+
loadMemoriesFromDir as loadMemoriesFromDir38,
|
|
15423
15435
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
|
|
15424
15436
|
readRecentBriefingMarker,
|
|
15425
15437
|
resolveBriefingBudget as resolveBriefingBudget3,
|
|
15426
15438
|
resolveHaivePaths as resolveHaivePaths48,
|
|
15439
|
+
runSensors as runSensors2,
|
|
15427
15440
|
saveConfig as saveConfig4,
|
|
15441
|
+
sensorTargetsFromDiff as sensorTargetsFromDiff2,
|
|
15428
15442
|
SESSION_RECAP_TTL_MS,
|
|
15429
15443
|
verifyAnchor as verifyAnchor4,
|
|
15430
15444
|
writeBriefingMarker as writeBriefingMarker3
|
|
@@ -15484,14 +15498,14 @@ function registerEnforce(program2) {
|
|
|
15484
15498
|
const root = findProjectRoot52(opts.dir);
|
|
15485
15499
|
const paths = resolveHaivePaths48(root);
|
|
15486
15500
|
const cacheDir = path53.join(paths.haiveDir, ".cache");
|
|
15487
|
-
if (
|
|
15501
|
+
if (existsSync75(cacheDir)) {
|
|
15488
15502
|
if (opts.dryRun) ui.info(`would clean ${path53.relative(root, cacheDir)} (preserving .gitignore)`);
|
|
15489
15503
|
else {
|
|
15490
15504
|
const removed = await cleanupCacheDir(cacheDir);
|
|
15491
15505
|
ui.success(`cleaned ${path53.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
|
|
15492
15506
|
}
|
|
15493
15507
|
}
|
|
15494
|
-
if (
|
|
15508
|
+
if (existsSync75(paths.runtimeDir)) {
|
|
15495
15509
|
if (opts.dryRun) ui.info(`would clean ${path53.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
|
|
15496
15510
|
else {
|
|
15497
15511
|
const removed = await cleanupRuntimeDir(paths.runtimeDir);
|
|
@@ -15529,7 +15543,7 @@ function registerEnforce(program2) {
|
|
|
15529
15543
|
const root = resolveRoot(opts.dir, payload);
|
|
15530
15544
|
if (!root) return;
|
|
15531
15545
|
const paths = resolveHaivePaths48(root);
|
|
15532
|
-
if (!
|
|
15546
|
+
if (!existsSync75(paths.haiveDir)) return;
|
|
15533
15547
|
await mkdir21(paths.runtimeDir, { recursive: true });
|
|
15534
15548
|
const sessionId = opts.sessionId ?? payload.session_id;
|
|
15535
15549
|
const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this hAIve-initialized project.";
|
|
@@ -15592,7 +15606,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
15592
15606
|
const root = resolveRoot(opts.dir, payload);
|
|
15593
15607
|
if (!root) return;
|
|
15594
15608
|
const paths = resolveHaivePaths48(root);
|
|
15595
|
-
if (!
|
|
15609
|
+
if (!existsSync75(paths.haiveDir)) return;
|
|
15596
15610
|
if (!isWriteLikeTool(payload)) return;
|
|
15597
15611
|
const config = await loadConfig14(paths);
|
|
15598
15612
|
if (config.enforcement?.requireBriefingFirst === false) return;
|
|
@@ -15657,7 +15671,7 @@ function emitPreToolUseContext(text) {
|
|
|
15657
15671
|
async function buildFinishReport(dir) {
|
|
15658
15672
|
const root = findProjectRoot52(dir);
|
|
15659
15673
|
const paths = resolveHaivePaths48(root);
|
|
15660
|
-
const initialized =
|
|
15674
|
+
const initialized = existsSync75(paths.haiveDir);
|
|
15661
15675
|
const config = initialized ? await loadConfig14(paths) : {};
|
|
15662
15676
|
const mode = config.enforcement?.mode ?? "strict";
|
|
15663
15677
|
const findings = [];
|
|
@@ -15849,7 +15863,7 @@ async function checkFailureCapture(paths, config) {
|
|
|
15849
15863
|
const gate = config.enforcement?.failureCaptureGate ?? "warn";
|
|
15850
15864
|
if (gate === "off") return [];
|
|
15851
15865
|
const obsFile = path53.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
15852
|
-
if (!
|
|
15866
|
+
if (!existsSync75(obsFile)) return [];
|
|
15853
15867
|
const failures = [];
|
|
15854
15868
|
try {
|
|
15855
15869
|
const raw = await readFile24(obsFile, "utf8");
|
|
@@ -15866,7 +15880,7 @@ async function checkFailureCapture(paths, config) {
|
|
|
15866
15880
|
return [];
|
|
15867
15881
|
}
|
|
15868
15882
|
if (failures.length === 0) return [];
|
|
15869
|
-
const memories =
|
|
15883
|
+
const memories = existsSync75(paths.memoriesDir) ? await loadMemoriesFromDir38(paths.memoriesDir) : [];
|
|
15870
15884
|
const captureTimes = memories.filter(({ memory: memory2 }) => ["attempt", "gotcha"].includes(memory2.frontmatter.type)).map(({ memory: memory2 }) => memory2.frontmatter.created_at);
|
|
15871
15885
|
const uncaptured = findUncapturedFailures(failures, captureTimes);
|
|
15872
15886
|
if (uncaptured.length === 0) {
|
|
@@ -15901,7 +15915,7 @@ function finishReport(root, initialized, mode, findings, config) {
|
|
|
15901
15915
|
async function runWithEnforcement(command, args, opts) {
|
|
15902
15916
|
const root = findProjectRoot52(opts.dir);
|
|
15903
15917
|
const paths = resolveHaivePaths48(root);
|
|
15904
|
-
if (!
|
|
15918
|
+
if (!existsSync75(paths.haiveDir)) {
|
|
15905
15919
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
15906
15920
|
process.exit(1);
|
|
15907
15921
|
}
|
|
@@ -15990,13 +16004,13 @@ async function writeWrapperBriefing(paths, sessionId, task) {
|
|
|
15990
16004
|
if (briefing.setup_warnings.length > 0) {
|
|
15991
16005
|
parts.push("", "## Setup Warnings", ...briefing.setup_warnings.map((w) => `- ${w}`));
|
|
15992
16006
|
}
|
|
15993
|
-
await
|
|
16007
|
+
await writeFile37(file, parts.join("\n") + "\n", "utf8");
|
|
15994
16008
|
return file;
|
|
15995
16009
|
}
|
|
15996
16010
|
async function buildEnforcementReport(dir, stage, sessionId) {
|
|
15997
16011
|
const root = findProjectRoot52(dir);
|
|
15998
16012
|
const paths = resolveHaivePaths48(root);
|
|
15999
|
-
const initialized =
|
|
16013
|
+
const initialized = existsSync75(paths.haiveDir);
|
|
16000
16014
|
const config = initialized ? await loadConfig14(paths) : {};
|
|
16001
16015
|
if (initialized) {
|
|
16002
16016
|
await applyLightweightRepairs(root, paths);
|
|
@@ -16030,7 +16044,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
16030
16044
|
findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
|
|
16031
16045
|
});
|
|
16032
16046
|
}
|
|
16033
|
-
findings.push(...await inspectIntegrationVersions(root, "0.
|
|
16047
|
+
findings.push(...await inspectIntegrationVersions(root, "0.20.0"));
|
|
16034
16048
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
16035
16049
|
const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
|
|
16036
16050
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
|
|
@@ -16100,8 +16114,8 @@ function withCategories(report) {
|
|
|
16100
16114
|
};
|
|
16101
16115
|
}
|
|
16102
16116
|
async function hasRecentSessionRecap(paths) {
|
|
16103
|
-
if (!
|
|
16104
|
-
const all = await
|
|
16117
|
+
if (!existsSync75(paths.memoriesDir)) return false;
|
|
16118
|
+
const all = await loadMemoriesFromDir38(paths.memoriesDir);
|
|
16105
16119
|
return all.some(({ memory: memory2 }) => {
|
|
16106
16120
|
const fm = memory2.frontmatter;
|
|
16107
16121
|
const freshnessDate = fm.verified_at ?? fm.created_at;
|
|
@@ -16109,8 +16123,8 @@ async function hasRecentSessionRecap(paths) {
|
|
|
16109
16123
|
});
|
|
16110
16124
|
}
|
|
16111
16125
|
async function verifyMemoryPolicy(paths, config) {
|
|
16112
|
-
if (!
|
|
16113
|
-
const all = await
|
|
16126
|
+
if (!existsSync75(paths.memoriesDir)) return [];
|
|
16127
|
+
const all = await loadMemoriesFromDir38(paths.memoriesDir);
|
|
16114
16128
|
const findings = [];
|
|
16115
16129
|
const staleImportant = [];
|
|
16116
16130
|
let verified = 0;
|
|
@@ -16147,12 +16161,12 @@ async function verifyMemoryPolicy(paths, config) {
|
|
|
16147
16161
|
return findings;
|
|
16148
16162
|
}
|
|
16149
16163
|
async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
16150
|
-
if (!
|
|
16164
|
+
if (!existsSync75(paths.memoriesDir)) return [];
|
|
16151
16165
|
const changedFiles = (await getChangedFiles(paths.root, stage)).filter((f) => !isGeneratedArtifact(f));
|
|
16152
16166
|
if (changedFiles.length === 0) {
|
|
16153
16167
|
return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
|
|
16154
16168
|
}
|
|
16155
|
-
const all = await
|
|
16169
|
+
const all = await loadMemoriesFromDir38(paths.memoriesDir);
|
|
16156
16170
|
const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention"]);
|
|
16157
16171
|
const relevant = all.map(({ memory: memory2 }) => memory2).filter((memory2) => {
|
|
16158
16172
|
const fm = memory2.frontmatter;
|
|
@@ -16216,20 +16230,83 @@ async function runPrecommitPolicy(paths, gate, stage) {
|
|
|
16216
16230
|
anchored_blocks,
|
|
16217
16231
|
semantic: true
|
|
16218
16232
|
}, { paths });
|
|
16233
|
+
const sensorFindings = await runSensorGate(paths, snapshot.diff);
|
|
16219
16234
|
if (!result.should_block) {
|
|
16220
|
-
return [
|
|
16221
|
-
|
|
16222
|
-
|
|
16223
|
-
|
|
16224
|
-
|
|
16235
|
+
return [
|
|
16236
|
+
{
|
|
16237
|
+
severity: "ok",
|
|
16238
|
+
code: "precommit-policy-pass",
|
|
16239
|
+
message: `${stage === "ci" ? "CI" : "Pre-commit"} policy passed for ${touchedPaths.length} changed file(s).`
|
|
16240
|
+
},
|
|
16241
|
+
...sensorFindings
|
|
16242
|
+
];
|
|
16243
|
+
}
|
|
16244
|
+
return [
|
|
16245
|
+
{
|
|
16246
|
+
severity: "error",
|
|
16247
|
+
code: "precommit-policy-block",
|
|
16248
|
+
message: `Pre-commit policy matched ${result.summary.blocking_warnings ?? result.summary.anti_patterns} blocking anti-pattern(s), ${result.summary.stale_anchors} stale anchor(s).`,
|
|
16249
|
+
fix: "Review the hAIve warnings, then update the code or the relevant memories.",
|
|
16250
|
+
impact: 45
|
|
16251
|
+
},
|
|
16252
|
+
...sensorFindings
|
|
16253
|
+
];
|
|
16254
|
+
}
|
|
16255
|
+
var SENSOR_OWNED_FILES = /* @__PURE__ */ new Set([
|
|
16256
|
+
...Object.values(BRIDGE_TARGET_PATH2),
|
|
16257
|
+
"CLAUDE.md",
|
|
16258
|
+
".cursorrules",
|
|
16259
|
+
".gitignore",
|
|
16260
|
+
".mcp.json",
|
|
16261
|
+
".cursor/mcp.json",
|
|
16262
|
+
".vscode/mcp.json",
|
|
16263
|
+
".cursor/rules/haive-mcp-required.mdc"
|
|
16264
|
+
]);
|
|
16265
|
+
function isSensorScannablePath(p) {
|
|
16266
|
+
if (!p) return false;
|
|
16267
|
+
if (p.startsWith(".ai/")) return false;
|
|
16268
|
+
return !SENSOR_OWNED_FILES.has(p);
|
|
16269
|
+
}
|
|
16270
|
+
async function runSensorGate(paths, diff) {
|
|
16271
|
+
if (!diff || !existsSync75(paths.memoriesDir)) return [];
|
|
16272
|
+
try {
|
|
16273
|
+
const loaded = await loadMemoriesFromDir38(paths.memoriesDir);
|
|
16274
|
+
const sensorMemories = loaded.map((l) => l.memory).filter((m) => {
|
|
16275
|
+
const s = m.frontmatter.sensor;
|
|
16276
|
+
return Boolean(s) && s.kind === "regex" && !isRetiredMemory3(m.frontmatter, m.body);
|
|
16277
|
+
});
|
|
16278
|
+
if (sensorMemories.length === 0) return [];
|
|
16279
|
+
const targets = sensorTargetsFromDiff2(diff).filter((t) => isSensorScannablePath(t.path));
|
|
16280
|
+
if (targets.length === 0) return [];
|
|
16281
|
+
const hits = runSensors2(sensorMemories, targets);
|
|
16282
|
+
const findings = [];
|
|
16283
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16284
|
+
for (const hit of hits) {
|
|
16285
|
+
if (seen.has(hit.memory_id)) continue;
|
|
16286
|
+
seen.add(hit.memory_id);
|
|
16287
|
+
const where = hit.file ? ` (${hit.file})` : "";
|
|
16288
|
+
if (hit.severity === "block") {
|
|
16289
|
+
findings.push({
|
|
16290
|
+
severity: "error",
|
|
16291
|
+
code: "sensor-block",
|
|
16292
|
+
message: `Block sensor fired \u2014 ${hit.memory_id}: ${hit.message}${where}`,
|
|
16293
|
+
fix: "Remove the flagged pattern, or run `haive sensors check` to inspect the match.",
|
|
16294
|
+
impact: 45
|
|
16295
|
+
});
|
|
16296
|
+
} else {
|
|
16297
|
+
findings.push({
|
|
16298
|
+
severity: "warn",
|
|
16299
|
+
code: "sensor-warn",
|
|
16300
|
+
message: `Sensor flagged ${hit.memory_id}: ${hit.message}${where}`,
|
|
16301
|
+
fix: "Review the flagged line; `haive sensors check` shows the matched code.",
|
|
16302
|
+
impact: 5
|
|
16303
|
+
});
|
|
16304
|
+
}
|
|
16305
|
+
}
|
|
16306
|
+
return findings;
|
|
16307
|
+
} catch {
|
|
16308
|
+
return [];
|
|
16225
16309
|
}
|
|
16226
|
-
return [{
|
|
16227
|
-
severity: "error",
|
|
16228
|
-
code: "precommit-policy-block",
|
|
16229
|
-
message: `Pre-commit policy matched ${result.summary.blocking_warnings ?? result.summary.anti_patterns} blocking anti-pattern(s), ${result.summary.stale_anchors} stale anchor(s).`,
|
|
16230
|
-
fix: "Review the hAIve warnings, then update the code or the relevant memories.",
|
|
16231
|
-
impact: 45
|
|
16232
|
-
}];
|
|
16233
16310
|
}
|
|
16234
16311
|
async function findGeneratedArtifacts(paths) {
|
|
16235
16312
|
const dirty = await runCommand4("git", ["status", "--short", "--untracked-files=all"], paths.root).catch(() => "");
|
|
@@ -16267,9 +16344,9 @@ async function cleanupRuntimeDir(runtimeDir) {
|
|
|
16267
16344
|
await rm3(path53.join(runtimeDir, entry.name), { recursive: true, force: true });
|
|
16268
16345
|
removed++;
|
|
16269
16346
|
}
|
|
16270
|
-
await
|
|
16271
|
-
if (!
|
|
16272
|
-
await
|
|
16347
|
+
await writeFile37(path53.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
|
|
16348
|
+
if (!existsSync75(path53.join(runtimeDir, "README.md"))) {
|
|
16349
|
+
await writeFile37(
|
|
16273
16350
|
path53.join(runtimeDir, "README.md"),
|
|
16274
16351
|
"# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. hAIve cleanup preserves briefing markers so enforcement state remains valid.\n",
|
|
16275
16352
|
"utf8"
|
|
@@ -16286,7 +16363,7 @@ async function cleanupCacheDir(cacheDir) {
|
|
|
16286
16363
|
await rm3(path53.join(cacheDir, entry.name), { recursive: true, force: true });
|
|
16287
16364
|
removed++;
|
|
16288
16365
|
}
|
|
16289
|
-
await
|
|
16366
|
+
await writeFile37(path53.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
|
|
16290
16367
|
return removed;
|
|
16291
16368
|
}
|
|
16292
16369
|
async function cleanupEnforcementDir(enforcementDir) {
|
|
@@ -16311,7 +16388,7 @@ async function inspectIntegrationVersions(root, expectedVersion) {
|
|
|
16311
16388
|
const findings = [];
|
|
16312
16389
|
for (const rel of files) {
|
|
16313
16390
|
const file = path53.join(root, rel);
|
|
16314
|
-
if (!
|
|
16391
|
+
if (!existsSync75(file)) continue;
|
|
16315
16392
|
const text = await readFile24(file, "utf8").catch(() => "");
|
|
16316
16393
|
for (const bin of extractAbsoluteHaiveBins2(text)) {
|
|
16317
16394
|
const version = versionForBinary2(bin);
|
|
@@ -16421,7 +16498,7 @@ async function resolveCiDiffRange(root) {
|
|
|
16421
16498
|
}
|
|
16422
16499
|
async function resolveGithubEventRange(root) {
|
|
16423
16500
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
16424
|
-
if (!eventPath || !
|
|
16501
|
+
if (!eventPath || !existsSync75(eventPath)) return null;
|
|
16425
16502
|
try {
|
|
16426
16503
|
const event = JSON.parse(await readFile24(eventPath, "utf8"));
|
|
16427
16504
|
const prBase = cleanGitSha(event.pull_request?.base?.sha);
|
|
@@ -16744,7 +16821,7 @@ function buildScore(findings, threshold = 80) {
|
|
|
16744
16821
|
}
|
|
16745
16822
|
async function installGitEnforcement(root) {
|
|
16746
16823
|
const hooksDir = path53.join(root, ".git", "hooks");
|
|
16747
|
-
if (!
|
|
16824
|
+
if (!existsSync75(path53.join(root, ".git"))) {
|
|
16748
16825
|
ui.warn("No .git directory found; git enforcement hooks skipped.");
|
|
16749
16826
|
return;
|
|
16750
16827
|
}
|
|
@@ -16774,17 +16851,17 @@ haive enforce commit-msg "$1" --dir . || exit $?
|
|
|
16774
16851
|
];
|
|
16775
16852
|
for (const hook of hooks) {
|
|
16776
16853
|
const file = path53.join(hooksDir, hook.name);
|
|
16777
|
-
if (
|
|
16854
|
+
if (existsSync75(file)) {
|
|
16778
16855
|
const current = await readFile24(file, "utf8").catch(() => "");
|
|
16779
16856
|
if (current.includes(ENFORCE_HOOK_MARKER)) {
|
|
16780
|
-
await
|
|
16857
|
+
await writeFile37(file, hook.body, "utf8");
|
|
16781
16858
|
} else {
|
|
16782
|
-
await
|
|
16859
|
+
await writeFile37(file, `${current.trimEnd()}
|
|
16783
16860
|
|
|
16784
16861
|
${hook.body}`, "utf8");
|
|
16785
16862
|
}
|
|
16786
16863
|
} else {
|
|
16787
|
-
await
|
|
16864
|
+
await writeFile37(file, hook.body, "utf8");
|
|
16788
16865
|
}
|
|
16789
16866
|
await chmod2(file, 493);
|
|
16790
16867
|
}
|
|
@@ -16793,11 +16870,11 @@ ${hook.body}`, "utf8");
|
|
|
16793
16870
|
async function installCiEnforcement(root) {
|
|
16794
16871
|
const workflowPath = path53.join(root, ".github", "workflows", "haive-enforcement.yml");
|
|
16795
16872
|
await mkdir21(path53.dirname(workflowPath), { recursive: true });
|
|
16796
|
-
if (
|
|
16873
|
+
if (existsSync75(workflowPath)) {
|
|
16797
16874
|
ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
|
|
16798
16875
|
return;
|
|
16799
16876
|
}
|
|
16800
|
-
await
|
|
16877
|
+
await writeFile37(workflowPath, `name: haive-enforcement
|
|
16801
16878
|
|
|
16802
16879
|
on:
|
|
16803
16880
|
pull_request:
|
|
@@ -16932,11 +17009,11 @@ function normalizeToolPath(file, root) {
|
|
|
16932
17009
|
return path53.relative(root, normalized).replace(/\\/g, "/");
|
|
16933
17010
|
}
|
|
16934
17011
|
async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
|
|
16935
|
-
if (!
|
|
17012
|
+
if (!existsSync75(paths.memoriesDir)) return [];
|
|
16936
17013
|
const marker = await readRecentBriefingMarker(paths, sessionId);
|
|
16937
17014
|
const consulted = new Set(marker?.memory_ids ?? []);
|
|
16938
17015
|
const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention", "attempt"]);
|
|
16939
|
-
const all = await
|
|
17016
|
+
const all = await loadMemoriesFromDir38(paths.memoriesDir);
|
|
16940
17017
|
return all.filter(({ memory: memory2 }) => {
|
|
16941
17018
|
const fm = memory2.frontmatter;
|
|
16942
17019
|
if (!policyTypes.has(fm.type)) return false;
|
|
@@ -17017,8 +17094,8 @@ function registerRun(program2) {
|
|
|
17017
17094
|
|
|
17018
17095
|
// src/commands/sensors.ts
|
|
17019
17096
|
import { execFile as execFile3 } from "child_process";
|
|
17020
|
-
import { existsSync as
|
|
17021
|
-
import { chmod as chmod3, mkdir as mkdir23, readFile as readFile25, writeFile as
|
|
17097
|
+
import { existsSync as existsSync76 } from "fs";
|
|
17098
|
+
import { chmod as chmod3, mkdir as mkdir23, readFile as readFile25, writeFile as writeFile38 } from "fs/promises";
|
|
17022
17099
|
import path54 from "path";
|
|
17023
17100
|
import { promisify as promisify3 } from "util";
|
|
17024
17101
|
import "commander";
|
|
@@ -17027,14 +17104,14 @@ import {
|
|
|
17027
17104
|
findProjectRoot as findProjectRoot53,
|
|
17028
17105
|
isRetiredMemory as isRetiredMemory4,
|
|
17029
17106
|
loadConfig as loadConfig15,
|
|
17030
|
-
loadMemoriesFromDir as
|
|
17107
|
+
loadMemoriesFromDir as loadMemoriesFromDir39,
|
|
17031
17108
|
loadUsageIndex as loadUsageIndex31,
|
|
17032
17109
|
recordPrevention as recordPrevention2,
|
|
17033
17110
|
resolveHaivePaths as resolveHaivePaths49,
|
|
17034
|
-
runSensors as
|
|
17111
|
+
runSensors as runSensors3,
|
|
17035
17112
|
saveUsageIndex as saveUsageIndex8,
|
|
17036
17113
|
selectCommandSensors,
|
|
17037
|
-
sensorTargetsFromDiff as
|
|
17114
|
+
sensorTargetsFromDiff as sensorTargetsFromDiff3,
|
|
17038
17115
|
serializeMemory as serializeMemory29
|
|
17039
17116
|
} from "@hiveai/core";
|
|
17040
17117
|
var exec2 = promisify3(execFile3);
|
|
@@ -17068,8 +17145,8 @@ function registerSensors(program2) {
|
|
|
17068
17145
|
const paths = resolveHaivePaths49(root);
|
|
17069
17146
|
const memories = await runnableSensorMemories(paths);
|
|
17070
17147
|
const diff = opts.diffFile ? await readFile25(path54.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
|
|
17071
|
-
const targets =
|
|
17072
|
-
const hits =
|
|
17148
|
+
const targets = sensorTargetsFromDiff3(diff);
|
|
17149
|
+
const hits = runSensors3(memories, targets.length > 0 ? targets : [{ path: "", content: diff }]);
|
|
17073
17150
|
const config = await loadConfig15(paths);
|
|
17074
17151
|
const runCommands = opts.commands || config.enforcement?.runCommandSensors === true;
|
|
17075
17152
|
const changedPaths = targets.map((t) => t.path).filter(Boolean);
|
|
@@ -17157,7 +17234,7 @@ function registerSensors(program2) {
|
|
|
17157
17234
|
}
|
|
17158
17235
|
const root = findProjectRoot53(opts.dir);
|
|
17159
17236
|
const paths = resolveHaivePaths49(root);
|
|
17160
|
-
const loaded =
|
|
17237
|
+
const loaded = existsSync76(paths.memoriesDir) ? await loadMemoriesFromDir39(paths.memoriesDir) : [];
|
|
17161
17238
|
const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
|
|
17162
17239
|
if (!found) {
|
|
17163
17240
|
ui.error(`No memory found with id ${id}`);
|
|
@@ -17177,7 +17254,7 @@ function registerSensors(program2) {
|
|
|
17177
17254
|
},
|
|
17178
17255
|
body: found.memory.body
|
|
17179
17256
|
};
|
|
17180
|
-
await
|
|
17257
|
+
await writeFile38(found.filePath, serializeMemory29(next), "utf8");
|
|
17181
17258
|
ui.success(`Updated ${id}: sensor severity=${severity}`);
|
|
17182
17259
|
if (sensor.pattern) ui.info(`pattern=${JSON.stringify(sensor.pattern)}`);
|
|
17183
17260
|
ui.info(`message=${sensor.message}`);
|
|
@@ -17196,7 +17273,7 @@ function registerSensors(program2) {
|
|
|
17196
17273
|
await mkdir23(outDir, { recursive: true });
|
|
17197
17274
|
const outPath = path54.join(outDir, format === "grep" ? "haive-sensors-grep.sh" : "haive-sensors-eslint.json");
|
|
17198
17275
|
const content = format === "grep" ? renderGrepScript(rows) : JSON.stringify({ sensors: rows }, null, 2) + "\n";
|
|
17199
|
-
await
|
|
17276
|
+
await writeFile38(outPath, content, "utf8");
|
|
17200
17277
|
if (format === "grep") await chmod3(outPath, 493);
|
|
17201
17278
|
ui.success(`Exported ${rows.length} sensor(s): ${path54.relative(root, outPath)}`);
|
|
17202
17279
|
});
|
|
@@ -17219,8 +17296,8 @@ async function sensorRows(paths) {
|
|
|
17219
17296
|
});
|
|
17220
17297
|
}
|
|
17221
17298
|
async function runnableSensorMemories(paths, regexOnly = true) {
|
|
17222
|
-
if (!
|
|
17223
|
-
const loaded = await
|
|
17299
|
+
if (!existsSync76(paths.memoriesDir)) return [];
|
|
17300
|
+
const loaded = await loadMemoriesFromDir39(paths.memoriesDir);
|
|
17224
17301
|
return loaded.map(({ memory: memory2 }) => memory2).filter((memory2) => {
|
|
17225
17302
|
const sensor = memory2.frontmatter.sensor;
|
|
17226
17303
|
if (!sensor) return false;
|
|
@@ -17269,15 +17346,15 @@ function shellQuote(value) {
|
|
|
17269
17346
|
}
|
|
17270
17347
|
|
|
17271
17348
|
// src/commands/ingest.ts
|
|
17272
|
-
import { existsSync as
|
|
17273
|
-
import { mkdir as mkdir24, readFile as readFile26, writeFile as
|
|
17349
|
+
import { existsSync as existsSync77 } from "fs";
|
|
17350
|
+
import { mkdir as mkdir24, readFile as readFile26, writeFile as writeFile39 } from "fs/promises";
|
|
17274
17351
|
import path55 from "path";
|
|
17275
17352
|
import "commander";
|
|
17276
17353
|
import {
|
|
17277
17354
|
draftsFromFindings as draftsFromFindings2,
|
|
17278
17355
|
filterNewDrafts as filterNewDrafts2,
|
|
17279
17356
|
findProjectRoot as findProjectRoot54,
|
|
17280
|
-
loadMemoriesFromDir as
|
|
17357
|
+
loadMemoriesFromDir as loadMemoriesFromDir40,
|
|
17281
17358
|
memoryFilePath as memoryFilePath12,
|
|
17282
17359
|
parseFindings as parseFindings2,
|
|
17283
17360
|
resolveHaivePaths as resolveHaivePaths50,
|
|
@@ -17307,7 +17384,7 @@ function registerIngest(program2) {
|
|
|
17307
17384
|
}
|
|
17308
17385
|
const root = findProjectRoot54(opts.dir);
|
|
17309
17386
|
const paths = resolveHaivePaths50(root);
|
|
17310
|
-
if (!
|
|
17387
|
+
if (!existsSync77(paths.haiveDir)) {
|
|
17311
17388
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
17312
17389
|
process.exitCode = 1;
|
|
17313
17390
|
return;
|
|
@@ -17329,7 +17406,7 @@ function registerIngest(program2) {
|
|
|
17329
17406
|
return;
|
|
17330
17407
|
}
|
|
17331
17408
|
const reportPath = path55.resolve(root, file);
|
|
17332
|
-
if (!
|
|
17409
|
+
if (!existsSync77(reportPath)) {
|
|
17333
17410
|
ui.error(`Report file not found: ${reportPath}`);
|
|
17334
17411
|
process.exitCode = 1;
|
|
17335
17412
|
return;
|
|
@@ -17358,7 +17435,7 @@ function registerIngest(program2) {
|
|
|
17358
17435
|
process.exitCode = 1;
|
|
17359
17436
|
return;
|
|
17360
17437
|
}
|
|
17361
|
-
const existing =
|
|
17438
|
+
const existing = existsSync77(paths.memoriesDir) ? await loadMemoriesFromDir40(paths.memoriesDir) : [];
|
|
17362
17439
|
const existingTopics = new Set(
|
|
17363
17440
|
existing.map(({ memory: memory2 }) => memory2.frontmatter.topic).filter((t) => Boolean(t))
|
|
17364
17441
|
);
|
|
@@ -17424,7 +17501,7 @@ function registerIngest(program2) {
|
|
|
17424
17501
|
async function writeDraft2(paths, draft) {
|
|
17425
17502
|
const file = memoryFilePath12(paths, draft.frontmatter.scope, draft.frontmatter.id, draft.frontmatter.module);
|
|
17426
17503
|
await mkdir24(path55.dirname(file), { recursive: true });
|
|
17427
|
-
await
|
|
17504
|
+
await writeFile39(file, serializeMemory30({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
|
|
17428
17505
|
return file;
|
|
17429
17506
|
}
|
|
17430
17507
|
async function fetchSonarIssues(opts) {
|
|
@@ -17465,13 +17542,13 @@ async function fetchSonarIssues(opts) {
|
|
|
17465
17542
|
}
|
|
17466
17543
|
|
|
17467
17544
|
// src/commands/dashboard.ts
|
|
17468
|
-
import { existsSync as
|
|
17545
|
+
import { existsSync as existsSync78 } from "fs";
|
|
17469
17546
|
import "commander";
|
|
17470
17547
|
import {
|
|
17471
17548
|
buildDashboard,
|
|
17472
17549
|
findProjectRoot as findProjectRoot55,
|
|
17473
17550
|
loadConfig as loadConfig16,
|
|
17474
|
-
loadMemoriesFromDir as
|
|
17551
|
+
loadMemoriesFromDir as loadMemoriesFromDir41,
|
|
17475
17552
|
loadPreventionEvents as loadPreventionEvents4,
|
|
17476
17553
|
loadUsageIndex as loadUsageIndex33,
|
|
17477
17554
|
resolveHaivePaths as resolveHaivePaths51
|
|
@@ -17482,12 +17559,12 @@ function registerDashboard(program2) {
|
|
|
17482
17559
|
).option("--json", "emit the full report as JSON", false).option("--top <n>", "rows per top-list", "10").option("--dormant-days <n>", "dormancy window for impact scoring").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
17483
17560
|
const root = findProjectRoot55(opts.dir);
|
|
17484
17561
|
const paths = resolveHaivePaths51(root);
|
|
17485
|
-
if (!
|
|
17562
|
+
if (!existsSync78(paths.haiveDir)) {
|
|
17486
17563
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
17487
17564
|
process.exitCode = 1;
|
|
17488
17565
|
return;
|
|
17489
17566
|
}
|
|
17490
|
-
const memories =
|
|
17567
|
+
const memories = existsSync78(paths.memoriesDir) ? await loadMemoriesFromDir41(paths.memoriesDir) : [];
|
|
17491
17568
|
const usage = await loadUsageIndex33(paths);
|
|
17492
17569
|
const preventionEvents = await loadPreventionEvents4(paths);
|
|
17493
17570
|
const config = await loadConfig16(paths);
|
|
@@ -17601,7 +17678,7 @@ function warnNum(n) {
|
|
|
17601
17678
|
// src/commands/dev-link.ts
|
|
17602
17679
|
import { execFile as execFile4 } from "child_process";
|
|
17603
17680
|
import { cp, readFile as readFile27 } from "fs/promises";
|
|
17604
|
-
import { existsSync as
|
|
17681
|
+
import { existsSync as existsSync79 } from "fs";
|
|
17605
17682
|
import path56 from "path";
|
|
17606
17683
|
import { promisify as promisify4 } from "util";
|
|
17607
17684
|
import "commander";
|
|
@@ -17611,7 +17688,7 @@ function registerDevLink(program2) {
|
|
|
17611
17688
|
const dev = program2.commands.find((c) => c.name() === "dev") ?? program2.command("dev").description("Developer utilities for working on hAIve itself.");
|
|
17612
17689
|
dev.command("link").description("Hot-swap this repo's built dist into the global @hiveai install so `haive` runs your local code.").option("-d, --dir <dir>", "repo root (default: discovered from cwd)").option("--json", "emit a machine-readable summary", false).action(async (opts) => {
|
|
17613
17690
|
const root = findProjectRoot56(opts.dir);
|
|
17614
|
-
if (!
|
|
17691
|
+
if (!existsSync79(path56.join(root, "packages", "cli", "dist", "index.js"))) {
|
|
17615
17692
|
ui.error(`Not the hAIve monorepo (no packages/cli/dist) at ${root}. Run \`pnpm -r build\` first, or pass --dir.`);
|
|
17616
17693
|
process.exitCode = 1;
|
|
17617
17694
|
return;
|
|
@@ -17623,7 +17700,7 @@ function registerDevLink(program2) {
|
|
|
17623
17700
|
globalModules = path56.join(path56.dirname(path56.dirname(process.execPath)), "lib", "node_modules");
|
|
17624
17701
|
}
|
|
17625
17702
|
const globalHive = path56.join(globalModules, "@hiveai");
|
|
17626
|
-
if (!
|
|
17703
|
+
if (!existsSync79(globalHive)) {
|
|
17627
17704
|
ui.error(`No global @hiveai install at ${globalHive}. Install once with \`npm i -g @hiveai/cli\`, then re-run.`);
|
|
17628
17705
|
process.exitCode = 1;
|
|
17629
17706
|
return;
|
|
@@ -17631,7 +17708,7 @@ function registerDevLink(program2) {
|
|
|
17631
17708
|
const linked = [];
|
|
17632
17709
|
const copyDist = async (fromPkg, toDistDir) => {
|
|
17633
17710
|
const from = path56.join(root, "packages", fromPkg, "dist");
|
|
17634
|
-
if (!
|
|
17711
|
+
if (!existsSync79(from) || !existsSync79(path56.dirname(toDistDir))) return;
|
|
17635
17712
|
await cp(from, toDistDir, { recursive: true });
|
|
17636
17713
|
linked.push(path56.relative(globalModules, toDistDir));
|
|
17637
17714
|
};
|
|
@@ -17690,7 +17767,7 @@ function registerCoverage(program2) {
|
|
|
17690
17767
|
maxHotFiles: 500
|
|
17691
17768
|
});
|
|
17692
17769
|
const hotFiles = radar.hotFiles.filter((h) => !isNoisePath(h.path)).map((h) => ({ path: h.path, changes: h.changes }));
|
|
17693
|
-
const memories = await
|
|
17770
|
+
const memories = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
17694
17771
|
const gaps = findCoverageGaps(hotFiles, memories, { minChanges, limit });
|
|
17695
17772
|
if (opts.json) {
|
|
17696
17773
|
console.log(JSON.stringify({ root, scanned_hot_files: hotFiles.length, gaps }, null, 2));
|
|
@@ -17718,7 +17795,7 @@ function registerCoverage(program2) {
|
|
|
17718
17795
|
|
|
17719
17796
|
// src/commands/merge-driver.ts
|
|
17720
17797
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
17721
|
-
import { readFileSync, writeFileSync, existsSync as
|
|
17798
|
+
import { readFileSync, writeFileSync, existsSync as existsSync80 } from "fs";
|
|
17722
17799
|
import path57 from "path";
|
|
17723
17800
|
import "commander";
|
|
17724
17801
|
import { findProjectRoot as findProjectRoot58, mergeMemoryVersions } from "@hiveai/core";
|
|
@@ -17752,7 +17829,7 @@ function registerMergeDriver(program2) {
|
|
|
17752
17829
|
return;
|
|
17753
17830
|
}
|
|
17754
17831
|
const gaPath = path57.join(root, ".gitattributes");
|
|
17755
|
-
let content =
|
|
17832
|
+
let content = existsSync80(gaPath) ? readFileSync(gaPath, "utf8") : "";
|
|
17756
17833
|
if (!content.includes(GITATTRIBUTES_MARK)) {
|
|
17757
17834
|
if (content.length > 0 && !content.endsWith("\n")) content += "\n";
|
|
17758
17835
|
content += GITATTRIBUTES_BLOCK + "\n";
|
|
@@ -17766,8 +17843,8 @@ function registerMergeDriver(program2) {
|
|
|
17766
17843
|
}
|
|
17767
17844
|
|
|
17768
17845
|
// src/commands/memory-resolve-conflict.ts
|
|
17769
|
-
import { writeFile as
|
|
17770
|
-
import { existsSync as
|
|
17846
|
+
import { writeFile as writeFile40 } from "fs/promises";
|
|
17847
|
+
import { existsSync as existsSync81 } from "fs";
|
|
17771
17848
|
import "commander";
|
|
17772
17849
|
import {
|
|
17773
17850
|
findProjectRoot as findProjectRoot59,
|
|
@@ -17779,12 +17856,12 @@ function registerMemoryResolveConflict(memory2) {
|
|
|
17779
17856
|
memory2.command("resolve-conflict <id_a> <id_b>").description("Resolve a contradiction: keep the stronger memory, deprecate (supersede) the other").option("--yes", "apply the resolution (without this, only previews it)", false).option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (idA, idB, opts) => {
|
|
17780
17857
|
const root = findProjectRoot59(opts.dir);
|
|
17781
17858
|
const paths = resolveHaivePaths53(root);
|
|
17782
|
-
if (!
|
|
17859
|
+
if (!existsSync81(paths.memoriesDir)) {
|
|
17783
17860
|
ui.error(`No .ai/memories at ${root}.`);
|
|
17784
17861
|
process.exitCode = 1;
|
|
17785
17862
|
return;
|
|
17786
17863
|
}
|
|
17787
|
-
const memories = await
|
|
17864
|
+
const memories = await loadMemoriesFromDir27(paths.memoriesDir);
|
|
17788
17865
|
const a = memories.find((m) => m.memory.frontmatter.id === idA);
|
|
17789
17866
|
const b = memories.find((m) => m.memory.frontmatter.id === idB);
|
|
17790
17867
|
if (!a || !b) {
|
|
@@ -17806,7 +17883,7 @@ function registerMemoryResolveConflict(memory2) {
|
|
|
17806
17883
|
if (!opts.json) ui.info("Preview only \u2014 re-run with --yes to apply.");
|
|
17807
17884
|
return;
|
|
17808
17885
|
}
|
|
17809
|
-
await
|
|
17886
|
+
await writeFile40(
|
|
17810
17887
|
loser.filePath,
|
|
17811
17888
|
serializeMemory31({
|
|
17812
17889
|
frontmatter: {
|
|
@@ -17825,8 +17902,8 @@ function registerMemoryResolveConflict(memory2) {
|
|
|
17825
17902
|
|
|
17826
17903
|
// src/commands/memory-seed-git.ts
|
|
17827
17904
|
import { execFile as execFile5 } from "child_process";
|
|
17828
|
-
import { mkdir as mkdir25, writeFile as
|
|
17829
|
-
import { existsSync as
|
|
17905
|
+
import { mkdir as mkdir25, writeFile as writeFile41 } from "fs/promises";
|
|
17906
|
+
import { existsSync as existsSync83 } from "fs";
|
|
17830
17907
|
import path58 from "path";
|
|
17831
17908
|
import { promisify as promisify5 } from "util";
|
|
17832
17909
|
import "commander";
|
|
@@ -17843,7 +17920,7 @@ function registerMemorySeedGit(memory2) {
|
|
|
17843
17920
|
memory2.command("seed-git").description("Propose draft `attempt` seeds from revert/hotfix commits in git history (cold-start)").option("--apply", "write the proposed seeds as draft memories (default: preview only)", false).option("--limit <n>", "max seeds to propose", "20").option("--days <n>", "git-history lookback window in days", "365").option("--scope <scope>", "personal | team", "team").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
17844
17921
|
const root = findProjectRoot60(opts.dir);
|
|
17845
17922
|
const paths = resolveHaivePaths54(root);
|
|
17846
|
-
if (!
|
|
17923
|
+
if (!existsSync83(paths.haiveDir)) {
|
|
17847
17924
|
ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
|
|
17848
17925
|
process.exitCode = 1;
|
|
17849
17926
|
return;
|
|
@@ -17888,9 +17965,9 @@ function registerMemorySeedGit(memory2) {
|
|
|
17888
17965
|
_Seeded from git ${p.kind} commit ${p.source_sha}. Review and validate (or delete) \u2014 not yet authoritative._
|
|
17889
17966
|
`;
|
|
17890
17967
|
const file = memoryFilePath13(paths, fm.scope, fm.id, fm.module);
|
|
17891
|
-
if (
|
|
17968
|
+
if (existsSync83(file)) continue;
|
|
17892
17969
|
await mkdir25(path58.dirname(file), { recursive: true });
|
|
17893
|
-
await
|
|
17970
|
+
await writeFile41(file, serializeMemory33({ frontmatter: fm, body }), "utf8");
|
|
17894
17971
|
written += 1;
|
|
17895
17972
|
}
|
|
17896
17973
|
if (!opts.json) {
|
|
@@ -17921,21 +17998,21 @@ async function readCommits(root, days) {
|
|
|
17921
17998
|
}
|
|
17922
17999
|
|
|
17923
18000
|
// src/commands/bridges.ts
|
|
17924
|
-
import { existsSync as
|
|
18001
|
+
import { existsSync as existsSync84 } from "fs";
|
|
17925
18002
|
import path59 from "path";
|
|
17926
18003
|
import "commander";
|
|
17927
18004
|
import {
|
|
17928
18005
|
findProjectRoot as findProjectRoot61,
|
|
17929
18006
|
resolveHaivePaths as resolveHaivePaths55,
|
|
17930
|
-
BRIDGE_TARGET_PATH as
|
|
17931
|
-
BRIDGE_TARGETS as
|
|
18007
|
+
BRIDGE_TARGET_PATH as BRIDGE_TARGET_PATH3,
|
|
18008
|
+
BRIDGE_TARGETS as BRIDGE_TARGETS3
|
|
17932
18009
|
} from "@hiveai/core";
|
|
17933
18010
|
function registerBridges(program2) {
|
|
17934
18011
|
const bridges = program2.command("bridges").description(
|
|
17935
18012
|
"Generate native agent bridge files from the hAIve corpus.\n Bridges inject top validated memories and block sensors into agent-harness-specific\n config files (.cursor/rules/haive-memories.mdc, .clinerules, .windsurfrules,\n .continuerules, .sourcegraph/cody-rules.md, .rules, AGENTS.md,\n .github/copilot-instructions.md).\n This is the reach differentiator vs memories.sh: our bridges carry enforcement, not just injection.\n\n Example:\n haive bridges sync --all\n haive bridges sync --only cline,windsurf\n"
|
|
17936
18013
|
);
|
|
17937
18014
|
bridges.command("sync").description(
|
|
17938
|
-
"Regenerate bridge files idempotently (marker-based, preserves manual content outside markers).\n Supported targets: " +
|
|
18015
|
+
"Regenerate bridge files idempotently (marker-based, preserves manual content outside markers).\n Supported targets: " + BRIDGE_TARGETS3.join(", ") + "\n"
|
|
17939
18016
|
).option("--all", "generate all supported bridge targets").option(
|
|
17940
18017
|
"--only <targets>",
|
|
17941
18018
|
"comma-separated list of targets to generate (e.g. cline,windsurf,agents)"
|
|
@@ -17943,7 +18020,7 @@ function registerBridges(program2) {
|
|
|
17943
18020
|
const root = findProjectRoot61(opts.dir);
|
|
17944
18021
|
const paths = resolveHaivePaths55(root);
|
|
17945
18022
|
const dryRun = opts.dryRun === true;
|
|
17946
|
-
if (!
|
|
18023
|
+
if (!existsSync84(paths.memoriesDir)) {
|
|
17947
18024
|
ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
|
|
17948
18025
|
process.exitCode = 1;
|
|
17949
18026
|
return;
|
|
@@ -17951,18 +18028,18 @@ function registerBridges(program2) {
|
|
|
17951
18028
|
let targets;
|
|
17952
18029
|
if (opts.only) {
|
|
17953
18030
|
const requested = opts.only.split(",").map((t) => t.trim().toLowerCase()).filter(Boolean);
|
|
17954
|
-
const invalid = requested.filter((t) => !
|
|
18031
|
+
const invalid = requested.filter((t) => !BRIDGE_TARGETS3.includes(t));
|
|
17955
18032
|
if (invalid.length > 0) {
|
|
17956
|
-
ui.error(`Unknown bridge target(s): ${invalid.join(", ")}. Valid: ${
|
|
18033
|
+
ui.error(`Unknown bridge target(s): ${invalid.join(", ")}. Valid: ${BRIDGE_TARGETS3.join(", ")}`);
|
|
17957
18034
|
process.exitCode = 1;
|
|
17958
18035
|
return;
|
|
17959
18036
|
}
|
|
17960
18037
|
targets = requested;
|
|
17961
18038
|
} else if (opts.all) {
|
|
17962
|
-
targets =
|
|
18039
|
+
targets = BRIDGE_TARGETS3;
|
|
17963
18040
|
} else {
|
|
17964
|
-
targets =
|
|
17965
|
-
(t) =>
|
|
18041
|
+
targets = BRIDGE_TARGETS3.filter(
|
|
18042
|
+
(t) => existsSync84(path59.join(root, BRIDGE_TARGET_PATH3[t]))
|
|
17966
18043
|
);
|
|
17967
18044
|
if (targets.length === 0) {
|
|
17968
18045
|
ui.info(
|
|
@@ -17989,9 +18066,9 @@ function registerBridges(program2) {
|
|
|
17989
18066
|
bridges.command("list").description("List bridge targets and their status in this project").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
17990
18067
|
const root = findProjectRoot61(opts.dir);
|
|
17991
18068
|
console.log(ui.bold("hAIve bridge targets:"));
|
|
17992
|
-
for (const target of
|
|
17993
|
-
const relPath =
|
|
17994
|
-
const exists =
|
|
18069
|
+
for (const target of BRIDGE_TARGETS3) {
|
|
18070
|
+
const relPath = BRIDGE_TARGET_PATH3[target];
|
|
18071
|
+
const exists = existsSync84(path59.join(root, relPath));
|
|
17995
18072
|
const marker = exists ? ui.dim("\u2713") : ui.dim("\xB7");
|
|
17996
18073
|
console.log(` ${marker} ${target.padEnd(10)} ${relPath}${exists ? "" : " (not present)"}`);
|
|
17997
18074
|
}
|
|
@@ -18002,7 +18079,7 @@ function registerBridges(program2) {
|
|
|
18002
18079
|
|
|
18003
18080
|
// src/index.ts
|
|
18004
18081
|
var program = new Command64();
|
|
18005
|
-
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.
|
|
18082
|
+
program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.20.0").option("--advanced", "show maintenance and experimental commands in help");
|
|
18006
18083
|
registerInit(program);
|
|
18007
18084
|
registerWelcome(program);
|
|
18008
18085
|
registerResolveProject(program);
|