@hivelore/mcp 0.36.0 → 0.38.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 +323 -194
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +42 -5
- package/dist/server.js +326 -195
- package/dist/server.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1450,43 +1450,142 @@ async function memTried(input, ctx) {
|
|
|
1450
1450
|
};
|
|
1451
1451
|
}
|
|
1452
1452
|
|
|
1453
|
-
// src/tools/
|
|
1454
|
-
import { existsSync as existsSync17 } from "fs";
|
|
1453
|
+
// src/tools/scaffold-test.ts
|
|
1454
|
+
import { existsSync as existsSync17, statSync } from "fs";
|
|
1455
1455
|
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile10 } from "fs/promises";
|
|
1456
1456
|
import path7 from "path";
|
|
1457
|
+
import { z as z17 } from "zod";
|
|
1458
|
+
import {
|
|
1459
|
+
loadMemoriesFromDir as loadMemoriesFromDir14,
|
|
1460
|
+
normalizeFramework,
|
|
1461
|
+
parseLessonFields,
|
|
1462
|
+
pickTestFramework,
|
|
1463
|
+
scaffoldPostIncidentTest
|
|
1464
|
+
} from "@hivelore/core";
|
|
1465
|
+
var PY_SIGNALS = ["pyproject.toml", "setup.py", "pytest.ini", "requirements.txt", "tox.ini"];
|
|
1466
|
+
async function detectTestFrameworkForPaths(root, anchorPaths) {
|
|
1467
|
+
const starts = anchorPaths.length > 0 ? anchorPaths : ["."];
|
|
1468
|
+
for (const rel of starts) {
|
|
1469
|
+
let dir = path7.resolve(root, rel);
|
|
1470
|
+
try {
|
|
1471
|
+
if (!statSync(dir).isDirectory()) dir = path7.dirname(dir);
|
|
1472
|
+
} catch {
|
|
1473
|
+
if (path7.extname(dir)) dir = path7.dirname(dir);
|
|
1474
|
+
}
|
|
1475
|
+
while (dir.startsWith(root)) {
|
|
1476
|
+
const pkgJson = path7.join(dir, "package.json");
|
|
1477
|
+
const hasPkg = existsSync17(pkgJson);
|
|
1478
|
+
const goMod = existsSync17(path7.join(dir, "go.mod"));
|
|
1479
|
+
const pySignal = PY_SIGNALS.some((s) => existsSync17(path7.join(dir, s)));
|
|
1480
|
+
if (hasPkg || goMod || pySignal) {
|
|
1481
|
+
let pkg = null;
|
|
1482
|
+
if (hasPkg) {
|
|
1483
|
+
try {
|
|
1484
|
+
pkg = JSON.parse(await readFile4(pkgJson, "utf8"));
|
|
1485
|
+
} catch {
|
|
1486
|
+
pkg = null;
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
const baseDir = path7.relative(root, dir).split(path7.sep).join("/");
|
|
1490
|
+
return { framework: pickTestFramework(pkg, { goMod, pySignal }), baseDir };
|
|
1491
|
+
}
|
|
1492
|
+
const parent = path7.dirname(dir);
|
|
1493
|
+
if (parent === dir || dir === root) break;
|
|
1494
|
+
dir = parent;
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
return { framework: "vitest", baseDir: "" };
|
|
1498
|
+
}
|
|
1499
|
+
var ScaffoldTestInputSchema = {
|
|
1500
|
+
memory_id: z17.string().min(1).describe("Id of the attempt/gotcha lesson to scaffold a post-incident test from."),
|
|
1501
|
+
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."),
|
|
1502
|
+
out_path: z17.string().optional().describe("Override the generated test file path (repo-relative)."),
|
|
1503
|
+
write: z17.boolean().default(true).describe("Write the file to disk (default). false = return the content for preview without writing.")
|
|
1504
|
+
};
|
|
1505
|
+
async function scaffoldTest(input, ctx) {
|
|
1506
|
+
const loaded = existsSync17(ctx.paths.memoriesDir) ? await loadMemoriesFromDir14(ctx.paths.memoriesDir) : [];
|
|
1507
|
+
const found = loaded.find(({ memory }) => memory.frontmatter.id === input.memory_id);
|
|
1508
|
+
if (!found) {
|
|
1509
|
+
return { ok: false, error: `No memory found with id ${input.memory_id}`, memory_id: input.memory_id };
|
|
1510
|
+
}
|
|
1511
|
+
const anchorPaths = found.memory.frontmatter.anchor.paths ?? [];
|
|
1512
|
+
const detected = await detectTestFrameworkForPaths(ctx.paths.root, anchorPaths);
|
|
1513
|
+
const framework = input.framework ? normalizeFramework(input.framework) ?? detected.framework : detected.framework;
|
|
1514
|
+
const fields = parseLessonFields(found.memory.body);
|
|
1515
|
+
const scaffold = scaffoldPostIncidentTest(
|
|
1516
|
+
{
|
|
1517
|
+
memoryId: input.memory_id,
|
|
1518
|
+
title: fields.title || input.memory_id,
|
|
1519
|
+
whyFailed: fields.whyFailed,
|
|
1520
|
+
instead: fields.instead,
|
|
1521
|
+
incident: found.memory.frontmatter.sensor?.incident,
|
|
1522
|
+
paths: anchorPaths
|
|
1523
|
+
},
|
|
1524
|
+
{ framework, outPath: input.out_path, baseDir: detected.baseDir }
|
|
1525
|
+
);
|
|
1526
|
+
const abs = path7.isAbsolute(scaffold.relPath) ? scaffold.relPath : path7.resolve(ctx.paths.root, scaffold.relPath);
|
|
1527
|
+
let written = false;
|
|
1528
|
+
let alreadyExists = false;
|
|
1529
|
+
if (input.write) {
|
|
1530
|
+
if (existsSync17(abs)) {
|
|
1531
|
+
alreadyExists = true;
|
|
1532
|
+
} else {
|
|
1533
|
+
await mkdir4(path7.dirname(abs), { recursive: true });
|
|
1534
|
+
await writeFile10(abs, scaffold.content, "utf8");
|
|
1535
|
+
written = true;
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
return {
|
|
1539
|
+
ok: true,
|
|
1540
|
+
memory_id: input.memory_id,
|
|
1541
|
+
framework,
|
|
1542
|
+
path: scaffold.relPath,
|
|
1543
|
+
run_command: scaffold.runCommand,
|
|
1544
|
+
propose_command: scaffold.proposeCommand,
|
|
1545
|
+
content: scaffold.content,
|
|
1546
|
+
written,
|
|
1547
|
+
already_exists: alreadyExists,
|
|
1548
|
+
notice: alreadyExists ? "File already exists \u2014 not overwritten. Delete it 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."
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
// src/tools/ingest-findings.ts
|
|
1553
|
+
import { existsSync as existsSync18 } from "fs";
|
|
1554
|
+
import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile11 } from "fs/promises";
|
|
1555
|
+
import path8 from "path";
|
|
1457
1556
|
import {
|
|
1458
1557
|
draftsFromFindings,
|
|
1459
1558
|
filterNewDrafts,
|
|
1460
|
-
loadMemoriesFromDir as
|
|
1559
|
+
loadMemoriesFromDir as loadMemoriesFromDir15,
|
|
1461
1560
|
memoryFilePath as memoryFilePath3,
|
|
1462
1561
|
parseFindings,
|
|
1463
1562
|
serializeMemory as serializeMemory9
|
|
1464
1563
|
} from "@hivelore/core";
|
|
1465
|
-
import { z as
|
|
1564
|
+
import { z as z18 } from "zod";
|
|
1466
1565
|
var IngestFindingsInputSchema = {
|
|
1467
|
-
format:
|
|
1468
|
-
report_path:
|
|
1469
|
-
report:
|
|
1470
|
-
type:
|
|
1471
|
-
scope:
|
|
1472
|
-
module:
|
|
1473
|
-
min_severity:
|
|
1474
|
-
include_stylistic:
|
|
1475
|
-
limit:
|
|
1476
|
-
author:
|
|
1477
|
-
dry_run:
|
|
1566
|
+
format: z18.enum(["sarif", "sonar"]).describe("Report format: 'sarif' (ESLint/Semgrep/CodeQL) or 'sonar' (SonarQube issues JSON)"),
|
|
1567
|
+
report_path: z18.string().optional().describe("Project-relative path to the findings JSON file. Provide this OR `report`."),
|
|
1568
|
+
report: z18.string().optional().describe("Inline findings JSON content. Provide this OR `report_path`."),
|
|
1569
|
+
type: z18.enum(["gotcha", "convention"]).default("gotcha").describe("Memory type for the created drafts"),
|
|
1570
|
+
scope: z18.enum(["personal", "team", "module"]).default("team").describe("Visibility scope for the created memories"),
|
|
1571
|
+
module: z18.string().optional().describe("Module name (required when scope=module)"),
|
|
1572
|
+
min_severity: z18.enum(["info", "minor", "major", "critical", "blocker"]).optional().describe("Ignore findings below this severity"),
|
|
1573
|
+
include_stylistic: z18.boolean().optional().describe("Also ingest auto-fixable stylistic rules (semi/quotes/prefer-const\u2026); off by default as low-value noise"),
|
|
1574
|
+
limit: z18.number().int().positive().optional().describe("Cap the number of memories created"),
|
|
1575
|
+
author: z18.string().optional().describe("Author handle or email"),
|
|
1576
|
+
dry_run: z18.boolean().default(false).describe("When true, return the drafts that WOULD be created without writing them")
|
|
1478
1577
|
};
|
|
1479
1578
|
async function ingestFindings(input, ctx) {
|
|
1480
|
-
if (!
|
|
1579
|
+
if (!existsSync18(ctx.paths.haiveDir)) {
|
|
1481
1580
|
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
|
|
1482
1581
|
}
|
|
1483
1582
|
let raw;
|
|
1484
1583
|
if (input.report && input.report.trim()) {
|
|
1485
1584
|
raw = input.report;
|
|
1486
1585
|
} else if (input.report_path) {
|
|
1487
|
-
const file =
|
|
1488
|
-
if (!
|
|
1489
|
-
raw = await
|
|
1586
|
+
const file = path8.resolve(ctx.paths.root, input.report_path);
|
|
1587
|
+
if (!existsSync18(file)) throw new Error(`Report file not found: ${file}`);
|
|
1588
|
+
raw = await readFile5(file, "utf8");
|
|
1490
1589
|
} else {
|
|
1491
1590
|
throw new Error("Provide either `report_path` or `report`.");
|
|
1492
1591
|
}
|
|
@@ -1500,7 +1599,7 @@ async function ingestFindings(input, ctx) {
|
|
|
1500
1599
|
...input.include_stylistic ? { includeStylistic: true } : {},
|
|
1501
1600
|
...input.limit ? { limit: input.limit } : {}
|
|
1502
1601
|
});
|
|
1503
|
-
const existing =
|
|
1602
|
+
const existing = existsSync18(ctx.paths.memoriesDir) ? await loadMemoriesFromDir15(ctx.paths.memoriesDir) : [];
|
|
1504
1603
|
const existingTopics = new Set(
|
|
1505
1604
|
existing.map(({ memory }) => memory.frontmatter.topic).filter((t) => Boolean(t))
|
|
1506
1605
|
);
|
|
@@ -1538,22 +1637,22 @@ async function writeDraft(ctx, draft) {
|
|
|
1538
1637
|
draft.frontmatter.id,
|
|
1539
1638
|
draft.frontmatter.module
|
|
1540
1639
|
);
|
|
1541
|
-
await
|
|
1542
|
-
await
|
|
1640
|
+
await mkdir5(path8.dirname(file), { recursive: true });
|
|
1641
|
+
await writeFile11(file, serializeMemory9({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
|
|
1543
1642
|
return file;
|
|
1544
1643
|
}
|
|
1545
1644
|
|
|
1546
1645
|
// src/tools/mem-session-end.ts
|
|
1547
|
-
import { writeFile as
|
|
1548
|
-
import { existsSync as
|
|
1549
|
-
import
|
|
1646
|
+
import { writeFile as writeFile13, mkdir as mkdir7 } from "fs/promises";
|
|
1647
|
+
import { existsSync as existsSync20 } from "fs";
|
|
1648
|
+
import path10 from "path";
|
|
1550
1649
|
import {
|
|
1551
1650
|
buildFrontmatter as buildFrontmatter3,
|
|
1552
|
-
loadMemoriesFromDir as
|
|
1651
|
+
loadMemoriesFromDir as loadMemoriesFromDir16,
|
|
1553
1652
|
memoryFilePath as memoryFilePath4,
|
|
1554
1653
|
serializeMemory as serializeMemory10
|
|
1555
1654
|
} from "@hivelore/core";
|
|
1556
|
-
import { z as
|
|
1655
|
+
import { z as z19 } from "zod";
|
|
1557
1656
|
|
|
1558
1657
|
// src/session-tracker.ts
|
|
1559
1658
|
import {
|
|
@@ -1562,12 +1661,12 @@ import {
|
|
|
1562
1661
|
loadConfig as loadConfig2,
|
|
1563
1662
|
writeSessionHandoff
|
|
1564
1663
|
} from "@hivelore/core";
|
|
1565
|
-
import { mkdir as
|
|
1566
|
-
import { existsSync as
|
|
1567
|
-
import
|
|
1664
|
+
import { mkdir as mkdir6, writeFile as writeFile12, rm } from "fs/promises";
|
|
1665
|
+
import { existsSync as existsSync19 } from "fs";
|
|
1666
|
+
import path9 from "path";
|
|
1568
1667
|
import { execSync as execSync2 } from "child_process";
|
|
1569
1668
|
function pendingDistillPath(ctx) {
|
|
1570
|
-
return
|
|
1669
|
+
return path9.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
|
|
1571
1670
|
}
|
|
1572
1671
|
var SessionTracker = class {
|
|
1573
1672
|
events = [];
|
|
@@ -1671,7 +1770,7 @@ var SessionTracker = class {
|
|
|
1671
1770
|
(e) => e.tool === "mem_session_end" && !e.summary?.startsWith("Auto-captured")
|
|
1672
1771
|
);
|
|
1673
1772
|
const isSubstantialSession = totalCalls >= 3 || writingTools.length > 0;
|
|
1674
|
-
if (!ranPostTask && isSubstantialSession &&
|
|
1773
|
+
if (!ranPostTask && isSubstantialSession && existsSync19(this.ctx.paths.haiveDir)) {
|
|
1675
1774
|
try {
|
|
1676
1775
|
const memoriesSaved = writingTools.map((e) => e.summary ?? "").filter(Boolean).slice(0, 20);
|
|
1677
1776
|
const payload = {
|
|
@@ -1684,9 +1783,9 @@ var SessionTracker = class {
|
|
|
1684
1783
|
...gitDiff ? { git_diff: gitDiff } : {},
|
|
1685
1784
|
...recapId ? { recap_id: recapId } : {}
|
|
1686
1785
|
};
|
|
1687
|
-
const cacheDir =
|
|
1688
|
-
await
|
|
1689
|
-
await
|
|
1786
|
+
const cacheDir = path9.join(this.ctx.paths.haiveDir, ".cache");
|
|
1787
|
+
await mkdir6(cacheDir, { recursive: true });
|
|
1788
|
+
await writeFile12(
|
|
1690
1789
|
pendingDistillPath(this.ctx),
|
|
1691
1790
|
JSON.stringify(payload, null, 2) + "\n",
|
|
1692
1791
|
"utf8"
|
|
@@ -1705,7 +1804,7 @@ var SessionTracker = class {
|
|
|
1705
1804
|
};
|
|
1706
1805
|
async function clearPendingDistill(ctx) {
|
|
1707
1806
|
const p = pendingDistillPath(ctx);
|
|
1708
|
-
if (
|
|
1807
|
+
if (existsSync19(p)) {
|
|
1709
1808
|
try {
|
|
1710
1809
|
await rm(p);
|
|
1711
1810
|
} catch {
|
|
@@ -1722,15 +1821,15 @@ function summarizeTools(events) {
|
|
|
1722
1821
|
|
|
1723
1822
|
// src/tools/mem-session-end.ts
|
|
1724
1823
|
var MemSessionEndInputSchema = {
|
|
1725
|
-
goal:
|
|
1726
|
-
accomplished:
|
|
1727
|
-
discoveries:
|
|
1824
|
+
goal: z19.string().min(1).describe("What you were trying to accomplish this session (1\u20132 sentences)"),
|
|
1825
|
+
accomplished: z19.string().describe("What was actually done \u2014 bullet list recommended"),
|
|
1826
|
+
discoveries: z19.string().default("").describe(
|
|
1728
1827
|
"Any bugs, inconsistencies, surprises, or missing knowledge found during this session. Empty if nothing surprising was found."
|
|
1729
1828
|
),
|
|
1730
|
-
files_touched:
|
|
1731
|
-
next_steps:
|
|
1732
|
-
scope:
|
|
1733
|
-
module:
|
|
1829
|
+
files_touched: z19.array(z19.string()).default([]).describe("Key files that were read or modified \u2014 used as anchor paths"),
|
|
1830
|
+
next_steps: z19.string().default("").describe("What should happen next (for the next session or a teammate)"),
|
|
1831
|
+
scope: z19.enum(["personal", "team", "module"]).default("personal").describe("Visibility: personal = private to you, team = shared with the team"),
|
|
1832
|
+
module: z19.string().optional().describe("Module name (required when scope=module)")
|
|
1734
1833
|
};
|
|
1735
1834
|
function recapTopic(scope, module) {
|
|
1736
1835
|
return module ? `session-recap-${scope}-${module}` : `session-recap-${scope}`;
|
|
@@ -1760,23 +1859,23 @@ ${input.next_steps}`);
|
|
|
1760
1859
|
return lines.join("\n");
|
|
1761
1860
|
}
|
|
1762
1861
|
async function memSessionEnd(input, ctx) {
|
|
1763
|
-
if (!
|
|
1862
|
+
if (!existsSync20(ctx.paths.haiveDir)) {
|
|
1764
1863
|
throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
|
|
1765
1864
|
}
|
|
1766
1865
|
const body = buildBody(input);
|
|
1767
1866
|
const topic = recapTopic(input.scope, input.module);
|
|
1768
1867
|
const normalizedFiles = input.files_touched.map((p) => {
|
|
1769
|
-
if (!p || !
|
|
1770
|
-
const rel =
|
|
1868
|
+
if (!p || !path10.isAbsolute(p)) return p;
|
|
1869
|
+
const rel = path10.relative(ctx.paths.root, p);
|
|
1771
1870
|
return rel.startsWith("..") ? p : rel;
|
|
1772
1871
|
});
|
|
1773
1872
|
const invalidPaths = normalizedFiles.filter(
|
|
1774
|
-
(p) => !
|
|
1873
|
+
(p) => !existsSync20(path10.resolve(ctx.paths.root, p))
|
|
1775
1874
|
);
|
|
1776
1875
|
if (invalidPaths.length > 0) {
|
|
1777
1876
|
console.warn(`[haive] session end: anchor path(s) not found: ${invalidPaths.join(", ")}`);
|
|
1778
1877
|
}
|
|
1779
|
-
const existing =
|
|
1878
|
+
const existing = existsSync20(ctx.paths.memoriesDir) ? await loadMemoriesFromDir16(ctx.paths.memoriesDir) : [];
|
|
1780
1879
|
const topicMatch = existing.find(
|
|
1781
1880
|
({ memory }) => memory.frontmatter.topic === topic && memory.frontmatter.scope === input.scope && (!input.module || memory.frontmatter.module === input.module)
|
|
1782
1881
|
);
|
|
@@ -1792,7 +1891,7 @@ async function memSessionEnd(input, ctx) {
|
|
|
1792
1891
|
paths: normalizedFiles.length ? normalizedFiles : fm.anchor.paths
|
|
1793
1892
|
}
|
|
1794
1893
|
};
|
|
1795
|
-
await
|
|
1894
|
+
await writeFile13(
|
|
1796
1895
|
topicMatch.filePath,
|
|
1797
1896
|
serializeMemory10({ frontmatter: newFrontmatter, body }),
|
|
1798
1897
|
"utf8"
|
|
@@ -1822,8 +1921,8 @@ async function memSessionEnd(input, ctx) {
|
|
|
1822
1921
|
frontmatter.id,
|
|
1823
1922
|
frontmatter.module
|
|
1824
1923
|
);
|
|
1825
|
-
await
|
|
1826
|
-
await
|
|
1924
|
+
await mkdir7(path10.dirname(file), { recursive: true });
|
|
1925
|
+
await writeFile13(file, serializeMemory10({ frontmatter, body }), "utf8");
|
|
1827
1926
|
await clearPendingDistill(ctx);
|
|
1828
1927
|
return {
|
|
1829
1928
|
id: frontmatter.id,
|
|
@@ -1835,9 +1934,9 @@ async function memSessionEnd(input, ctx) {
|
|
|
1835
1934
|
}
|
|
1836
1935
|
|
|
1837
1936
|
// src/tools/get-briefing.ts
|
|
1838
|
-
import { readFile as
|
|
1839
|
-
import { existsSync as
|
|
1840
|
-
import
|
|
1937
|
+
import { readFile as readFile7, writeFile as writeFile14, readdir as readdir4 } from "fs/promises";
|
|
1938
|
+
import { existsSync as existsSync22 } from "fs";
|
|
1939
|
+
import path12 from "path";
|
|
1841
1940
|
import {
|
|
1842
1941
|
allocateBudget,
|
|
1843
1942
|
assessBootstrapState,
|
|
@@ -1861,7 +1960,7 @@ import {
|
|
|
1861
1960
|
loadConfig as loadConfig3,
|
|
1862
1961
|
memoryHasExcludedTag,
|
|
1863
1962
|
hashProjectContext,
|
|
1864
|
-
loadMemoriesFromDir as
|
|
1963
|
+
loadMemoriesFromDir as loadMemoriesFromDir17,
|
|
1865
1964
|
loadPreventionEvents,
|
|
1866
1965
|
loadUsageIndex as loadUsageIndex8,
|
|
1867
1966
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
|
|
@@ -1879,12 +1978,12 @@ import {
|
|
|
1879
1978
|
truncateToTokens,
|
|
1880
1979
|
writeBriefingMarker
|
|
1881
1980
|
} from "@hivelore/core";
|
|
1882
|
-
import { z as
|
|
1981
|
+
import { z as z20 } from "zod";
|
|
1883
1982
|
|
|
1884
1983
|
// src/tools/briefing-helpers.ts
|
|
1885
|
-
import { readdir as readdir3, readFile as
|
|
1886
|
-
import { existsSync as
|
|
1887
|
-
import
|
|
1984
|
+
import { readdir as readdir3, readFile as readFile6 } from "fs/promises";
|
|
1985
|
+
import { existsSync as existsSync21 } from "fs";
|
|
1986
|
+
import path11 from "path";
|
|
1888
1987
|
import {
|
|
1889
1988
|
classifyMemoryPriority as coreClassifyPriority,
|
|
1890
1989
|
isGlobPath,
|
|
@@ -2017,16 +2116,16 @@ async function trySemanticHits(ctx, task, limit) {
|
|
|
2017
2116
|
}
|
|
2018
2117
|
async function loadModuleContexts2(ctx, modules) {
|
|
2019
2118
|
if (modules.length === 0) return [];
|
|
2020
|
-
if (!
|
|
2119
|
+
if (!existsSync21(ctx.paths.modulesContextDir)) return [];
|
|
2021
2120
|
const available = new Set(
|
|
2022
2121
|
(await readdir3(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
|
|
2023
2122
|
);
|
|
2024
2123
|
const out = [];
|
|
2025
2124
|
for (const m of modules) {
|
|
2026
2125
|
if (!available.has(m)) continue;
|
|
2027
|
-
const file =
|
|
2028
|
-
if (
|
|
2029
|
-
out.push({ name: m, content: await
|
|
2126
|
+
const file = path11.join(ctx.paths.modulesContextDir, m, "context.md");
|
|
2127
|
+
if (existsSync21(file)) {
|
|
2128
|
+
out.push({ name: m, content: await readFile6(file, "utf8") });
|
|
2030
2129
|
}
|
|
2031
2130
|
}
|
|
2032
2131
|
return out;
|
|
@@ -2034,38 +2133,38 @@ async function loadModuleContexts2(ctx, modules) {
|
|
|
2034
2133
|
|
|
2035
2134
|
// src/tools/get-briefing.ts
|
|
2036
2135
|
var GetBriefingInputSchema = {
|
|
2037
|
-
task:
|
|
2136
|
+
task: z20.string().optional().describe(
|
|
2038
2137
|
"What you are about to do, in 1\u20132 sentences. Used to rank relevant memories semantically."
|
|
2039
2138
|
),
|
|
2040
|
-
files:
|
|
2041
|
-
max_tokens:
|
|
2139
|
+
files: z20.array(z20.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
|
|
2140
|
+
max_tokens: z20.number().int().positive().default(8e3).describe(
|
|
2042
2141
|
"Approximate token budget for the entire briefing. Each section is allocated a share and truncated to fit."
|
|
2043
2142
|
),
|
|
2044
|
-
max_memories:
|
|
2045
|
-
include_project_context:
|
|
2046
|
-
dedupe_project_context:
|
|
2143
|
+
max_memories: z20.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
|
|
2144
|
+
include_project_context: z20.boolean().default(true),
|
|
2145
|
+
dedupe_project_context: z20.boolean().optional().describe(
|
|
2047
2146
|
"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."
|
|
2048
2147
|
),
|
|
2049
|
-
include_module_contexts:
|
|
2050
|
-
semantic:
|
|
2148
|
+
include_module_contexts: z20.boolean().default(true),
|
|
2149
|
+
semantic: z20.boolean().default(true).describe(
|
|
2051
2150
|
"Use semantic ranking when a task is provided (requires `hivelore embeddings index`)."
|
|
2052
2151
|
),
|
|
2053
|
-
include_stale:
|
|
2054
|
-
track:
|
|
2055
|
-
format:
|
|
2152
|
+
include_stale: z20.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
|
|
2153
|
+
track: z20.boolean().default(true).describe("Increment read_count on returned memories"),
|
|
2154
|
+
format: z20.enum(["full", "compact", "actions"]).default("full").describe(
|
|
2056
2155
|
"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."
|
|
2057
2156
|
),
|
|
2058
|
-
symbols:
|
|
2157
|
+
symbols: z20.array(z20.string()).default([]).describe(
|
|
2059
2158
|
"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."
|
|
2060
2159
|
),
|
|
2061
|
-
min_semantic_score:
|
|
2160
|
+
min_semantic_score: z20.number().min(0).max(1).default(0).describe(
|
|
2062
2161
|
"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."
|
|
2063
2162
|
),
|
|
2064
|
-
budget_preset:
|
|
2163
|
+
budget_preset: z20.enum(["quick", "balanced", "deep"]).optional().describe(
|
|
2065
2164
|
"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."
|
|
2066
2165
|
)
|
|
2067
2166
|
};
|
|
2068
|
-
var GetBriefingZod =
|
|
2167
|
+
var GetBriefingZod = z20.object(GetBriefingInputSchema);
|
|
2069
2168
|
async function getBriefing(input, ctx) {
|
|
2070
2169
|
const resolvedBudget = resolveBriefingBudget(input.budget_preset, {
|
|
2071
2170
|
max_tokens: input.max_tokens,
|
|
@@ -2081,8 +2180,8 @@ async function getBriefing(input, ctx) {
|
|
|
2081
2180
|
let usage = { version: 1, updated_at: "", by_id: {} };
|
|
2082
2181
|
let byId = /* @__PURE__ */ new Map();
|
|
2083
2182
|
let lastSession;
|
|
2084
|
-
if (
|
|
2085
|
-
const allLoaded = await
|
|
2183
|
+
if (existsSync22(ctx.paths.memoriesDir)) {
|
|
2184
|
+
const allLoaded = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
|
|
2086
2185
|
const recaps = allLoaded.filter(({ memory }) => memory.frontmatter.type === "session_recap").sort(
|
|
2087
2186
|
(a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
|
|
2088
2187
|
);
|
|
@@ -2256,7 +2355,7 @@ async function getBriefing(input, ctx) {
|
|
|
2256
2355
|
if (!isAutoPromoteEligible(loaded.memory.frontmatter, u, rule)) continue;
|
|
2257
2356
|
const newFm = { ...loaded.memory.frontmatter, status: "validated", validated_by: "auto" };
|
|
2258
2357
|
try {
|
|
2259
|
-
await
|
|
2358
|
+
await writeFile14(loaded.filePath, serializeMemory11({ frontmatter: newFm, body: loaded.memory.body }), "utf8");
|
|
2260
2359
|
m.status = "validated";
|
|
2261
2360
|
m.confidence = "trusted";
|
|
2262
2361
|
} catch {
|
|
@@ -2264,7 +2363,7 @@ async function getBriefing(input, ctx) {
|
|
|
2264
2363
|
}
|
|
2265
2364
|
}
|
|
2266
2365
|
}
|
|
2267
|
-
let projectContextRaw = input.include_project_context &&
|
|
2366
|
+
let projectContextRaw = input.include_project_context && existsSync22(ctx.paths.projectContext) ? await readFile7(ctx.paths.projectContext, "utf8") : "";
|
|
2268
2367
|
let contextOmittedRecent = false;
|
|
2269
2368
|
if (projectContextRaw && input.dedupe_project_context !== false) {
|
|
2270
2369
|
const ctxHash = hashProjectContext(projectContextRaw);
|
|
@@ -2279,7 +2378,7 @@ async function getBriefing(input, ctx) {
|
|
|
2279
2378
|
const setupWarnings = [];
|
|
2280
2379
|
let autoContextGenerated = false;
|
|
2281
2380
|
let projectContext = isTemplateContext ? "" : projectContextRaw;
|
|
2282
|
-
if ((isTemplateContext || !
|
|
2381
|
+
if ((isTemplateContext || !existsSync22(ctx.paths.projectContext)) && input.include_project_context) {
|
|
2283
2382
|
const haiveConfig = await loadConfig3(ctx.paths);
|
|
2284
2383
|
if (haiveConfig.autoContext) {
|
|
2285
2384
|
const codeMap = await loadCodeMap(ctx.paths);
|
|
@@ -2453,8 +2552,8 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
2453
2552
|
actionRequired.push(extractActionItem(m.id, loaded.memory.body));
|
|
2454
2553
|
}
|
|
2455
2554
|
}
|
|
2456
|
-
if (
|
|
2457
|
-
const allMems = await
|
|
2555
|
+
if (existsSync22(ctx.paths.memoriesDir)) {
|
|
2556
|
+
const allMems = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
|
|
2458
2557
|
for (const { memory } of allMems) {
|
|
2459
2558
|
const fm = memory.frontmatter;
|
|
2460
2559
|
if (!fm.requires_human_approval) continue;
|
|
@@ -2464,9 +2563,9 @@ ${m.content}`).join("\n\n---\n\n"),
|
|
|
2464
2563
|
}
|
|
2465
2564
|
}
|
|
2466
2565
|
const pendingDistillFile = pendingDistillPath(ctx);
|
|
2467
|
-
if (
|
|
2566
|
+
if (existsSync22(pendingDistillFile)) {
|
|
2468
2567
|
try {
|
|
2469
|
-
const raw = await
|
|
2568
|
+
const raw = await readFile7(pendingDistillFile, "utf8");
|
|
2470
2569
|
const pd = JSON.parse(raw);
|
|
2471
2570
|
const ageMs = Date.now() - new Date(pd.session_end).getTime();
|
|
2472
2571
|
const SEVEN_DAYS = 7 * 24 * 60 * 60 * 1e3;
|
|
@@ -2493,7 +2592,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
2493
2592
|
}
|
|
2494
2593
|
}
|
|
2495
2594
|
const memoriesEmpty = outputMemories.length === 0;
|
|
2496
|
-
const hasMemoriesDir =
|
|
2595
|
+
const hasMemoriesDir = existsSync22(ctx.paths.memoriesDir);
|
|
2497
2596
|
const isColdStart = isTemplateContext && memoriesEmpty && !lastSession && !autoContextGenerated;
|
|
2498
2597
|
const hasUnguessableSignal = outputMemories.some(
|
|
2499
2598
|
(m) => (m.priority === "must_read" || m.priority === "useful") && specificityScore(m.body) >= GUESSABLE_THRESHOLD
|
|
@@ -2508,10 +2607,10 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
|
|
|
2508
2607
|
try {
|
|
2509
2608
|
let pcRaw = "";
|
|
2510
2609
|
try {
|
|
2511
|
-
pcRaw = await
|
|
2610
|
+
pcRaw = await readFile7(ctx.paths.projectContext, "utf8");
|
|
2512
2611
|
} catch {
|
|
2513
2612
|
}
|
|
2514
|
-
const allForBootstrap =
|
|
2613
|
+
const allForBootstrap = existsSync22(ctx.paths.memoriesDir) ? await loadMemoriesFromDir17(ctx.paths.memoriesDir) : [];
|
|
2515
2614
|
const cmForBootstrap = await loadCodeMap(ctx.paths);
|
|
2516
2615
|
let existingModules = [];
|
|
2517
2616
|
try {
|
|
@@ -2575,7 +2674,7 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
|
|
|
2575
2674
|
"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."
|
|
2576
2675
|
);
|
|
2577
2676
|
}
|
|
2578
|
-
if (outputMemories.length > 0 &&
|
|
2677
|
+
if (outputMemories.length > 0 && existsSync22(ctx.paths.haiveDir)) {
|
|
2579
2678
|
const proof = briefingProofLine(await loadPreventionEvents(ctx.paths));
|
|
2580
2679
|
if (proof) hints.push(proof);
|
|
2581
2680
|
}
|
|
@@ -2589,7 +2688,7 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
|
|
|
2589
2688
|
adaptiveTrim
|
|
2590
2689
|
});
|
|
2591
2690
|
const breadcrumbTokens = breadcrumbs ? estimateTokens([...breadcrumbs.start_here, ...breadcrumbs.drill_down, breadcrumbs.note ?? ""].join("\n")) : 0;
|
|
2592
|
-
if (
|
|
2691
|
+
if (existsSync22(ctx.paths.haiveDir)) {
|
|
2593
2692
|
await writeBriefingMarker(ctx.paths, {
|
|
2594
2693
|
sessionId: process.env.HAIVE_SESSION_ID,
|
|
2595
2694
|
...input.task ? { task: input.task } : {},
|
|
@@ -2645,10 +2744,10 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
|
|
|
2645
2744
|
};
|
|
2646
2745
|
}
|
|
2647
2746
|
async function detectRunCommands(root) {
|
|
2648
|
-
const pkgPath =
|
|
2649
|
-
if (!
|
|
2747
|
+
const pkgPath = path12.join(root, "package.json");
|
|
2748
|
+
if (!existsSync22(pkgPath)) return null;
|
|
2650
2749
|
try {
|
|
2651
|
-
const pkg = JSON.parse(await
|
|
2750
|
+
const pkg = JSON.parse(await readFile7(pkgPath, "utf8"));
|
|
2652
2751
|
const scripts = pkg.scripts ?? {};
|
|
2653
2752
|
const order = ["test", "build", "lint", "typecheck", "type-check", "dev", "start"];
|
|
2654
2753
|
const lines = order.filter((name) => typeof scripts[name] === "string" && scripts[name].trim() !== "").map((name) => `- \`${name}\`: \`${scripts[name]}\``);
|
|
@@ -2713,24 +2812,24 @@ function oneLine(value) {
|
|
|
2713
2812
|
return value.replace(/\s+/g, " ").replace(/"/g, '\\"').trim().slice(0, 120);
|
|
2714
2813
|
}
|
|
2715
2814
|
function serverVersion() {
|
|
2716
|
-
return true ? "0.
|
|
2815
|
+
return true ? "0.38.0" : "dev";
|
|
2717
2816
|
}
|
|
2718
2817
|
|
|
2719
2818
|
// src/tools/code-map.ts
|
|
2720
2819
|
import { estimateTokens as estimateTokens2, loadCodeMap as loadCodeMap2, queryCodeMap as queryCodeMap2 } from "@hivelore/core";
|
|
2721
|
-
import { z as
|
|
2820
|
+
import { z as z21 } from "zod";
|
|
2722
2821
|
var CodeMapInputSchema = {
|
|
2723
|
-
file:
|
|
2724
|
-
symbol:
|
|
2725
|
-
paths:
|
|
2822
|
+
file: z21.string().optional().describe("Filter to files whose path contains this substring"),
|
|
2823
|
+
symbol: z21.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
|
|
2824
|
+
paths: z21.array(z21.string()).default([]).describe(
|
|
2726
2825
|
"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."
|
|
2727
2826
|
),
|
|
2728
|
-
max_files:
|
|
2729
|
-
max_tokens:
|
|
2827
|
+
max_files: z21.number().int().positive().default(40).describe("Cap on returned files (hard limit, applied after token budget)"),
|
|
2828
|
+
max_tokens: z21.number().int().positive().optional().describe(
|
|
2730
2829
|
"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)."
|
|
2731
2830
|
)
|
|
2732
2831
|
};
|
|
2733
|
-
var CodeMapInputZod =
|
|
2832
|
+
var CodeMapInputZod = z21.object(CodeMapInputSchema);
|
|
2734
2833
|
async function codeMapTool(input, ctx) {
|
|
2735
2834
|
const map = await loadCodeMap2(ctx.paths);
|
|
2736
2835
|
if (!map) {
|
|
@@ -2799,18 +2898,18 @@ function estimateFileEntryTokens(f) {
|
|
|
2799
2898
|
}
|
|
2800
2899
|
|
|
2801
2900
|
// src/tools/mem-diff.ts
|
|
2802
|
-
import { existsSync as
|
|
2803
|
-
import { loadMemoriesFromDir as
|
|
2804
|
-
import { z as
|
|
2901
|
+
import { existsSync as existsSync23 } from "fs";
|
|
2902
|
+
import { loadMemoriesFromDir as loadMemoriesFromDir18 } from "@hivelore/core";
|
|
2903
|
+
import { z as z22 } from "zod";
|
|
2805
2904
|
var MemDiffInputSchema = {
|
|
2806
|
-
id_a:
|
|
2807
|
-
id_b:
|
|
2905
|
+
id_a: z22.string().min(1).describe("First memory id"),
|
|
2906
|
+
id_b: z22.string().min(1).describe("Second memory id")
|
|
2808
2907
|
};
|
|
2809
2908
|
async function memDiff(input, ctx) {
|
|
2810
|
-
if (!
|
|
2909
|
+
if (!existsSync23(ctx.paths.memoriesDir)) {
|
|
2811
2910
|
throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
|
|
2812
2911
|
}
|
|
2813
|
-
const all = await
|
|
2912
|
+
const all = await loadMemoriesFromDir18(ctx.paths.memoriesDir);
|
|
2814
2913
|
const foundA = all.find((m) => m.memory.frontmatter.id === input.id_a);
|
|
2815
2914
|
const foundB = all.find((m) => m.memory.frontmatter.id === input.id_b);
|
|
2816
2915
|
if (!foundA) throw new Error(`No memory with id "${input.id_a}".`);
|
|
@@ -2844,19 +2943,19 @@ async function memDiff(input, ctx) {
|
|
|
2844
2943
|
}
|
|
2845
2944
|
|
|
2846
2945
|
// src/tools/get-recap.ts
|
|
2847
|
-
import { existsSync as
|
|
2848
|
-
import { loadMemoriesFromDir as
|
|
2849
|
-
import { z as
|
|
2946
|
+
import { existsSync as existsSync24 } from "fs";
|
|
2947
|
+
import { loadMemoriesFromDir as loadMemoriesFromDir19 } from "@hivelore/core";
|
|
2948
|
+
import { z as z23 } from "zod";
|
|
2850
2949
|
var GetRecapInputSchema = {
|
|
2851
|
-
scope:
|
|
2950
|
+
scope: z23.enum(["personal", "team", "any"]).default("any").describe(
|
|
2852
2951
|
"Limit to a specific scope's recap. Default 'any' returns the most recent recap across both personal and team scopes."
|
|
2853
2952
|
)
|
|
2854
2953
|
};
|
|
2855
2954
|
async function getRecap(input, ctx) {
|
|
2856
|
-
if (!
|
|
2955
|
+
if (!existsSync24(ctx.paths.memoriesDir)) {
|
|
2857
2956
|
return { recap: null, notice: "No .ai/memories directory \u2014 haive not initialized here." };
|
|
2858
2957
|
}
|
|
2859
|
-
const all = await
|
|
2958
|
+
const all = await loadMemoriesFromDir19(ctx.paths.memoriesDir);
|
|
2860
2959
|
const recaps = all.filter(({ memory }) => memory.frontmatter.type === "session_recap").filter(({ memory }) => input.scope === "any" || memory.frontmatter.scope === input.scope).sort(
|
|
2861
2960
|
(a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
|
|
2862
2961
|
);
|
|
@@ -2880,13 +2979,13 @@ async function getRecap(input, ctx) {
|
|
|
2880
2979
|
}
|
|
2881
2980
|
|
|
2882
2981
|
// src/tools/mem-relevant-to.ts
|
|
2883
|
-
import { z as
|
|
2982
|
+
import { z as z24 } from "zod";
|
|
2884
2983
|
var MemRelevantToInputSchema = {
|
|
2885
|
-
task:
|
|
2886
|
-
files:
|
|
2887
|
-
limit:
|
|
2888
|
-
min_semantic_score:
|
|
2889
|
-
format:
|
|
2984
|
+
task: z24.string().min(1).describe("What you are about to do, in 1\u20132 sentences. Used to rank relevant memories."),
|
|
2985
|
+
files: z24.array(z24.string()).default([]).describe("Optional: files you are about to edit \u2014 surfaces anchored memories."),
|
|
2986
|
+
limit: z24.number().int().positive().max(30).default(8).describe("Cap on returned memories."),
|
|
2987
|
+
min_semantic_score: z24.number().min(0).max(1).default(0.25).describe("Drop weakly-related semantic hits below this cosine threshold."),
|
|
2988
|
+
format: z24.enum(["full", "compact", "actions"]).default("full").describe("'compact' = id + 1-line summary; 'full' = complete bodies; 'actions' = bullet-first excerpts.")
|
|
2890
2989
|
};
|
|
2891
2990
|
async function memRelevantTo(input, ctx) {
|
|
2892
2991
|
const briefingInput = {
|
|
@@ -2916,14 +3015,14 @@ async function memRelevantTo(input, ctx) {
|
|
|
2916
3015
|
}
|
|
2917
3016
|
|
|
2918
3017
|
// src/tools/code-search.ts
|
|
2919
|
-
import { z as
|
|
3018
|
+
import { z as z25 } from "zod";
|
|
2920
3019
|
import { loadCodeMap as loadCodeMap3 } from "@hivelore/core";
|
|
2921
3020
|
var CodeSearchInputSchema = {
|
|
2922
|
-
query:
|
|
3021
|
+
query: z25.string().min(1).describe(
|
|
2923
3022
|
"Natural-language description of what you are looking for in the codebase (e.g. 'function that hashes passwords', 'JWT signing logic', 'route registration')."
|
|
2924
3023
|
),
|
|
2925
|
-
k:
|
|
2926
|
-
min_score:
|
|
3024
|
+
k: z25.number().int().positive().max(50).default(5).describe("Number of top hits to return."),
|
|
3025
|
+
min_score: z25.number().min(0).max(1).default(0.2).describe(
|
|
2927
3026
|
"Minimum cosine similarity. Hits below this threshold are dropped to avoid noise. Try 0.3+ for stricter matching."
|
|
2928
3027
|
)
|
|
2929
3028
|
};
|
|
@@ -2966,7 +3065,7 @@ async function codeSearch(input, ctx) {
|
|
|
2966
3065
|
}
|
|
2967
3066
|
|
|
2968
3067
|
// src/tools/anti-patterns-check.ts
|
|
2969
|
-
import { existsSync as
|
|
3068
|
+
import { existsSync as existsSync25 } from "fs";
|
|
2970
3069
|
import {
|
|
2971
3070
|
addedLinesFromDiff,
|
|
2972
3071
|
BRIDGE_TARGET_PATH,
|
|
@@ -2976,7 +3075,7 @@ import {
|
|
|
2976
3075
|
diffHasDistinctiveOverlap,
|
|
2977
3076
|
getUsage as getUsage7,
|
|
2978
3077
|
isRetiredMemory as isRetiredMemory2,
|
|
2979
|
-
loadMemoriesFromDir as
|
|
3078
|
+
loadMemoriesFromDir as loadMemoriesFromDir20,
|
|
2980
3079
|
loadUsageIndex as loadUsageIndex9,
|
|
2981
3080
|
literalMatchesAnyToken as literalMatchesAnyToken3,
|
|
2982
3081
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths3,
|
|
@@ -2985,19 +3084,19 @@ import {
|
|
|
2985
3084
|
sensorTargetsFromDiff,
|
|
2986
3085
|
tokenizeQuery as tokenizeQuery3
|
|
2987
3086
|
} from "@hivelore/core";
|
|
2988
|
-
import { z as
|
|
3087
|
+
import { z as z26 } from "zod";
|
|
2989
3088
|
var AntiPatternsCheckInputSchema = {
|
|
2990
|
-
diff:
|
|
3089
|
+
diff: z26.string().optional().describe(
|
|
2991
3090
|
"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."
|
|
2992
3091
|
),
|
|
2993
|
-
paths:
|
|
3092
|
+
paths: z26.array(z26.string()).default([]).describe(
|
|
2994
3093
|
"File paths affected by the change. Memories anchored to any of these paths are surfaced regardless of the diff content."
|
|
2995
3094
|
),
|
|
2996
|
-
limit:
|
|
2997
|
-
semantic:
|
|
3095
|
+
limit: z26.number().int().positive().max(20).default(8).describe("Cap on returned warnings."),
|
|
3096
|
+
semantic: z26.boolean().default(true).describe(
|
|
2998
3097
|
"When true, also use semantic search (requires @hivelore/embeddings + memory index) to find related anti-patterns."
|
|
2999
3098
|
),
|
|
3000
|
-
min_semantic_score:
|
|
3099
|
+
min_semantic_score: z26.number().min(0).max(1).default(0.45).describe(
|
|
3001
3100
|
"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."
|
|
3002
3101
|
)
|
|
3003
3102
|
};
|
|
@@ -3081,10 +3180,10 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
3081
3180
|
notice: "Nothing to check \u2014 provide either `diff` text or `paths`."
|
|
3082
3181
|
};
|
|
3083
3182
|
}
|
|
3084
|
-
if (!
|
|
3183
|
+
if (!existsSync25(ctx.paths.memoriesDir)) {
|
|
3085
3184
|
return { scanned: 0, warnings: [], notice: "No .ai/memories directory \u2014 nothing to check against." };
|
|
3086
3185
|
}
|
|
3087
|
-
const all = await
|
|
3186
|
+
const all = await loadMemoriesFromDir20(ctx.paths.memoriesDir);
|
|
3088
3187
|
const minSemanticScore = input.min_semantic_score ?? 0.45;
|
|
3089
3188
|
const negative = all.filter(({ memory }) => {
|
|
3090
3189
|
const t = memory.frontmatter.type;
|
|
@@ -3200,19 +3299,19 @@ async function antiPatternsCheck(input, ctx) {
|
|
|
3200
3299
|
}
|
|
3201
3300
|
|
|
3202
3301
|
// src/tools/mem-distill.ts
|
|
3203
|
-
import { existsSync as
|
|
3302
|
+
import { existsSync as existsSync26 } from "fs";
|
|
3204
3303
|
import {
|
|
3205
|
-
loadMemoriesFromDir as
|
|
3304
|
+
loadMemoriesFromDir as loadMemoriesFromDir21,
|
|
3206
3305
|
tokenizeQuery as tokenizeQuery4
|
|
3207
3306
|
} from "@hivelore/core";
|
|
3208
|
-
import { z as
|
|
3307
|
+
import { z as z27 } from "zod";
|
|
3209
3308
|
var MemDistillInputSchema = {
|
|
3210
|
-
since_days:
|
|
3211
|
-
min_cluster:
|
|
3212
|
-
type_filter:
|
|
3309
|
+
since_days: z27.number().int().positive().default(30).describe("Only consider memories created in the last N days."),
|
|
3310
|
+
min_cluster: z27.number().int().min(2).default(3).describe("Minimum cluster size to surface."),
|
|
3311
|
+
type_filter: z27.enum(["gotcha", "attempt", "all"]).default("gotcha").describe(
|
|
3213
3312
|
"Memory type to scan. 'gotcha' targets observe-style discoveries that recur, 'attempt' surfaces failed approaches that repeat, 'all' considers both."
|
|
3214
3313
|
),
|
|
3215
|
-
scope:
|
|
3314
|
+
scope: z27.enum(["personal", "team", "module", "any"]).default("any").describe("Restrict to a specific scope.")
|
|
3216
3315
|
};
|
|
3217
3316
|
var MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
3218
3317
|
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
@@ -3252,11 +3351,11 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
|
3252
3351
|
"error"
|
|
3253
3352
|
]);
|
|
3254
3353
|
async function memDistill(input, ctx) {
|
|
3255
|
-
if (!
|
|
3354
|
+
if (!existsSync26(ctx.paths.memoriesDir)) {
|
|
3256
3355
|
return { scanned: 0, singletons: 0, clusters: [], notice: "No .ai/memories directory." };
|
|
3257
3356
|
}
|
|
3258
3357
|
const cutoff = Date.now() - input.since_days * MS_PER_DAY;
|
|
3259
|
-
const all = await
|
|
3358
|
+
const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
|
|
3260
3359
|
const candidates = all.filter(({ memory }) => {
|
|
3261
3360
|
const fm = memory.frontmatter;
|
|
3262
3361
|
if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
|
|
@@ -3361,17 +3460,17 @@ function firstHeading(body) {
|
|
|
3361
3460
|
|
|
3362
3461
|
// src/tools/precommit-check.ts
|
|
3363
3462
|
import { pathsOverlap as pathsOverlap2 } from "@hivelore/core";
|
|
3364
|
-
import { z as
|
|
3463
|
+
import { z as z28 } from "zod";
|
|
3365
3464
|
var PreCommitCheckInputSchema = {
|
|
3366
|
-
diff:
|
|
3465
|
+
diff: z28.string().optional().describe(
|
|
3367
3466
|
"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`."
|
|
3368
3467
|
),
|
|
3369
|
-
paths:
|
|
3370
|
-
block_on:
|
|
3468
|
+
paths: z28.array(z28.string()).default([]).describe("Project-relative paths affected by the change. At least one of `diff` or `paths` should be provided."),
|
|
3469
|
+
block_on: z28.enum(["any", "high-confidence", "never"]).default("high-confidence").describe(
|
|
3371
3470
|
"When to set should_block=true: 'any' = any warning blocks; 'high-confidence' = only warnings from authoritative/trusted memories block; 'never' = report only, never block."
|
|
3372
3471
|
),
|
|
3373
|
-
semantic:
|
|
3374
|
-
anchored_blocks:
|
|
3472
|
+
semantic: z28.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index)."),
|
|
3473
|
+
anchored_blocks: z28.boolean().default(false).describe(
|
|
3375
3474
|
"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."
|
|
3376
3475
|
)
|
|
3377
3476
|
};
|
|
@@ -3677,14 +3776,14 @@ function repairTargetPathForWarning(warning, paths) {
|
|
|
3677
3776
|
}
|
|
3678
3777
|
|
|
3679
3778
|
// src/tools/mem-conflict-candidates.ts
|
|
3680
|
-
import { existsSync as
|
|
3779
|
+
import { existsSync as existsSync27 } from "fs";
|
|
3681
3780
|
import {
|
|
3682
3781
|
findLexicalConflictPairs,
|
|
3683
3782
|
findTopicStatusConflictPairs,
|
|
3684
|
-
loadMemoriesFromDir as
|
|
3783
|
+
loadMemoriesFromDir as loadMemoriesFromDir22,
|
|
3685
3784
|
planConflictResolution
|
|
3686
3785
|
} from "@hivelore/core";
|
|
3687
|
-
import { z as
|
|
3786
|
+
import { z as z29 } from "zod";
|
|
3688
3787
|
function suggestResolution(byId, idA, idB) {
|
|
3689
3788
|
const a = byId.get(idA);
|
|
3690
3789
|
const b = byId.get(idB);
|
|
@@ -3698,17 +3797,17 @@ function suggestResolution(byId, idA, idB) {
|
|
|
3698
3797
|
};
|
|
3699
3798
|
}
|
|
3700
3799
|
var MemConflictCandidatesInputSchema = {
|
|
3701
|
-
since_days:
|
|
3702
|
-
types:
|
|
3703
|
-
min_jaccard:
|
|
3704
|
-
max_pairs:
|
|
3705
|
-
max_scan:
|
|
3706
|
-
max_topic_pairs:
|
|
3800
|
+
since_days: z29.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
|
|
3801
|
+
types: z29.array(z29.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
|
|
3802
|
+
min_jaccard: z29.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
|
|
3803
|
+
max_pairs: z29.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
|
|
3804
|
+
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."),
|
|
3805
|
+
max_topic_pairs: z29.number().int().positive().max(100).default(20).describe(
|
|
3707
3806
|
"Cap for extra signal: memories sharing the same topic with validated vs rejected status."
|
|
3708
3807
|
)
|
|
3709
3808
|
};
|
|
3710
3809
|
async function memConflictCandidates(input, ctx) {
|
|
3711
|
-
if (!
|
|
3810
|
+
if (!existsSync27(ctx.paths.memoriesDir)) {
|
|
3712
3811
|
return {
|
|
3713
3812
|
pairs: [],
|
|
3714
3813
|
topic_status_pairs: [],
|
|
@@ -3717,7 +3816,7 @@ async function memConflictCandidates(input, ctx) {
|
|
|
3717
3816
|
notice: "No .ai/memories directory."
|
|
3718
3817
|
};
|
|
3719
3818
|
}
|
|
3720
|
-
const all = await
|
|
3819
|
+
const all = await loadMemoriesFromDir22(ctx.paths.memoriesDir);
|
|
3721
3820
|
const byId = new Map(all.map((m) => [m.memory.frontmatter.id, m]));
|
|
3722
3821
|
const { pairs, scanned, truncated } = findLexicalConflictPairs(all, {
|
|
3723
3822
|
sinceDays: input.since_days,
|
|
@@ -3747,9 +3846,9 @@ async function memConflictCandidates(input, ctx) {
|
|
|
3747
3846
|
|
|
3748
3847
|
// src/tools/mem-resolve-project.ts
|
|
3749
3848
|
import { resolveProjectInfo } from "@hivelore/core";
|
|
3750
|
-
import { z as
|
|
3849
|
+
import { z as z30 } from "zod";
|
|
3751
3850
|
var MemResolveProjectInputSchema = {
|
|
3752
|
-
cwd:
|
|
3851
|
+
cwd: z30.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
|
|
3753
3852
|
};
|
|
3754
3853
|
async function memResolveProject(input, _ctx) {
|
|
3755
3854
|
void _ctx;
|
|
@@ -3763,10 +3862,10 @@ async function memResolveProject(input, _ctx) {
|
|
|
3763
3862
|
|
|
3764
3863
|
// src/tools/mem-suggest-topic.ts
|
|
3765
3864
|
import { MemoryTypeSchema, suggestTopicKey } from "@hivelore/core";
|
|
3766
|
-
import { z as
|
|
3865
|
+
import { z as z31 } from "zod";
|
|
3767
3866
|
var MemSuggestTopicInputSchema = {
|
|
3768
3867
|
type: MemoryTypeSchema.describe("Memory kind \u2014 drives the suggested topic family."),
|
|
3769
|
-
title:
|
|
3868
|
+
title: z31.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
|
|
3770
3869
|
};
|
|
3771
3870
|
async function memSuggestTopic(input, _ctx) {
|
|
3772
3871
|
void _ctx;
|
|
@@ -3775,19 +3874,19 @@ async function memSuggestTopic(input, _ctx) {
|
|
|
3775
3874
|
}
|
|
3776
3875
|
|
|
3777
3876
|
// src/tools/mem-timeline.ts
|
|
3778
|
-
import { existsSync as
|
|
3779
|
-
import { collectTimelineEntries, loadMemoriesFromDir as
|
|
3780
|
-
import { z as
|
|
3877
|
+
import { existsSync as existsSync28 } from "fs";
|
|
3878
|
+
import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir23 } from "@hivelore/core";
|
|
3879
|
+
import { z as z32 } from "zod";
|
|
3781
3880
|
var MemTimelineInputSchema = {
|
|
3782
|
-
memory_id:
|
|
3783
|
-
topic:
|
|
3784
|
-
limit:
|
|
3881
|
+
memory_id: z32.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
|
|
3882
|
+
topic: z32.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
|
|
3883
|
+
limit: z32.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
|
|
3785
3884
|
};
|
|
3786
3885
|
async function memTimeline(input, ctx) {
|
|
3787
|
-
if (!
|
|
3886
|
+
if (!existsSync28(ctx.paths.memoriesDir)) {
|
|
3788
3887
|
return { entries: [], total: 0, notice: "No .ai/memories directory." };
|
|
3789
3888
|
}
|
|
3790
|
-
const all = await
|
|
3889
|
+
const all = await loadMemoriesFromDir23(ctx.paths.memoriesDir);
|
|
3791
3890
|
const { entries, notice } = collectTimelineEntries(all, {
|
|
3792
3891
|
memoryId: input.memory_id,
|
|
3793
3892
|
topic: input.topic,
|
|
@@ -3797,12 +3896,12 @@ async function memTimeline(input, ctx) {
|
|
|
3797
3896
|
}
|
|
3798
3897
|
|
|
3799
3898
|
// src/prompts/bootstrap-project.ts
|
|
3800
|
-
import { z as
|
|
3899
|
+
import { z as z33 } from "zod";
|
|
3801
3900
|
var BootstrapProjectArgsSchema = {
|
|
3802
|
-
module:
|
|
3901
|
+
module: z33.string().optional().describe(
|
|
3803
3902
|
"Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
|
|
3804
3903
|
),
|
|
3805
|
-
focus:
|
|
3904
|
+
focus: z33.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
|
|
3806
3905
|
};
|
|
3807
3906
|
var ROOT_TEMPLATE = `# Project context
|
|
3808
3907
|
|
|
@@ -3884,25 +3983,25 @@ ${template}\`\`\`
|
|
|
3884
3983
|
}
|
|
3885
3984
|
|
|
3886
3985
|
// src/prompts/bootstrap-repo.ts
|
|
3887
|
-
import { readFile as
|
|
3888
|
-
import { existsSync as
|
|
3986
|
+
import { readFile as readFile8, readdir as readdir5 } from "fs/promises";
|
|
3987
|
+
import { existsSync as existsSync29 } from "fs";
|
|
3889
3988
|
import {
|
|
3890
3989
|
assessBootstrapState as assessBootstrapState2,
|
|
3891
3990
|
loadCodeMap as loadCodeMap4,
|
|
3892
|
-
loadMemoriesFromDir as
|
|
3991
|
+
loadMemoriesFromDir as loadMemoriesFromDir24,
|
|
3893
3992
|
renderBootstrapChecklist as renderBootstrapChecklist2
|
|
3894
3993
|
} from "@hivelore/core";
|
|
3895
|
-
import { z as
|
|
3994
|
+
import { z as z34 } from "zod";
|
|
3896
3995
|
var BootstrapRepoArgsSchema = {
|
|
3897
|
-
focus:
|
|
3996
|
+
focus: z34.string().optional().describe("Optional area to emphasize first (e.g. 'payments', 'auth').")
|
|
3898
3997
|
};
|
|
3899
3998
|
async function currentAssessment(ctx) {
|
|
3900
3999
|
let projectContextRaw = "";
|
|
3901
4000
|
try {
|
|
3902
|
-
projectContextRaw = await
|
|
4001
|
+
projectContextRaw = await readFile8(ctx.paths.projectContext, "utf8");
|
|
3903
4002
|
} catch {
|
|
3904
4003
|
}
|
|
3905
|
-
const memories =
|
|
4004
|
+
const memories = existsSync29(ctx.paths.memoriesDir) ? await loadMemoriesFromDir24(ctx.paths.memoriesDir) : [];
|
|
3906
4005
|
const codeMap = await loadCodeMap4(ctx.paths);
|
|
3907
4006
|
let existingModules = [];
|
|
3908
4007
|
try {
|
|
@@ -3970,10 +4069,10 @@ Main code areas detected: ${areas}
|
|
|
3970
4069
|
}
|
|
3971
4070
|
|
|
3972
4071
|
// src/prompts/post-task.ts
|
|
3973
|
-
import { z as
|
|
4072
|
+
import { z as z35 } from "zod";
|
|
3974
4073
|
var PostTaskArgsSchema = {
|
|
3975
|
-
task_summary:
|
|
3976
|
-
files_touched:
|
|
4074
|
+
task_summary: z35.string().optional().describe("One sentence describing what you just did"),
|
|
4075
|
+
files_touched: z35.array(z35.string()).optional().describe("Files you created or modified during the task")
|
|
3977
4076
|
};
|
|
3978
4077
|
function postTaskPrompt(args, ctx) {
|
|
3979
4078
|
const taskLine = args.task_summary ? `
|
|
@@ -4070,12 +4169,12 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
|
|
|
4070
4169
|
}
|
|
4071
4170
|
|
|
4072
4171
|
// src/prompts/import-docs.ts
|
|
4073
|
-
import { z as
|
|
4172
|
+
import { z as z36 } from "zod";
|
|
4074
4173
|
var ImportDocsArgsSchema = {
|
|
4075
|
-
content:
|
|
4076
|
-
source:
|
|
4077
|
-
scope:
|
|
4078
|
-
dry_run:
|
|
4174
|
+
content: z36.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
|
|
4175
|
+
source: z36.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
|
|
4176
|
+
scope: z36.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
|
|
4177
|
+
dry_run: z36.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
|
|
4079
4178
|
};
|
|
4080
4179
|
function importDocsPrompt(args, ctx) {
|
|
4081
4180
|
const sourceLine = args.source ? `
|
|
@@ -4141,7 +4240,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
|
|
|
4141
4240
|
// src/server.ts
|
|
4142
4241
|
import { hasRecentBriefingMarker, loadConfigSync } from "@hivelore/core";
|
|
4143
4242
|
var SERVER_NAME = "hivelore";
|
|
4144
|
-
var SERVER_VERSION = "0.
|
|
4243
|
+
var SERVER_VERSION = "0.38.0";
|
|
4145
4244
|
function jsonResult(data) {
|
|
4146
4245
|
return {
|
|
4147
4246
|
content: [
|
|
@@ -4164,7 +4263,8 @@ var ENFORCEMENT_PROFILE_TOOLS = [
|
|
|
4164
4263
|
"code_search",
|
|
4165
4264
|
"pre_commit_check",
|
|
4166
4265
|
"mem_session_end",
|
|
4167
|
-
"propose_sensor"
|
|
4266
|
+
"propose_sensor",
|
|
4267
|
+
"scaffold_test"
|
|
4168
4268
|
];
|
|
4169
4269
|
var MAINTENANCE_PROFILE_TOOLS = [
|
|
4170
4270
|
...ENFORCEMENT_PROFILE_TOOLS,
|
|
@@ -4370,6 +4470,35 @@ function createHaiveServer(options = {}) {
|
|
|
4370
4470
|
return jsonResult(await proposeSensor(input, context));
|
|
4371
4471
|
}
|
|
4372
4472
|
);
|
|
4473
|
+
registerTool(
|
|
4474
|
+
"scaffold_test",
|
|
4475
|
+
[
|
|
4476
|
+
"Generate a PENDING post-incident test from a lesson (attempt/gotcha) \u2014 the on-ramp to a command",
|
|
4477
|
+
"sensor. A command sensor routes YOUR test as its oracle, but someone has to write it; this writes",
|
|
4478
|
+
"the skeleton so you only fill in the assertion.",
|
|
4479
|
+
"",
|
|
4480
|
+
"USE THIS right after mem_tried when the mistake is behavioural (a regex can't express it): it",
|
|
4481
|
+
"writes a stub carrying the incident's provenance and returns the exact `sensors propose --kind",
|
|
4482
|
+
"test` command to arm it.",
|
|
4483
|
+
"",
|
|
4484
|
+
"It DOES NOT arm a sensor \u2014 propose_sensor stays the sole validated writer, and the stub is left",
|
|
4485
|
+
"PENDING (todo/skip) so the suite stays green until you write the assertion. Monorepo-aware: the",
|
|
4486
|
+
"framework and location come from the package that owns the lesson's anchor paths.",
|
|
4487
|
+
"",
|
|
4488
|
+
"PARAMETERS:",
|
|
4489
|
+
" memory_id \u2014 the attempt/gotcha to scaffold from",
|
|
4490
|
+
" framework \u2014 vitest | jest | pytest | gotest (auto-detected when omitted)",
|
|
4491
|
+
" out_path \u2014 override the test file path (repo-relative)",
|
|
4492
|
+
" write \u2014 write the file (default true); false returns the content for preview",
|
|
4493
|
+
"",
|
|
4494
|
+
"RETURNS: { ok, path, run_command, propose_command, content, written, already_exists, notice }"
|
|
4495
|
+
].join("\n"),
|
|
4496
|
+
ScaffoldTestInputSchema,
|
|
4497
|
+
async (input) => {
|
|
4498
|
+
tracker.record("scaffold_test", input.memory_id);
|
|
4499
|
+
return jsonResult(await scaffoldTest(input, context));
|
|
4500
|
+
}
|
|
4501
|
+
);
|
|
4373
4502
|
registerTool(
|
|
4374
4503
|
"ingest_findings",
|
|
4375
4504
|
[
|