@hivelore/mcp 0.37.0 → 0.39.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 +367 -198
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +62 -6
- package/dist/server.js +379 -199
- package/dist/server.js.map +1 -1
- package/package.json +3 -3
package/dist/server.js
CHANGED
|
@@ -1259,6 +1259,7 @@ async function proposeSensor(input, ctx) {
|
|
|
1259
1259
|
if (!found) {
|
|
1260
1260
|
throw new Error(`No memory found with id ${input.memory_id}`);
|
|
1261
1261
|
}
|
|
1262
|
+
const personalScopeNudge = found.memory.frontmatter.scope === "personal" ? ` Note: this lesson is personal-scoped, so the sensor guards only YOUR machine (personal memories are gitignored). Promote it so the gate travels with the repo: hivelore memory promote ${input.memory_id}.` : "";
|
|
1262
1263
|
if (kind !== "regex") {
|
|
1263
1264
|
const verdictCmd = runCommandForValidation(input.command.trim(), ctx.paths.root, input.timeout_ms);
|
|
1264
1265
|
const anchorPathsCmd = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
|
|
@@ -1293,7 +1294,7 @@ ${verdictCmd.detail}`,
|
|
|
1293
1294
|
accepted: true,
|
|
1294
1295
|
memory_id: input.memory_id,
|
|
1295
1296
|
severity: input.severity,
|
|
1296
|
-
guidance: verdictCmd.status === "passed" ? "Command oracle passes on the current tree; the gate now runs it when the diff touches the sensor's paths (requires enforcement.runCommandSensors=true)." : `Accepted at warn severity, but note: ${verdictCmd.status} on the current tree (${verdictCmd.detail})
|
|
1297
|
+
guidance: (verdictCmd.status === "passed" ? "Command oracle passes on the current tree; the gate now runs it when the diff touches the sensor's paths (requires enforcement.runCommandSensors=true)." : `Accepted at warn severity, but note: ${verdictCmd.status} on the current tree (${verdictCmd.detail}).`) + personalScopeNudge,
|
|
1297
1298
|
self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: null, fired_on: [] }
|
|
1298
1299
|
};
|
|
1299
1300
|
}
|
|
@@ -1342,6 +1343,7 @@ ${verdictCmd.detail}`,
|
|
|
1342
1343
|
accepted: true,
|
|
1343
1344
|
memory_id: input.memory_id,
|
|
1344
1345
|
severity: input.severity,
|
|
1346
|
+
...personalScopeNudge ? { guidance: personalScopeNudge.trim() } : {},
|
|
1345
1347
|
self_check,
|
|
1346
1348
|
file_path: found.filePath
|
|
1347
1349
|
};
|
|
@@ -1352,7 +1354,9 @@ var MemTriedInputSchema = {
|
|
|
1352
1354
|
what: z16.string().min(1).describe("Brief description of the approach that was tried"),
|
|
1353
1355
|
why_failed: z16.string().min(1).describe("Why it failed or why it should NOT be used"),
|
|
1354
1356
|
instead: z16.string().optional().describe("What to use or do instead (recommended alternative)"),
|
|
1355
|
-
scope: z16.enum(["personal", "team", "module"]).
|
|
1357
|
+
scope: z16.enum(["personal", "team", "module"]).optional().describe(
|
|
1358
|
+
"Visibility scope. Defaults to personal \u2014 EXCEPT when a one-shot `sensor` is attached: an enforced lesson is team truth (the sensor must travel to every machine and CI), so it defaults to team. Pass scope explicitly to override."
|
|
1359
|
+
),
|
|
1356
1360
|
module: z16.string().optional().describe("Module name (required when scope=module)"),
|
|
1357
1361
|
tags: z16.array(z16.string()).default([]).describe("Tags for filtering"),
|
|
1358
1362
|
paths: z16.array(z16.string()).default([]).describe("Anchor file paths this applies to"),
|
|
@@ -1376,10 +1380,11 @@ async function memTried(input, ctx) {
|
|
|
1376
1380
|
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
|
|
1377
1381
|
}
|
|
1378
1382
|
const slug = input.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 5).join("-");
|
|
1383
|
+
const scope = input.scope ?? (input.sensor ? "team" : "personal");
|
|
1379
1384
|
const baseFm = buildFrontmatter2({
|
|
1380
1385
|
type: "attempt",
|
|
1381
1386
|
slug,
|
|
1382
|
-
scope
|
|
1387
|
+
scope,
|
|
1383
1388
|
module: input.module,
|
|
1384
1389
|
tags: input.tags,
|
|
1385
1390
|
paths: input.paths,
|
|
@@ -1427,7 +1432,7 @@ async function memTried(input, ctx) {
|
|
|
1427
1432
|
...verdict.reason ? { reason: verdict.reason } : {},
|
|
1428
1433
|
...verdict.guidance ? { guidance: verdict.guidance } : {}
|
|
1429
1434
|
},
|
|
1430
|
-
hint: verdict.accepted ? "Loop closed: the attempt is saved AND enforced \u2014 the gate now refuses a repeat deterministically." : `Attempt saved, but the sensor was rejected (${verdict.reason}). Revise per the guidance and re-propose with propose_sensor.`
|
|
1435
|
+
hint: (verdict.accepted ? "Loop closed: the attempt is saved AND enforced \u2014 the gate now refuses a repeat deterministically." : `Attempt saved, but the sensor was rejected (${verdict.reason}). Revise per the guidance and re-propose with propose_sensor.`) + (input.scope === void 0 ? " Saved team-scoped (an enforced lesson must travel with the repo) \u2014 pass scope:'personal' to keep it private." : "")
|
|
1431
1436
|
};
|
|
1432
1437
|
}
|
|
1433
1438
|
const seed = input.paths.length > 0 ? suggestSensorSeed2(body, input.paths) : null;
|
|
@@ -1448,43 +1453,185 @@ async function memTried(input, ctx) {
|
|
|
1448
1453
|
};
|
|
1449
1454
|
}
|
|
1450
1455
|
|
|
1451
|
-
// src/tools/
|
|
1452
|
-
import { existsSync as existsSync17 } from "fs";
|
|
1456
|
+
// src/tools/scaffold-test.ts
|
|
1457
|
+
import { existsSync as existsSync17, statSync } from "fs";
|
|
1453
1458
|
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile10 } from "fs/promises";
|
|
1454
1459
|
import path7 from "path";
|
|
1460
|
+
import { z as z17 } from "zod";
|
|
1461
|
+
import {
|
|
1462
|
+
buildProposeCommand,
|
|
1463
|
+
loadMemoriesFromDir as loadMemoriesFromDir14,
|
|
1464
|
+
normalizeFramework,
|
|
1465
|
+
parseLessonFields,
|
|
1466
|
+
pickTestFramework,
|
|
1467
|
+
scaffoldPostIncidentTest
|
|
1468
|
+
} from "@hivelore/core";
|
|
1469
|
+
var PY_SIGNALS = ["pyproject.toml", "setup.py", "pytest.ini", "requirements.txt", "tox.ini"];
|
|
1470
|
+
async function detectForAnchor(root, rel) {
|
|
1471
|
+
let dir = path7.resolve(root, rel);
|
|
1472
|
+
try {
|
|
1473
|
+
if (!statSync(dir).isDirectory()) dir = path7.dirname(dir);
|
|
1474
|
+
} catch {
|
|
1475
|
+
if (path7.extname(dir)) dir = path7.dirname(dir);
|
|
1476
|
+
}
|
|
1477
|
+
while (dir.startsWith(root)) {
|
|
1478
|
+
const pkgJson = path7.join(dir, "package.json");
|
|
1479
|
+
const hasPkg = existsSync17(pkgJson);
|
|
1480
|
+
const goMod = existsSync17(path7.join(dir, "go.mod"));
|
|
1481
|
+
const pySignal = PY_SIGNALS.some((s) => existsSync17(path7.join(dir, s)));
|
|
1482
|
+
if (hasPkg || goMod || pySignal) {
|
|
1483
|
+
let pkg = null;
|
|
1484
|
+
if (hasPkg) {
|
|
1485
|
+
try {
|
|
1486
|
+
pkg = JSON.parse(await readFile4(pkgJson, "utf8"));
|
|
1487
|
+
} catch {
|
|
1488
|
+
pkg = null;
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
const baseDir = path7.relative(root, dir).split(path7.sep).join("/");
|
|
1492
|
+
return { framework: pickTestFramework(pkg, { goMod, pySignal }), baseDir };
|
|
1493
|
+
}
|
|
1494
|
+
const parent = path7.dirname(dir);
|
|
1495
|
+
if (parent === dir || dir === root) break;
|
|
1496
|
+
dir = parent;
|
|
1497
|
+
}
|
|
1498
|
+
return null;
|
|
1499
|
+
}
|
|
1500
|
+
async function detectTestFrameworkForPaths(root, anchorPaths) {
|
|
1501
|
+
const starts = anchorPaths.length > 0 ? anchorPaths : ["."];
|
|
1502
|
+
for (const rel of starts) {
|
|
1503
|
+
const found = await detectForAnchor(root, rel);
|
|
1504
|
+
if (found) return found;
|
|
1505
|
+
}
|
|
1506
|
+
return { framework: "vitest", baseDir: "" };
|
|
1507
|
+
}
|
|
1508
|
+
async function detectTestFrameworksForAnchors(root, anchorPaths) {
|
|
1509
|
+
const starts = anchorPaths.length > 0 ? anchorPaths : ["."];
|
|
1510
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1511
|
+
for (const rel of starts) {
|
|
1512
|
+
const found = await detectForAnchor(root, rel) ?? { framework: "vitest", baseDir: "" };
|
|
1513
|
+
const existing = groups.get(found.baseDir);
|
|
1514
|
+
if (existing) existing.anchors.push(rel);
|
|
1515
|
+
else groups.set(found.baseDir, { ...found, anchors: [rel] });
|
|
1516
|
+
}
|
|
1517
|
+
return [...groups.values()];
|
|
1518
|
+
}
|
|
1519
|
+
var ScaffoldTestInputSchema = {
|
|
1520
|
+
memory_id: z17.string().min(1).describe("Id of the attempt/gotcha lesson to scaffold a post-incident test from."),
|
|
1521
|
+
framework: z17.enum(["vitest", "jest", "pytest", "gotest"]).optional().describe("Test framework. Auto-detected from the package that owns the lesson's anchor paths when omitted."),
|
|
1522
|
+
out_path: z17.string().optional().describe("Override the generated test file path (repo-relative)."),
|
|
1523
|
+
write: z17.boolean().default(true).describe("Write the file to disk (default). false = return the content for preview without writing.")
|
|
1524
|
+
};
|
|
1525
|
+
async function scaffoldTest(input, ctx) {
|
|
1526
|
+
const loaded = existsSync17(ctx.paths.memoriesDir) ? await loadMemoriesFromDir14(ctx.paths.memoriesDir) : [];
|
|
1527
|
+
const found = loaded.find(({ memory }) => memory.frontmatter.id === input.memory_id);
|
|
1528
|
+
if (!found) {
|
|
1529
|
+
return { ok: false, error: `No memory found with id ${input.memory_id}`, memory_id: input.memory_id };
|
|
1530
|
+
}
|
|
1531
|
+
const anchorPaths = found.memory.frontmatter.anchor.paths ?? [];
|
|
1532
|
+
const allGroups = await detectTestFrameworksForAnchors(ctx.paths.root, anchorPaths);
|
|
1533
|
+
const groups = input.out_path ? allGroups.slice(0, 1) : allGroups;
|
|
1534
|
+
const frameworkFor = (detected) => input.framework ? normalizeFramework(input.framework) ?? detected : detected;
|
|
1535
|
+
const fields = parseLessonFields(found.memory.body);
|
|
1536
|
+
const lesson = {
|
|
1537
|
+
memoryId: input.memory_id,
|
|
1538
|
+
title: fields.title || input.memory_id,
|
|
1539
|
+
whyFailed: fields.whyFailed,
|
|
1540
|
+
instead: fields.instead,
|
|
1541
|
+
incident: found.memory.frontmatter.sensor?.incident,
|
|
1542
|
+
paths: anchorPaths
|
|
1543
|
+
};
|
|
1544
|
+
let scaffolds = groups.map(
|
|
1545
|
+
(g) => scaffoldPostIncidentTest(lesson, { framework: frameworkFor(g.framework), outPath: input.out_path, baseDir: g.baseDir })
|
|
1546
|
+
);
|
|
1547
|
+
let proposeCommand = scaffolds[0].proposeCommand;
|
|
1548
|
+
if (scaffolds.length > 1) {
|
|
1549
|
+
proposeCommand = buildProposeCommand(lesson, scaffolds.map((s) => s.runCommand).join(" && "));
|
|
1550
|
+
scaffolds = groups.map(
|
|
1551
|
+
(g) => scaffoldPostIncidentTest(lesson, {
|
|
1552
|
+
framework: frameworkFor(g.framework),
|
|
1553
|
+
baseDir: g.baseDir,
|
|
1554
|
+
proposeCommandOverride: proposeCommand
|
|
1555
|
+
})
|
|
1556
|
+
);
|
|
1557
|
+
}
|
|
1558
|
+
const results = [];
|
|
1559
|
+
for (const scaffold of scaffolds) {
|
|
1560
|
+
const abs = path7.isAbsolute(scaffold.relPath) ? scaffold.relPath : path7.resolve(ctx.paths.root, scaffold.relPath);
|
|
1561
|
+
let written = false;
|
|
1562
|
+
let alreadyExists = false;
|
|
1563
|
+
if (input.write) {
|
|
1564
|
+
if (existsSync17(abs)) {
|
|
1565
|
+
alreadyExists = true;
|
|
1566
|
+
} else {
|
|
1567
|
+
await mkdir4(path7.dirname(abs), { recursive: true });
|
|
1568
|
+
await writeFile10(abs, scaffold.content, "utf8");
|
|
1569
|
+
written = true;
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
results.push({
|
|
1573
|
+
framework: scaffold.framework,
|
|
1574
|
+
path: scaffold.relPath,
|
|
1575
|
+
run_command: scaffold.runCommand,
|
|
1576
|
+
content: scaffold.content,
|
|
1577
|
+
written,
|
|
1578
|
+
already_exists: alreadyExists
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
const first = results[0];
|
|
1582
|
+
const anyExisting = results.some((r) => r.already_exists);
|
|
1583
|
+
return {
|
|
1584
|
+
ok: true,
|
|
1585
|
+
memory_id: input.memory_id,
|
|
1586
|
+
framework: first.framework,
|
|
1587
|
+
path: first.path,
|
|
1588
|
+
run_command: first.run_command,
|
|
1589
|
+
propose_command: proposeCommand,
|
|
1590
|
+
content: first.content,
|
|
1591
|
+
written: first.written,
|
|
1592
|
+
already_exists: first.already_exists,
|
|
1593
|
+
...results.length > 1 ? { scaffolds: results } : {},
|
|
1594
|
+
notice: (results.length > 1 ? `Lesson spans ${results.length} packages \u2014 one pending test per owning package; ONE propose_command arms them all (chained oracle). ` : "") + (anyExisting ? "Some file(s) already exist \u2014 not overwritten. Delete them or pass out_path to write elsewhere." : "PENDING test scaffolded. Fill in the assertion (RED on the incident, GREEN once fixed), run it, then arm it with propose_command \u2014 propose_sensor stays the sole validated writer.")
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// src/tools/ingest-findings.ts
|
|
1599
|
+
import { existsSync as existsSync18 } from "fs";
|
|
1600
|
+
import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile11 } from "fs/promises";
|
|
1601
|
+
import path8 from "path";
|
|
1455
1602
|
import {
|
|
1456
1603
|
draftsFromFindings,
|
|
1457
1604
|
filterNewDrafts,
|
|
1458
|
-
loadMemoriesFromDir as
|
|
1605
|
+
loadMemoriesFromDir as loadMemoriesFromDir15,
|
|
1459
1606
|
memoryFilePath as memoryFilePath3,
|
|
1460
1607
|
parseFindings,
|
|
1461
1608
|
serializeMemory as serializeMemory9
|
|
1462
1609
|
} from "@hivelore/core";
|
|
1463
|
-
import { z as
|
|
1610
|
+
import { z as z18 } from "zod";
|
|
1464
1611
|
var IngestFindingsInputSchema = {
|
|
1465
|
-
format:
|
|
1466
|
-
report_path:
|
|
1467
|
-
report:
|
|
1468
|
-
type:
|
|
1469
|
-
scope:
|
|
1470
|
-
module:
|
|
1471
|
-
min_severity:
|
|
1472
|
-
include_stylistic:
|
|
1473
|
-
limit:
|
|
1474
|
-
author:
|
|
1475
|
-
dry_run:
|
|
1612
|
+
format: z18.enum(["sarif", "sonar"]).describe("Report format: 'sarif' (ESLint/Semgrep/CodeQL) or 'sonar' (SonarQube issues JSON)"),
|
|
1613
|
+
report_path: z18.string().optional().describe("Project-relative path to the findings JSON file. Provide this OR `report`."),
|
|
1614
|
+
report: z18.string().optional().describe("Inline findings JSON content. Provide this OR `report_path`."),
|
|
1615
|
+
type: z18.enum(["gotcha", "convention"]).default("gotcha").describe("Memory type for the created drafts"),
|
|
1616
|
+
scope: z18.enum(["personal", "team", "module"]).default("team").describe("Visibility scope for the created memories"),
|
|
1617
|
+
module: z18.string().optional().describe("Module name (required when scope=module)"),
|
|
1618
|
+
min_severity: z18.enum(["info", "minor", "major", "critical", "blocker"]).optional().describe("Ignore findings below this severity"),
|
|
1619
|
+
include_stylistic: z18.boolean().optional().describe("Also ingest auto-fixable stylistic rules (semi/quotes/prefer-const\u2026); off by default as low-value noise"),
|
|
1620
|
+
limit: z18.number().int().positive().optional().describe("Cap the number of memories created"),
|
|
1621
|
+
author: z18.string().optional().describe("Author handle or email"),
|
|
1622
|
+
dry_run: z18.boolean().default(false).describe("When true, return the drafts that WOULD be created without writing them")
|
|
1476
1623
|
};
|
|
1477
1624
|
async function ingestFindings(input, ctx) {
|
|
1478
|
-
if (!
|
|
1625
|
+
if (!existsSync18(ctx.paths.haiveDir)) {
|
|
1479
1626
|
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
|
|
1480
1627
|
}
|
|
1481
1628
|
let raw;
|
|
1482
1629
|
if (input.report && input.report.trim()) {
|
|
1483
1630
|
raw = input.report;
|
|
1484
1631
|
} else if (input.report_path) {
|
|
1485
|
-
const file =
|
|
1486
|
-
if (!
|
|
1487
|
-
raw = await
|
|
1632
|
+
const file = path8.resolve(ctx.paths.root, input.report_path);
|
|
1633
|
+
if (!existsSync18(file)) throw new Error(`Report file not found: ${file}`);
|
|
1634
|
+
raw = await readFile5(file, "utf8");
|
|
1488
1635
|
} else {
|
|
1489
1636
|
throw new Error("Provide either `report_path` or `report`.");
|
|
1490
1637
|
}
|
|
@@ -1498,7 +1645,7 @@ async function ingestFindings(input, ctx) {
|
|
|
1498
1645
|
...input.include_stylistic ? { includeStylistic: true } : {},
|
|
1499
1646
|
...input.limit ? { limit: input.limit } : {}
|
|
1500
1647
|
});
|
|
1501
|
-
const existing =
|
|
1648
|
+
const existing = existsSync18(ctx.paths.memoriesDir) ? await loadMemoriesFromDir15(ctx.paths.memoriesDir) : [];
|
|
1502
1649
|
const existingTopics = new Set(
|
|
1503
1650
|
existing.map(({ memory }) => memory.frontmatter.topic).filter((t) => Boolean(t))
|
|
1504
1651
|
);
|
|
@@ -1536,22 +1683,22 @@ async function writeDraft(ctx, draft) {
|
|
|
1536
1683
|
draft.frontmatter.id,
|
|
1537
1684
|
draft.frontmatter.module
|
|
1538
1685
|
);
|
|
1539
|
-
await
|
|
1540
|
-
await
|
|
1686
|
+
await mkdir5(path8.dirname(file), { recursive: true });
|
|
1687
|
+
await writeFile11(file, serializeMemory9({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
|
|
1541
1688
|
return file;
|
|
1542
1689
|
}
|
|
1543
1690
|
|
|
1544
1691
|
// src/tools/mem-session-end.ts
|
|
1545
|
-
import { writeFile as
|
|
1546
|
-
import { existsSync as
|
|
1547
|
-
import
|
|
1692
|
+
import { writeFile as writeFile13, mkdir as mkdir7 } from "fs/promises";
|
|
1693
|
+
import { existsSync as existsSync20 } from "fs";
|
|
1694
|
+
import path10 from "path";
|
|
1548
1695
|
import {
|
|
1549
1696
|
buildFrontmatter as buildFrontmatter3,
|
|
1550
|
-
loadMemoriesFromDir as
|
|
1697
|
+
loadMemoriesFromDir as loadMemoriesFromDir16,
|
|
1551
1698
|
memoryFilePath as memoryFilePath4,
|
|
1552
1699
|
serializeMemory as serializeMemory10
|
|
1553
1700
|
} from "@hivelore/core";
|
|
1554
|
-
import { z as
|
|
1701
|
+
import { z as z19 } from "zod";
|
|
1555
1702
|
|
|
1556
1703
|
// src/session-tracker.ts
|
|
1557
1704
|
import {
|
|
@@ -1560,12 +1707,12 @@ import {
|
|
|
1560
1707
|
loadConfig as loadConfig2,
|
|
1561
1708
|
writeSessionHandoff
|
|
1562
1709
|
} from "@hivelore/core";
|
|
1563
|
-
import { mkdir as
|
|
1564
|
-
import { existsSync as
|
|
1565
|
-
import
|
|
1710
|
+
import { mkdir as mkdir6, writeFile as writeFile12, rm } from "fs/promises";
|
|
1711
|
+
import { existsSync as existsSync19 } from "fs";
|
|
1712
|
+
import path9 from "path";
|
|
1566
1713
|
import { execSync as execSync2 } from "child_process";
|
|
1567
1714
|
function pendingDistillPath(ctx) {
|
|
1568
|
-
return
|
|
1715
|
+
return path9.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
|
|
1569
1716
|
}
|
|
1570
1717
|
var SessionTracker = class {
|
|
1571
1718
|
events = [];
|
|
@@ -1669,7 +1816,7 @@ var SessionTracker = class {
|
|
|
1669
1816
|
(e) => e.tool === "mem_session_end" && !e.summary?.startsWith("Auto-captured")
|
|
1670
1817
|
);
|
|
1671
1818
|
const isSubstantialSession = totalCalls >= 3 || writingTools.length > 0;
|
|
1672
|
-
if (!ranPostTask && isSubstantialSession &&
|
|
1819
|
+
if (!ranPostTask && isSubstantialSession && existsSync19(this.ctx.paths.haiveDir)) {
|
|
1673
1820
|
try {
|
|
1674
1821
|
const memoriesSaved = writingTools.map((e) => e.summary ?? "").filter(Boolean).slice(0, 20);
|
|
1675
1822
|
const payload = {
|
|
@@ -1682,9 +1829,9 @@ var SessionTracker = class {
|
|
|
1682
1829
|
...gitDiff ? { git_diff: gitDiff } : {},
|
|
1683
1830
|
...recapId ? { recap_id: recapId } : {}
|
|
1684
1831
|
};
|
|
1685
|
-
const cacheDir =
|
|
1686
|
-
await
|
|
1687
|
-
await
|
|
1832
|
+
const cacheDir = path9.join(this.ctx.paths.haiveDir, ".cache");
|
|
1833
|
+
await mkdir6(cacheDir, { recursive: true });
|
|
1834
|
+
await writeFile12(
|
|
1688
1835
|
pendingDistillPath(this.ctx),
|
|
1689
1836
|
JSON.stringify(payload, null, 2) + "\n",
|
|
1690
1837
|
"utf8"
|
|
@@ -1703,7 +1850,7 @@ var SessionTracker = class {
|
|
|
1703
1850
|
};
|
|
1704
1851
|
async function clearPendingDistill(ctx) {
|
|
1705
1852
|
const p = pendingDistillPath(ctx);
|
|
1706
|
-
if (
|
|
1853
|
+
if (existsSync19(p)) {
|
|
1707
1854
|
try {
|
|
1708
1855
|
await rm(p);
|
|
1709
1856
|
} catch {
|
|
@@ -1720,15 +1867,15 @@ function summarizeTools(events) {
|
|
|
1720
1867
|
|
|
1721
1868
|
// src/tools/mem-session-end.ts
|
|
1722
1869
|
var MemSessionEndInputSchema = {
|
|
1723
|
-
goal:
|
|
1724
|
-
accomplished:
|
|
1725
|
-
discoveries:
|
|
1870
|
+
goal: z19.string().min(1).describe("What you were trying to accomplish this session (1\u20132 sentences)"),
|
|
1871
|
+
accomplished: z19.string().describe("What was actually done \u2014 bullet list recommended"),
|
|
1872
|
+
discoveries: z19.string().default("").describe(
|
|
1726
1873
|
"Any bugs, inconsistencies, surprises, or missing knowledge found during this session. Empty if nothing surprising was found."
|
|
1727
1874
|
),
|
|
1728
|
-
files_touched:
|
|
1729
|
-
next_steps:
|
|
1730
|
-
scope:
|
|
1731
|
-
module:
|
|
1875
|
+
files_touched: z19.array(z19.string()).default([]).describe("Key files that were read or modified \u2014 used as anchor paths"),
|
|
1876
|
+
next_steps: z19.string().default("").describe("What should happen next (for the next session or a teammate)"),
|
|
1877
|
+
scope: z19.enum(["personal", "team", "module"]).default("personal").describe("Visibility: personal = private to you, team = shared with the team"),
|
|
1878
|
+
module: z19.string().optional().describe("Module name (required when scope=module)")
|
|
1732
1879
|
};
|
|
1733
1880
|
function recapTopic(scope, module) {
|
|
1734
1881
|
return module ? `session-recap-${scope}-${module}` : `session-recap-${scope}`;
|
|
@@ -1758,23 +1905,23 @@ ${input.next_steps}`);
|
|
|
1758
1905
|
return lines.join("\n");
|
|
1759
1906
|
}
|
|
1760
1907
|
async function memSessionEnd(input, ctx) {
|
|
1761
|
-
if (!
|
|
1908
|
+
if (!existsSync20(ctx.paths.haiveDir)) {
|
|
1762
1909
|
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
|
|
1763
1910
|
}
|
|
1764
1911
|
const body = buildBody(input);
|
|
1765
1912
|
const topic = recapTopic(input.scope, input.module);
|
|
1766
1913
|
const normalizedFiles = input.files_touched.map((p) => {
|
|
1767
|
-
if (!p || !
|
|
1768
|
-
const rel =
|
|
1914
|
+
if (!p || !path10.isAbsolute(p)) return p;
|
|
1915
|
+
const rel = path10.relative(ctx.paths.root, p);
|
|
1769
1916
|
return rel.startsWith("..") ? p : rel;
|
|
1770
1917
|
});
|
|
1771
1918
|
const invalidPaths = normalizedFiles.filter(
|
|
1772
|
-
(p) => !
|
|
1919
|
+
(p) => !existsSync20(path10.resolve(ctx.paths.root, p))
|
|
1773
1920
|
);
|
|
1774
1921
|
if (invalidPaths.length > 0) {
|
|
1775
1922
|
console.warn(`[haive] session end: anchor path(s) not found: ${invalidPaths.join(", ")}`);
|
|
1776
1923
|
}
|
|
1777
|
-
const existing =
|
|
1924
|
+
const existing = existsSync20(ctx.paths.memoriesDir) ? await loadMemoriesFromDir16(ctx.paths.memoriesDir) : [];
|
|
1778
1925
|
const topicMatch = existing.find(
|
|
1779
1926
|
({ memory }) => memory.frontmatter.topic === topic && memory.frontmatter.scope === input.scope && (!input.module || memory.frontmatter.module === input.module)
|
|
1780
1927
|
);
|
|
@@ -1790,7 +1937,7 @@ async function memSessionEnd(input, ctx) {
|
|
|
1790
1937
|
paths: normalizedFiles.length ? normalizedFiles : fm.anchor.paths
|
|
1791
1938
|
}
|
|
1792
1939
|
};
|
|
1793
|
-
await
|
|
1940
|
+
await writeFile13(
|
|
1794
1941
|
topicMatch.filePath,
|
|
1795
1942
|
serializeMemory10({ frontmatter: newFrontmatter, body }),
|
|
1796
1943
|
"utf8"
|
|
@@ -1820,8 +1967,8 @@ async function memSessionEnd(input, ctx) {
|
|
|
1820
1967
|
frontmatter.id,
|
|
1821
1968
|
frontmatter.module
|
|
1822
1969
|
);
|
|
1823
|
-
await
|
|
1824
|
-
await
|
|
1970
|
+
await mkdir7(path10.dirname(file), { recursive: true });
|
|
1971
|
+
await writeFile13(file, serializeMemory10({ frontmatter, body }), "utf8");
|
|
1825
1972
|
await clearPendingDistill(ctx);
|
|
1826
1973
|
return {
|
|
1827
1974
|
id: frontmatter.id,
|
|
@@ -1833,9 +1980,9 @@ async function memSessionEnd(input, ctx) {
|
|
|
1833
1980
|
}
|
|
1834
1981
|
|
|
1835
1982
|
// src/tools/get-briefing.ts
|
|
1836
|
-
import { readFile as
|
|
1837
|
-
import { existsSync as
|
|
1838
|
-
import
|
|
1983
|
+
import { readFile as readFile7, writeFile as writeFile14, readdir as readdir4 } from "fs/promises";
|
|
1984
|
+
import { existsSync as existsSync22 } from "fs";
|
|
1985
|
+
import path12 from "path";
|
|
1839
1986
|
import {
|
|
1840
1987
|
allocateBudget,
|
|
1841
1988
|
assessBootstrapState,
|
|
@@ -1859,7 +2006,7 @@ import {
|
|
|
1859
2006
|
loadConfig as loadConfig3,
|
|
1860
2007
|
memoryHasExcludedTag,
|
|
1861
2008
|
hashProjectContext,
|
|
1862
|
-
loadMemoriesFromDir as
|
|
2009
|
+
loadMemoriesFromDir as loadMemoriesFromDir17,
|
|
1863
2010
|
loadPreventionEvents,
|
|
1864
2011
|
loadUsageIndex as loadUsageIndex8,
|
|
1865
2012
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
|
|
@@ -1877,12 +2024,12 @@ import {
|
|
|
1877
2024
|
truncateToTokens,
|
|
1878
2025
|
writeBriefingMarker
|
|
1879
2026
|
} from "@hivelore/core";
|
|
1880
|
-
import { z as
|
|
2027
|
+
import { z as z20 } from "zod";
|
|
1881
2028
|
|
|
1882
2029
|
// src/tools/briefing-helpers.ts
|
|
1883
|
-
import { readdir as readdir3, readFile as
|
|
1884
|
-
import { existsSync as
|
|
1885
|
-
import
|
|
2030
|
+
import { readdir as readdir3, readFile as readFile6 } from "fs/promises";
|
|
2031
|
+
import { existsSync as existsSync21 } from "fs";
|
|
2032
|
+
import path11 from "path";
|
|
1886
2033
|
import {
|
|
1887
2034
|
classifyMemoryPriority as coreClassifyPriority,
|
|
1888
2035
|
isGlobPath,
|
|
@@ -2015,16 +2162,16 @@ async function trySemanticHits(ctx, task, limit) {
|
|
|
2015
2162
|
}
|
|
2016
2163
|
async function loadModuleContexts2(ctx, modules) {
|
|
2017
2164
|
if (modules.length === 0) return [];
|
|
2018
|
-
if (!
|
|
2165
|
+
if (!existsSync21(ctx.paths.modulesContextDir)) return [];
|
|
2019
2166
|
const available = new Set(
|
|
2020
2167
|
(await readdir3(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
|
|
2021
2168
|
);
|
|
2022
2169
|
const out = [];
|
|
2023
2170
|
for (const m of modules) {
|
|
2024
2171
|
if (!available.has(m)) continue;
|
|
2025
|
-
const file =
|
|
2026
|
-
if (
|
|
2027
|
-
out.push({ name: m, content: await
|
|
2172
|
+
const file = path11.join(ctx.paths.modulesContextDir, m, "context.md");
|
|
2173
|
+
if (existsSync21(file)) {
|
|
2174
|
+
out.push({ name: m, content: await readFile6(file, "utf8") });
|
|
2028
2175
|
}
|
|
2029
2176
|
}
|
|
2030
2177
|
return out;
|
|
@@ -2032,38 +2179,38 @@ async function loadModuleContexts2(ctx, modules) {
|
|
|
2032
2179
|
|
|
2033
2180
|
// src/tools/get-briefing.ts
|
|
2034
2181
|
var GetBriefingInputSchema = {
|
|
2035
|
-
task:
|
|
2182
|
+
task: z20.string().optional().describe(
|
|
2036
2183
|
"What you are about to do, in 1\u20132 sentences. Used to rank relevant memories semantically."
|
|
2037
2184
|
),
|
|
2038
|
-
files:
|
|
2039
|
-
max_tokens:
|
|
2185
|
+
files: z20.array(z20.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
|
|
2186
|
+
max_tokens: z20.number().int().positive().default(8e3).describe(
|
|
2040
2187
|
"Approximate token budget for the entire briefing. Each section is allocated a share and truncated to fit."
|
|
2041
2188
|
),
|
|
2042
|
-
max_memories:
|
|
2043
|
-
include_project_context:
|
|
2044
|
-
dedupe_project_context:
|
|
2189
|
+
max_memories: z20.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
|
|
2190
|
+
include_project_context: z20.boolean().default(true),
|
|
2191
|
+
dedupe_project_context: z20.boolean().optional().describe(
|
|
2045
2192
|
"Token saver (default ON): skip re-emitting the project-context body if an identical copy was already sent within the last few minutes this session (the agent still has it). Set false to always include it."
|
|
2046
2193
|
),
|
|
2047
|
-
include_module_contexts:
|
|
2048
|
-
semantic:
|
|
2194
|
+
include_module_contexts: z20.boolean().default(true),
|
|
2195
|
+
semantic: z20.boolean().default(true).describe(
|
|
2049
2196
|
"Use semantic ranking when a task is provided (requires `hivelore embeddings index`)."
|
|
2050
2197
|
),
|
|
2051
|
-
include_stale:
|
|
2052
|
-
track:
|
|
2053
|
-
format:
|
|
2198
|
+
include_stale: z20.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
|
|
2199
|
+
track: z20.boolean().default(true).describe("Increment read_count on returned memories"),
|
|
2200
|
+
format: z20.enum(["full", "compact", "actions"]).default("full").describe(
|
|
2054
2201
|
"Output format: 'full' returns memory bodies (honors token budget via truncation); 'compact' returns a 1-line summary per memory (call mem_get for detail); 'actions' squeezes bodies to actionable bullet lines \u2014 fewer tokens vs full."
|
|
2055
2202
|
),
|
|
2056
|
-
symbols:
|
|
2203
|
+
symbols: z20.array(z20.string()).default([]).describe(
|
|
2057
2204
|
"Symbol names to look up in the code-map (e.g. ['PaymentService', 'TenantFilter']). Returns the file(s) exporting each symbol so agents don't need to grep. Requires `hivelore index code` to have been run."
|
|
2058
2205
|
),
|
|
2059
|
-
min_semantic_score:
|
|
2206
|
+
min_semantic_score: z20.number().min(0).max(1).default(0).describe(
|
|
2060
2207
|
"Drop semantic-only memory hits whose cosine score is below this threshold. Useful to avoid weakly-related noise when the task is short or the corpus is broad. Has no effect on memories matched via anchor/module/literal \u2014 those are always kept. Try 0.25\u20130.4 for stricter matching."
|
|
2061
2208
|
),
|
|
2062
|
-
budget_preset:
|
|
2209
|
+
budget_preset: z20.enum(["quick", "balanced", "deep"]).optional().describe(
|
|
2063
2210
|
"Shortcut token budget: 'quick' minimizes tokens/skip module CONTEXT slices; 'balanced' mirrors historical defaults; 'deep' uses a larger briefing. When set, overrides max_tokens, max_memories, and include_module_contexts."
|
|
2064
2211
|
)
|
|
2065
2212
|
};
|
|
2066
|
-
var GetBriefingZod =
|
|
2213
|
+
var GetBriefingZod = z20.object(GetBriefingInputSchema);
|
|
2067
2214
|
async function getBriefing(input, ctx) {
|
|
2068
2215
|
const resolvedBudget = resolveBriefingBudget(input.budget_preset, {
|
|
2069
2216
|
max_tokens: input.max_tokens,
|
|
@@ -2079,8 +2226,8 @@ async function getBriefing(input, ctx) {
|
|
|
2079
2226
|
let usage = { version: 1, updated_at: "", by_id: {} };
|
|
2080
2227
|
let byId = /* @__PURE__ */ new Map();
|
|
2081
2228
|
let lastSession;
|
|
2082
|
-
if (
|
|
2083
|
-
const allLoaded = await
|
|
2229
|
+
if (existsSync22(ctx.paths.memoriesDir)) {
|
|
2230
|
+
const allLoaded = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
|
|
2084
2231
|
const recaps = allLoaded.filter(({ memory }) => memory.frontmatter.type === "session_recap").sort(
|
|
2085
2232
|
(a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
|
|
2086
2233
|
);
|
|
@@ -2254,7 +2401,7 @@ async function getBriefing(input, ctx) {
|
|
|
2254
2401
|
if (!isAutoPromoteEligible(loaded.memory.frontmatter, u, rule)) continue;
|
|
2255
2402
|
const newFm = { ...loaded.memory.frontmatter, status: "validated", validated_by: "auto" };
|
|
2256
2403
|
try {
|
|
2257
|
-
await
|
|
2404
|
+
await writeFile14(loaded.filePath, serializeMemory11({ frontmatter: newFm, body: loaded.memory.body }), "utf8");
|
|
2258
2405
|
m.status = "validated";
|
|
2259
2406
|
m.confidence = "trusted";
|
|
2260
2407
|
} catch {
|
|
@@ -2262,7 +2409,7 @@ async function getBriefing(input, ctx) {
|
|
|
2262
2409
|
}
|
|
2263
2410
|
}
|
|
2264
2411
|
}
|
|
2265
|
-
let projectContextRaw = input.include_project_context &&
|
|
2412
|
+
let projectContextRaw = input.include_project_context && existsSync22(ctx.paths.projectContext) ? await readFile7(ctx.paths.projectContext, "utf8") : "";
|
|
2266
2413
|
let contextOmittedRecent = false;
|
|
2267
2414
|
if (projectContextRaw && input.dedupe_project_context !== false) {
|
|
2268
2415
|
const ctxHash = hashProjectContext(projectContextRaw);
|
|
@@ -2277,7 +2424,7 @@ async function getBriefing(input, ctx) {
|
|
|
2277
2424
|
const setupWarnings = [];
|
|
2278
2425
|
let autoContextGenerated = false;
|
|
2279
2426
|
let projectContext = isTemplateContext ? "" : projectContextRaw;
|
|
2280
|
-
if ((isTemplateContext || !
|
|
2427
|
+
if ((isTemplateContext || !existsSync22(ctx.paths.projectContext)) && input.include_project_context) {
|
|
2281
2428
|
const haiveConfig = await loadConfig3(ctx.paths);
|
|
2282
2429
|
if (haiveConfig.autoContext) {
|
|
2283
2430
|
const codeMap = await loadCodeMap(ctx.paths);
|
|
@@ -2451,8 +2598,8 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
2451
2598
|
actionRequired.push(extractActionItem(m.id, loaded.memory.body));
|
|
2452
2599
|
}
|
|
2453
2600
|
}
|
|
2454
|
-
if (
|
|
2455
|
-
const allMems = await
|
|
2601
|
+
if (existsSync22(ctx.paths.memoriesDir)) {
|
|
2602
|
+
const allMems = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
|
|
2456
2603
|
for (const { memory } of allMems) {
|
|
2457
2604
|
const fm = memory.frontmatter;
|
|
2458
2605
|
if (!fm.requires_human_approval) continue;
|
|
@@ -2462,9 +2609,9 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
2462
2609
|
}
|
|
2463
2610
|
}
|
|
2464
2611
|
const pendingDistillFile = pendingDistillPath(ctx);
|
|
2465
|
-
if (
|
|
2612
|
+
if (existsSync22(pendingDistillFile)) {
|
|
2466
2613
|
try {
|
|
2467
|
-
const raw = await
|
|
2614
|
+
const raw = await readFile7(pendingDistillFile, "utf8");
|
|
2468
2615
|
const pd = JSON.parse(raw);
|
|
2469
2616
|
const ageMs = Date.now() - new Date(pd.session_end).getTime();
|
|
2470
2617
|
const SEVEN_DAYS = 7 * 24 * 60 * 60 * 1e3;
|
|
@@ -2491,7 +2638,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
2491
2638
|
}
|
|
2492
2639
|
}
|
|
2493
2640
|
const memoriesEmpty = outputMemories.length === 0;
|
|
2494
|
-
const hasMemoriesDir =
|
|
2641
|
+
const hasMemoriesDir = existsSync22(ctx.paths.memoriesDir);
|
|
2495
2642
|
const isColdStart = isTemplateContext && memoriesEmpty && !lastSession && !autoContextGenerated;
|
|
2496
2643
|
const hasUnguessableSignal = outputMemories.some(
|
|
2497
2644
|
(m) => (m.priority === "must_read" || m.priority === "useful") && specificityScore(m.body) >= GUESSABLE_THRESHOLD
|
|
@@ -2506,10 +2653,10 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
2506
2653
|
try {
|
|
2507
2654
|
let pcRaw = "";
|
|
2508
2655
|
try {
|
|
2509
|
-
pcRaw = await
|
|
2656
|
+
pcRaw = await readFile7(ctx.paths.projectContext, "utf8");
|
|
2510
2657
|
} catch {
|
|
2511
2658
|
}
|
|
2512
|
-
const allForBootstrap =
|
|
2659
|
+
const allForBootstrap = existsSync22(ctx.paths.memoriesDir) ? await loadMemoriesFromDir17(ctx.paths.memoriesDir) : [];
|
|
2513
2660
|
const cmForBootstrap = await loadCodeMap(ctx.paths);
|
|
2514
2661
|
let existingModules = [];
|
|
2515
2662
|
try {
|
|
@@ -2573,7 +2720,7 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
|
|
|
2573
2720
|
"No team-specific policy matched these files/task \u2014 nothing here a capable model can't infer. The auto-generated project context was trimmed to keep this briefing near-zero-cost; proceed with normal Read/Grep."
|
|
2574
2721
|
);
|
|
2575
2722
|
}
|
|
2576
|
-
if (outputMemories.length > 0 &&
|
|
2723
|
+
if (outputMemories.length > 0 && existsSync22(ctx.paths.haiveDir)) {
|
|
2577
2724
|
const proof = briefingProofLine(await loadPreventionEvents(ctx.paths));
|
|
2578
2725
|
if (proof) hints.push(proof);
|
|
2579
2726
|
}
|
|
@@ -2587,7 +2734,7 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
|
|
|
2587
2734
|
adaptiveTrim
|
|
2588
2735
|
});
|
|
2589
2736
|
const breadcrumbTokens = breadcrumbs ? estimateTokens([...breadcrumbs.start_here, ...breadcrumbs.drill_down, breadcrumbs.note ?? ""].join("\n")) : 0;
|
|
2590
|
-
if (
|
|
2737
|
+
if (existsSync22(ctx.paths.haiveDir)) {
|
|
2591
2738
|
await writeBriefingMarker(ctx.paths, {
|
|
2592
2739
|
sessionId: process.env.HAIVE_SESSION_ID,
|
|
2593
2740
|
...input.task ? { task: input.task } : {},
|
|
@@ -2643,10 +2790,10 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
|
|
|
2643
2790
|
};
|
|
2644
2791
|
}
|
|
2645
2792
|
async function detectRunCommands(root) {
|
|
2646
|
-
const pkgPath =
|
|
2647
|
-
if (!
|
|
2793
|
+
const pkgPath = path12.join(root, "package.json");
|
|
2794
|
+
if (!existsSync22(pkgPath)) return null;
|
|
2648
2795
|
try {
|
|
2649
|
-
const pkg = JSON.parse(await
|
|
2796
|
+
const pkg = JSON.parse(await readFile7(pkgPath, "utf8"));
|
|
2650
2797
|
const scripts = pkg.scripts ?? {};
|
|
2651
2798
|
const order = ["test", "build", "lint", "typecheck", "type-check", "dev", "start"];
|
|
2652
2799
|
const lines = order.filter((name) => typeof scripts[name] === "string" && scripts[name].trim() !== "").map((name) => `- \`${name}\`: \`${scripts[name]}\``);
|
|
@@ -2711,24 +2858,24 @@ function oneLine(value) {
|
|
|
2711
2858
|
return value.replace(/\s+/g, " ").replace(/"/g, '\\"').trim().slice(0, 120);
|
|
2712
2859
|
}
|
|
2713
2860
|
function serverVersion() {
|
|
2714
|
-
return true ? "0.
|
|
2861
|
+
return true ? "0.39.0" : "dev";
|
|
2715
2862
|
}
|
|
2716
2863
|
|
|
2717
2864
|
// src/tools/code-map.ts
|
|
2718
2865
|
import { estimateTokens as estimateTokens2, loadCodeMap as loadCodeMap2, queryCodeMap as queryCodeMap2 } from "@hivelore/core";
|
|
2719
|
-
import { z as
|
|
2866
|
+
import { z as z21 } from "zod";
|
|
2720
2867
|
var CodeMapInputSchema = {
|
|
2721
|
-
file:
|
|
2722
|
-
symbol:
|
|
2723
|
-
paths:
|
|
2868
|
+
file: z21.string().optional().describe("Filter to files whose path contains this substring"),
|
|
2869
|
+
symbol: z21.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
|
|
2870
|
+
paths: z21.array(z21.string()).default([]).describe(
|
|
2724
2871
|
"Filter to files under any of these path prefixes (e.g. ['packages/mcp/src/tools/', 'src/auth/']). OR-joined with `file` substring; useful to get a focused view of one module."
|
|
2725
2872
|
),
|
|
2726
|
-
max_files:
|
|
2727
|
-
max_tokens:
|
|
2873
|
+
max_files: z21.number().int().positive().default(40).describe("Cap on returned files (hard limit, applied after token budget)"),
|
|
2874
|
+
max_tokens: z21.number().int().positive().optional().describe(
|
|
2728
2875
|
"Approximate token budget for the response. When the matching set exceeds it, files are ranked by export density (exports per LOC) and the highest-signal ones are kept first. Omit to disable budgeting (legacy behavior)."
|
|
2729
2876
|
)
|
|
2730
2877
|
};
|
|
2731
|
-
var CodeMapInputZod =
|
|
2878
|
+
var CodeMapInputZod = z21.object(CodeMapInputSchema);
|
|
2732
2879
|
async function codeMapTool(input, ctx) {
|
|
2733
2880
|
const map = await loadCodeMap2(ctx.paths);
|
|
2734
2881
|
if (!map) {
|
|
@@ -2797,18 +2944,18 @@ function estimateFileEntryTokens(f) {
|
|
|
2797
2944
|
}
|
|
2798
2945
|
|
|
2799
2946
|
// src/tools/mem-diff.ts
|
|
2800
|
-
import { existsSync as
|
|
2801
|
-
import { loadMemoriesFromDir as
|
|
2802
|
-
import { z as
|
|
2947
|
+
import { existsSync as existsSync23 } from "fs";
|
|
2948
|
+
import { loadMemoriesFromDir as loadMemoriesFromDir18 } from "@hivelore/core";
|
|
2949
|
+
import { z as z22 } from "zod";
|
|
2803
2950
|
var MemDiffInputSchema = {
|
|
2804
|
-
id_a:
|
|
2805
|
-
id_b:
|
|
2951
|
+
id_a: z22.string().min(1).describe("First memory id"),
|
|
2952
|
+
id_b: z22.string().min(1).describe("Second memory id")
|
|
2806
2953
|
};
|
|
2807
2954
|
async function memDiff(input, ctx) {
|
|
2808
|
-
if (!
|
|
2955
|
+
if (!existsSync23(ctx.paths.memoriesDir)) {
|
|
2809
2956
|
throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
|
|
2810
2957
|
}
|
|
2811
|
-
const all = await
|
|
2958
|
+
const all = await loadMemoriesFromDir18(ctx.paths.memoriesDir);
|
|
2812
2959
|
const foundA = all.find((m) => m.memory.frontmatter.id === input.id_a);
|
|
2813
2960
|
const foundB = all.find((m) => m.memory.frontmatter.id === input.id_b);
|
|
2814
2961
|
if (!foundA) throw new Error(`No memory with id "${input.id_a}".`);
|
|
@@ -2842,19 +2989,19 @@ async function memDiff(input, ctx) {
|
|
|
2842
2989
|
}
|
|
2843
2990
|
|
|
2844
2991
|
// src/tools/get-recap.ts
|
|
2845
|
-
import { existsSync as
|
|
2846
|
-
import { loadMemoriesFromDir as
|
|
2847
|
-
import { z as
|
|
2992
|
+
import { existsSync as existsSync24 } from "fs";
|
|
2993
|
+
import { loadMemoriesFromDir as loadMemoriesFromDir19 } from "@hivelore/core";
|
|
2994
|
+
import { z as z23 } from "zod";
|
|
2848
2995
|
var GetRecapInputSchema = {
|
|
2849
|
-
scope:
|
|
2996
|
+
scope: z23.enum(["personal", "team", "any"]).default("any").describe(
|
|
2850
2997
|
"Limit to a specific scope's recap. Default 'any' returns the most recent recap across both personal and team scopes."
|
|
2851
2998
|
)
|
|
2852
2999
|
};
|
|
2853
3000
|
async function getRecap(input, ctx) {
|
|
2854
|
-
if (!
|
|
3001
|
+
if (!existsSync24(ctx.paths.memoriesDir)) {
|
|
2855
3002
|
return { recap: null, notice: "No .ai/memories directory \u2014 haive not initialized here." };
|
|
2856
3003
|
}
|
|
2857
|
-
const all = await
|
|
3004
|
+
const all = await loadMemoriesFromDir19(ctx.paths.memoriesDir);
|
|
2858
3005
|
const recaps = all.filter(({ memory }) => memory.frontmatter.type === "session_recap").filter(({ memory }) => input.scope === "any" || memory.frontmatter.scope === input.scope).sort(
|
|
2859
3006
|
(a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
|
|
2860
3007
|
);
|
|
@@ -2878,13 +3025,13 @@ async function getRecap(input, ctx) {
|
|
|
2878
3025
|
}
|
|
2879
3026
|
|
|
2880
3027
|
// src/tools/mem-relevant-to.ts
|
|
2881
|
-
import { z as
|
|
3028
|
+
import { z as z24 } from "zod";
|
|
2882
3029
|
var MemRelevantToInputSchema = {
|
|
2883
|
-
task:
|
|
2884
|
-
files:
|
|
2885
|
-
limit:
|
|
2886
|
-
min_semantic_score:
|
|
2887
|
-
format:
|
|
3030
|
+
task: z24.string().min(1).describe("What you are about to do, in 1\u20132 sentences. Used to rank relevant memories."),
|
|
3031
|
+
files: z24.array(z24.string()).default([]).describe("Optional: files you are about to edit \u2014 surfaces anchored memories."),
|
|
3032
|
+
limit: z24.number().int().positive().max(30).default(8).describe("Cap on returned memories."),
|
|
3033
|
+
min_semantic_score: z24.number().min(0).max(1).default(0.25).describe("Drop weakly-related semantic hits below this cosine threshold."),
|
|
3034
|
+
format: z24.enum(["full", "compact", "actions"]).default("full").describe("'compact' = id + 1-line summary; 'full' = complete bodies; 'actions' = bullet-first excerpts.")
|
|
2888
3035
|
};
|
|
2889
3036
|
async function memRelevantTo(input, ctx) {
|
|
2890
3037
|
const briefingInput = {
|
|
@@ -2914,14 +3061,14 @@ async function memRelevantTo(input, ctx) {
|
|
|
2914
3061
|
}
|
|
2915
3062
|
|
|
2916
3063
|
// src/tools/code-search.ts
|
|
2917
|
-
import { z as
|
|
3064
|
+
import { z as z25 } from "zod";
|
|
2918
3065
|
import { loadCodeMap as loadCodeMap3 } from "@hivelore/core";
|
|
2919
3066
|
var CodeSearchInputSchema = {
|
|
2920
|
-
query:
|
|
3067
|
+
query: z25.string().min(1).describe(
|
|
2921
3068
|
"Natural-language description of what you are looking for in the codebase (e.g. 'function that hashes passwords', 'JWT signing logic', 'route registration')."
|
|
2922
3069
|
),
|
|
2923
|
-
k:
|
|
2924
|
-
min_score:
|
|
3070
|
+
k: z25.number().int().positive().max(50).default(5).describe("Number of top hits to return."),
|
|
3071
|
+
min_score: z25.number().min(0).max(1).default(0.2).describe(
|
|
2925
3072
|
"Minimum cosine similarity. Hits below this threshold are dropped to avoid noise. Try 0.3+ for stricter matching."
|
|
2926
3073
|
)
|
|
2927
3074
|
};
|
|
@@ -2964,7 +3111,7 @@ async function codeSearch(input, ctx) {
|
|
|
2964
3111
|
}
|
|
2965
3112
|
|
|
2966
3113
|
// src/tools/anti-patterns-check.ts
|
|
2967
|
-
import { existsSync as
|
|
3114
|
+
import { existsSync as existsSync25 } from "fs";
|
|
2968
3115
|
import {
|
|
2969
3116
|
addedLinesFromDiff,
|
|
2970
3117
|
BRIDGE_TARGET_PATH,
|
|
@@ -2974,7 +3121,7 @@ import {
|
|
|
2974
3121
|
diffHasDistinctiveOverlap,
|
|
2975
3122
|
getUsage as getUsage7,
|
|
2976
3123
|
isRetiredMemory as isRetiredMemory2,
|
|
2977
|
-
loadMemoriesFromDir as
|
|
3124
|
+
loadMemoriesFromDir as loadMemoriesFromDir20,
|
|
2978
3125
|
loadUsageIndex as loadUsageIndex9,
|
|
2979
3126
|
literalMatchesAnyToken as literalMatchesAnyToken3,
|
|
2980
3127
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths3,
|
|
@@ -2983,19 +3130,19 @@ import {
|
|
|
2983
3130
|
sensorTargetsFromDiff,
|
|
2984
3131
|
tokenizeQuery as tokenizeQuery3
|
|
2985
3132
|
} from "@hivelore/core";
|
|
2986
|
-
import { z as
|
|
3133
|
+
import { z as z26 } from "zod";
|
|
2987
3134
|
var AntiPatternsCheckInputSchema = {
|
|
2988
|
-
diff:
|
|
3135
|
+
diff: z26.string().optional().describe(
|
|
2989
3136
|
"Raw unified diff text (or any code/text snippet) to scan for previously documented anti-patterns. Tokens from the diff are used to match memory bodies and the embeddings index."
|
|
2990
3137
|
),
|
|
2991
|
-
paths:
|
|
3138
|
+
paths: z26.array(z26.string()).default([]).describe(
|
|
2992
3139
|
"File paths affected by the change. Memories anchored to any of these paths are surfaced regardless of the diff content."
|
|
2993
3140
|
),
|
|
2994
|
-
limit:
|
|
2995
|
-
semantic:
|
|
3141
|
+
limit: z26.number().int().positive().max(20).default(8).describe("Cap on returned warnings."),
|
|
3142
|
+
semantic: z26.boolean().default(true).describe(
|
|
2996
3143
|
"When true, also use semantic search (requires @hivelore/embeddings + memory index) to find related anti-patterns."
|
|
2997
3144
|
),
|
|
2998
|
-
min_semantic_score:
|
|
3145
|
+
min_semantic_score: z26.number().min(0).max(1).default(0.45).describe(
|
|
2999
3146
|
"Minimum cosine score for semantic-only anti-pattern hits. Anchor/literal matches still surface. Default 0.45 keeps broad, weakly-related memories out of review noise."
|
|
3000
3147
|
)
|
|
3001
3148
|
};
|
|
@@ -3079,10 +3226,10 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
3079
3226
|
notice: "Nothing to check \u2014 provide either `diff` text or `paths`."
|
|
3080
3227
|
};
|
|
3081
3228
|
}
|
|
3082
|
-
if (!
|
|
3229
|
+
if (!existsSync25(ctx.paths.memoriesDir)) {
|
|
3083
3230
|
return { scanned: 0, warnings: [], notice: "No .ai/memories directory \u2014 nothing to check against." };
|
|
3084
3231
|
}
|
|
3085
|
-
const all = await
|
|
3232
|
+
const all = await loadMemoriesFromDir20(ctx.paths.memoriesDir);
|
|
3086
3233
|
const minSemanticScore = input.min_semantic_score ?? 0.45;
|
|
3087
3234
|
const negative = all.filter(({ memory }) => {
|
|
3088
3235
|
const t = memory.frontmatter.type;
|
|
@@ -3198,19 +3345,19 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
3198
3345
|
}
|
|
3199
3346
|
|
|
3200
3347
|
// src/tools/mem-distill.ts
|
|
3201
|
-
import { existsSync as
|
|
3348
|
+
import { existsSync as existsSync26 } from "fs";
|
|
3202
3349
|
import {
|
|
3203
|
-
loadMemoriesFromDir as
|
|
3350
|
+
loadMemoriesFromDir as loadMemoriesFromDir21,
|
|
3204
3351
|
tokenizeQuery as tokenizeQuery4
|
|
3205
3352
|
} from "@hivelore/core";
|
|
3206
|
-
import { z as
|
|
3353
|
+
import { z as z27 } from "zod";
|
|
3207
3354
|
var MemDistillInputSchema = {
|
|
3208
|
-
since_days:
|
|
3209
|
-
min_cluster:
|
|
3210
|
-
type_filter:
|
|
3355
|
+
since_days: z27.number().int().positive().default(30).describe("Only consider memories created in the last N days."),
|
|
3356
|
+
min_cluster: z27.number().int().min(2).default(3).describe("Minimum cluster size to surface."),
|
|
3357
|
+
type_filter: z27.enum(["gotcha", "attempt", "all"]).default("gotcha").describe(
|
|
3211
3358
|
"Memory type to scan. 'gotcha' targets observe-style discoveries that recur, 'attempt' surfaces failed approaches that repeat, 'all' considers both."
|
|
3212
3359
|
),
|
|
3213
|
-
scope:
|
|
3360
|
+
scope: z27.enum(["personal", "team", "module", "any"]).default("any").describe("Restrict to a specific scope.")
|
|
3214
3361
|
};
|
|
3215
3362
|
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
3216
3363
|
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
@@ -3250,11 +3397,11 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
|
3250
3397
|
"error"
|
|
3251
3398
|
]);
|
|
3252
3399
|
async function memDistill(input, ctx) {
|
|
3253
|
-
if (!
|
|
3400
|
+
if (!existsSync26(ctx.paths.memoriesDir)) {
|
|
3254
3401
|
return { scanned: 0, singletons: 0, clusters: [], notice: "No .ai/memories directory." };
|
|
3255
3402
|
}
|
|
3256
3403
|
const cutoff = Date.now() - input.since_days * MS_PER_DAY;
|
|
3257
|
-
const all = await
|
|
3404
|
+
const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
|
|
3258
3405
|
const candidates = all.filter(({ memory }) => {
|
|
3259
3406
|
const fm = memory.frontmatter;
|
|
3260
3407
|
if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
|
|
@@ -3359,17 +3506,17 @@ function firstHeading(body) {
|
|
|
3359
3506
|
|
|
3360
3507
|
// src/tools/precommit-check.ts
|
|
3361
3508
|
import { pathsOverlap as pathsOverlap2 } from "@hivelore/core";
|
|
3362
|
-
import { z as
|
|
3509
|
+
import { z as z28 } from "zod";
|
|
3363
3510
|
var PreCommitCheckInputSchema = {
|
|
3364
|
-
diff:
|
|
3511
|
+
diff: z28.string().optional().describe(
|
|
3365
3512
|
"Raw unified diff text to scan. If omitted, only `paths` is used. When called from a pre-commit hook, pipe the output of `git diff --cached`."
|
|
3366
3513
|
),
|
|
3367
|
-
paths:
|
|
3368
|
-
block_on:
|
|
3514
|
+
paths: z28.array(z28.string()).default([]).describe("Project-relative paths affected by the change. At least one of `diff` or `paths` should be provided."),
|
|
3515
|
+
block_on: z28.enum(["any", "high-confidence", "never"]).default("high-confidence").describe(
|
|
3369
3516
|
"When to set should_block=true: 'any' = any warning blocks; 'high-confidence' = only warnings from authoritative/trusted memories block; 'never' = report only, never block."
|
|
3370
3517
|
),
|
|
3371
|
-
semantic:
|
|
3372
|
-
anchored_blocks:
|
|
3518
|
+
semantic: z28.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index)."),
|
|
3519
|
+
anchored_blocks: z28.boolean().default(false).describe(
|
|
3373
3520
|
"When true, ALSO block a high-confidence anti-pattern (attempt/gotcha) that is anchored to a touched file AND corroborated by the diff (literal token overlap, or semantic >= 0.45) \u2014 not just very strong semantic matches. Powers the 'anchored' enforcement gate. Config/docs-only commits are still downgraded. Default false preserves the soft, semantic-only blocking behavior."
|
|
3374
3521
|
)
|
|
3375
3522
|
};
|
|
@@ -3675,14 +3822,14 @@ function repairTargetPathForWarning(warning, paths) {
|
|
|
3675
3822
|
}
|
|
3676
3823
|
|
|
3677
3824
|
// src/tools/mem-conflict-candidates.ts
|
|
3678
|
-
import { existsSync as
|
|
3825
|
+
import { existsSync as existsSync27 } from "fs";
|
|
3679
3826
|
import {
|
|
3680
3827
|
findLexicalConflictPairs,
|
|
3681
3828
|
findTopicStatusConflictPairs,
|
|
3682
|
-
loadMemoriesFromDir as
|
|
3829
|
+
loadMemoriesFromDir as loadMemoriesFromDir22,
|
|
3683
3830
|
planConflictResolution
|
|
3684
3831
|
} from "@hivelore/core";
|
|
3685
|
-
import { z as
|
|
3832
|
+
import { z as z29 } from "zod";
|
|
3686
3833
|
function suggestResolution(byId, idA, idB) {
|
|
3687
3834
|
const a = byId.get(idA);
|
|
3688
3835
|
const b = byId.get(idB);
|
|
@@ -3696,17 +3843,17 @@ function suggestResolution(byId, idA, idB) {
|
|
|
3696
3843
|
};
|
|
3697
3844
|
}
|
|
3698
3845
|
var MemConflictCandidatesInputSchema = {
|
|
3699
|
-
since_days:
|
|
3700
|
-
types:
|
|
3701
|
-
min_jaccard:
|
|
3702
|
-
max_pairs:
|
|
3703
|
-
max_scan:
|
|
3704
|
-
max_topic_pairs:
|
|
3846
|
+
since_days: z29.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
|
|
3847
|
+
types: z29.array(z29.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
|
|
3848
|
+
min_jaccard: z29.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
|
|
3849
|
+
max_pairs: z29.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
|
|
3850
|
+
max_scan: z29.number().int().positive().max(2e3).default(500).describe("Maximum memories sampled for O(n\xB2) scan \u2014 excess dropped after chronological sort."),
|
|
3851
|
+
max_topic_pairs: z29.number().int().positive().max(100).default(20).describe(
|
|
3705
3852
|
"Cap for extra signal: memories sharing the same topic with validated vs rejected status."
|
|
3706
3853
|
)
|
|
3707
3854
|
};
|
|
3708
3855
|
async function memConflictCandidates(input, ctx) {
|
|
3709
|
-
if (!
|
|
3856
|
+
if (!existsSync27(ctx.paths.memoriesDir)) {
|
|
3710
3857
|
return {
|
|
3711
3858
|
pairs: [],
|
|
3712
3859
|
topic_status_pairs: [],
|
|
@@ -3715,7 +3862,7 @@ async function memConflictCandidates(input, ctx) {
|
|
|
3715
3862
|
notice: "No .ai/memories directory."
|
|
3716
3863
|
};
|
|
3717
3864
|
}
|
|
3718
|
-
const all = await
|
|
3865
|
+
const all = await loadMemoriesFromDir22(ctx.paths.memoriesDir);
|
|
3719
3866
|
const byId = new Map(all.map((m) => [m.memory.frontmatter.id, m]));
|
|
3720
3867
|
const { pairs, scanned, truncated } = findLexicalConflictPairs(all, {
|
|
3721
3868
|
sinceDays: input.since_days,
|
|
@@ -3745,9 +3892,9 @@ async function memConflictCandidates(input, ctx) {
|
|
|
3745
3892
|
|
|
3746
3893
|
// src/tools/mem-resolve-project.ts
|
|
3747
3894
|
import { resolveProjectInfo } from "@hivelore/core";
|
|
3748
|
-
import { z as
|
|
3895
|
+
import { z as z30 } from "zod";
|
|
3749
3896
|
var MemResolveProjectInputSchema = {
|
|
3750
|
-
cwd:
|
|
3897
|
+
cwd: z30.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
|
|
3751
3898
|
};
|
|
3752
3899
|
async function memResolveProject(input, _ctx) {
|
|
3753
3900
|
void _ctx;
|
|
@@ -3761,10 +3908,10 @@ async function memResolveProject(input, _ctx) {
|
|
|
3761
3908
|
|
|
3762
3909
|
// src/tools/mem-suggest-topic.ts
|
|
3763
3910
|
import { MemoryTypeSchema, suggestTopicKey } from "@hivelore/core";
|
|
3764
|
-
import { z as
|
|
3911
|
+
import { z as z31 } from "zod";
|
|
3765
3912
|
var MemSuggestTopicInputSchema = {
|
|
3766
3913
|
type: MemoryTypeSchema.describe("Memory kind \u2014 drives the suggested topic family."),
|
|
3767
|
-
title:
|
|
3914
|
+
title: z31.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
|
|
3768
3915
|
};
|
|
3769
3916
|
async function memSuggestTopic(input, _ctx) {
|
|
3770
3917
|
void _ctx;
|
|
@@ -3773,19 +3920,19 @@ async function memSuggestTopic(input, _ctx) {
|
|
|
3773
3920
|
}
|
|
3774
3921
|
|
|
3775
3922
|
// src/tools/mem-timeline.ts
|
|
3776
|
-
import { existsSync as
|
|
3777
|
-
import { collectTimelineEntries, loadMemoriesFromDir as
|
|
3778
|
-
import { z as
|
|
3923
|
+
import { existsSync as existsSync28 } from "fs";
|
|
3924
|
+
import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir23 } from "@hivelore/core";
|
|
3925
|
+
import { z as z32 } from "zod";
|
|
3779
3926
|
var MemTimelineInputSchema = {
|
|
3780
|
-
memory_id:
|
|
3781
|
-
topic:
|
|
3782
|
-
limit:
|
|
3927
|
+
memory_id: z32.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
|
|
3928
|
+
topic: z32.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
|
|
3929
|
+
limit: z32.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
|
|
3783
3930
|
};
|
|
3784
3931
|
async function memTimeline(input, ctx) {
|
|
3785
|
-
if (!
|
|
3932
|
+
if (!existsSync28(ctx.paths.memoriesDir)) {
|
|
3786
3933
|
return { entries: [], total: 0, notice: "No .ai/memories directory." };
|
|
3787
3934
|
}
|
|
3788
|
-
const all = await
|
|
3935
|
+
const all = await loadMemoriesFromDir23(ctx.paths.memoriesDir);
|
|
3789
3936
|
const { entries, notice } = collectTimelineEntries(all, {
|
|
3790
3937
|
memoryId: input.memory_id,
|
|
3791
3938
|
topic: input.topic,
|
|
@@ -3795,12 +3942,12 @@ async function memTimeline(input, ctx) {
|
|
|
3795
3942
|
}
|
|
3796
3943
|
|
|
3797
3944
|
// src/prompts/bootstrap-project.ts
|
|
3798
|
-
import { z as
|
|
3945
|
+
import { z as z33 } from "zod";
|
|
3799
3946
|
var BootstrapProjectArgsSchema = {
|
|
3800
|
-
module:
|
|
3947
|
+
module: z33.string().optional().describe(
|
|
3801
3948
|
"Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
|
|
3802
3949
|
),
|
|
3803
|
-
focus:
|
|
3950
|
+
focus: z33.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
|
|
3804
3951
|
};
|
|
3805
3952
|
var ROOT_TEMPLATE = `# Project context
|
|
3806
3953
|
|
|
@@ -3882,25 +4029,25 @@ ${template}\`\`\`
|
|
|
3882
4029
|
}
|
|
3883
4030
|
|
|
3884
4031
|
// src/prompts/bootstrap-repo.ts
|
|
3885
|
-
import { readFile as
|
|
3886
|
-
import { existsSync as
|
|
4032
|
+
import { readFile as readFile8, readdir as readdir5 } from "fs/promises";
|
|
4033
|
+
import { existsSync as existsSync29 } from "fs";
|
|
3887
4034
|
import {
|
|
3888
4035
|
assessBootstrapState as assessBootstrapState2,
|
|
3889
4036
|
loadCodeMap as loadCodeMap4,
|
|
3890
|
-
loadMemoriesFromDir as
|
|
4037
|
+
loadMemoriesFromDir as loadMemoriesFromDir24,
|
|
3891
4038
|
renderBootstrapChecklist as renderBootstrapChecklist2
|
|
3892
4039
|
} from "@hivelore/core";
|
|
3893
|
-
import { z as
|
|
4040
|
+
import { z as z34 } from "zod";
|
|
3894
4041
|
var BootstrapRepoArgsSchema = {
|
|
3895
|
-
focus:
|
|
4042
|
+
focus: z34.string().optional().describe("Optional area to emphasize first (e.g. 'payments', 'auth').")
|
|
3896
4043
|
};
|
|
3897
4044
|
async function currentAssessment(ctx) {
|
|
3898
4045
|
let projectContextRaw = "";
|
|
3899
4046
|
try {
|
|
3900
|
-
projectContextRaw = await
|
|
4047
|
+
projectContextRaw = await readFile8(ctx.paths.projectContext, "utf8");
|
|
3901
4048
|
} catch {
|
|
3902
4049
|
}
|
|
3903
|
-
const memories =
|
|
4050
|
+
const memories = existsSync29(ctx.paths.memoriesDir) ? await loadMemoriesFromDir24(ctx.paths.memoriesDir) : [];
|
|
3904
4051
|
const codeMap = await loadCodeMap4(ctx.paths);
|
|
3905
4052
|
let existingModules = [];
|
|
3906
4053
|
try {
|
|
@@ -3968,10 +4115,10 @@ Main code areas detected: ${areas}
|
|
|
3968
4115
|
}
|
|
3969
4116
|
|
|
3970
4117
|
// src/prompts/post-task.ts
|
|
3971
|
-
import { z as
|
|
4118
|
+
import { z as z35 } from "zod";
|
|
3972
4119
|
var PostTaskArgsSchema = {
|
|
3973
|
-
task_summary:
|
|
3974
|
-
files_touched:
|
|
4120
|
+
task_summary: z35.string().optional().describe("One sentence describing what you just did"),
|
|
4121
|
+
files_touched: z35.array(z35.string()).optional().describe("Files you created or modified during the task")
|
|
3975
4122
|
};
|
|
3976
4123
|
function postTaskPrompt(args, ctx) {
|
|
3977
4124
|
const taskLine = args.task_summary ? `
|
|
@@ -4068,12 +4215,12 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
|
|
|
4068
4215
|
}
|
|
4069
4216
|
|
|
4070
4217
|
// src/prompts/import-docs.ts
|
|
4071
|
-
import { z as
|
|
4218
|
+
import { z as z36 } from "zod";
|
|
4072
4219
|
var ImportDocsArgsSchema = {
|
|
4073
|
-
content:
|
|
4074
|
-
source:
|
|
4075
|
-
scope:
|
|
4076
|
-
dry_run:
|
|
4220
|
+
content: z36.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
|
|
4221
|
+
source: z36.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
|
|
4222
|
+
scope: z36.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
|
|
4223
|
+
dry_run: z36.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
|
|
4077
4224
|
};
|
|
4078
4225
|
function importDocsPrompt(args, ctx) {
|
|
4079
4226
|
const sourceLine = args.source ? `
|
|
@@ -4139,7 +4286,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
4139
4286
|
// src/server.ts
|
|
4140
4287
|
import { hasRecentBriefingMarker, loadConfigSync } from "@hivelore/core";
|
|
4141
4288
|
var SERVER_NAME = "hivelore";
|
|
4142
|
-
var SERVER_VERSION = "0.
|
|
4289
|
+
var SERVER_VERSION = "0.39.0";
|
|
4143
4290
|
function jsonResult(data) {
|
|
4144
4291
|
return {
|
|
4145
4292
|
content: [
|
|
@@ -4162,7 +4309,8 @@ var ENFORCEMENT_PROFILE_TOOLS = [
|
|
|
4162
4309
|
"code_search",
|
|
4163
4310
|
"pre_commit_check",
|
|
4164
4311
|
"mem_session_end",
|
|
4165
|
-
"propose_sensor"
|
|
4312
|
+
"propose_sensor",
|
|
4313
|
+
"scaffold_test"
|
|
4166
4314
|
];
|
|
4167
4315
|
var MAINTENANCE_PROFILE_TOOLS = [
|
|
4168
4316
|
...ENFORCEMENT_PROFILE_TOOLS,
|
|
@@ -4368,6 +4516,35 @@ function createHaiveServer(options = {}) {
|
|
|
4368
4516
|
return jsonResult(await proposeSensor(input, context));
|
|
4369
4517
|
}
|
|
4370
4518
|
);
|
|
4519
|
+
registerTool(
|
|
4520
|
+
"scaffold_test",
|
|
4521
|
+
[
|
|
4522
|
+
"Generate a PENDING post-incident test from a lesson (attempt/gotcha) \u2014 the on-ramp to a command",
|
|
4523
|
+
"sensor. A command sensor routes YOUR test as its oracle, but someone has to write it; this writes",
|
|
4524
|
+
"the skeleton so you only fill in the assertion.",
|
|
4525
|
+
"",
|
|
4526
|
+
"USE THIS right after mem_tried when the mistake is behavioural (a regex can't express it): it",
|
|
4527
|
+
"writes a stub carrying the incident's provenance and returns the exact `sensors propose --kind",
|
|
4528
|
+
"test` command to arm it.",
|
|
4529
|
+
"",
|
|
4530
|
+
"It DOES NOT arm a sensor \u2014 propose_sensor stays the sole validated writer, and the stub is left",
|
|
4531
|
+
"PENDING (todo/skip) so the suite stays green until you write the assertion. Monorepo-aware: the",
|
|
4532
|
+
"framework and location come from the package that owns the lesson's anchor paths.",
|
|
4533
|
+
"",
|
|
4534
|
+
"PARAMETERS:",
|
|
4535
|
+
" memory_id \u2014 the attempt/gotcha to scaffold from",
|
|
4536
|
+
" framework \u2014 vitest | jest | pytest | gotest (auto-detected when omitted)",
|
|
4537
|
+
" out_path \u2014 override the test file path (repo-relative)",
|
|
4538
|
+
" write \u2014 write the file (default true); false returns the content for preview",
|
|
4539
|
+
"",
|
|
4540
|
+
"RETURNS: { ok, path, run_command, propose_command, content, written, already_exists, notice }"
|
|
4541
|
+
].join("\n"),
|
|
4542
|
+
ScaffoldTestInputSchema,
|
|
4543
|
+
async (input) => {
|
|
4544
|
+
tracker.record("scaffold_test", input.memory_id);
|
|
4545
|
+
return jsonResult(await scaffoldTest(input, context));
|
|
4546
|
+
}
|
|
4547
|
+
);
|
|
4371
4548
|
registerTool(
|
|
4372
4549
|
"ingest_findings",
|
|
4373
4550
|
[
|
|
@@ -5064,6 +5241,8 @@ export {
|
|
|
5064
5241
|
codeMapTool,
|
|
5065
5242
|
codeSearch,
|
|
5066
5243
|
createHaiveServer,
|
|
5244
|
+
detectTestFrameworkForPaths,
|
|
5245
|
+
detectTestFrameworksForAnchors,
|
|
5067
5246
|
getAllowedToolsForProfile,
|
|
5068
5247
|
getBriefing,
|
|
5069
5248
|
getRecap,
|
|
@@ -5079,6 +5258,7 @@ export {
|
|
|
5079
5258
|
printHaiveMcpVersion,
|
|
5080
5259
|
proposeSensor,
|
|
5081
5260
|
readPresumedCorrectTargets,
|
|
5082
|
-
runHaiveMcpStdio
|
|
5261
|
+
runHaiveMcpStdio,
|
|
5262
|
+
scaffoldTest
|
|
5083
5263
|
};
|
|
5084
5264
|
//# sourceMappingURL=server.js.map
|