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