@hivelore/mcp 0.37.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/server.js CHANGED
@@ -1448,43 +1448,142 @@ async function memTried(input, ctx) {
1448
1448
  };
1449
1449
  }
1450
1450
 
1451
- // src/tools/ingest-findings.ts
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 loadMemoriesFromDir14,
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 z17 } from "zod";
1562
+ import { z as z18 } from "zod";
1464
1563
  var IngestFindingsInputSchema = {
1465
- format: z17.enum(["sarif", "sonar"]).describe("Report format: 'sarif' (ESLint/Semgrep/CodeQL) or 'sonar' (SonarQube issues JSON)"),
1466
- report_path: z17.string().optional().describe("Project-relative path to the findings JSON file. Provide this OR `report`."),
1467
- report: z17.string().optional().describe("Inline findings JSON content. Provide this OR `report_path`."),
1468
- type: z17.enum(["gotcha", "convention"]).default("gotcha").describe("Memory type for the created drafts"),
1469
- scope: z17.enum(["personal", "team", "module"]).default("team").describe("Visibility scope for the created memories"),
1470
- module: z17.string().optional().describe("Module name (required when scope=module)"),
1471
- min_severity: z17.enum(["info", "minor", "major", "critical", "blocker"]).optional().describe("Ignore findings below this severity"),
1472
- include_stylistic: z17.boolean().optional().describe("Also ingest auto-fixable stylistic rules (semi/quotes/prefer-const\u2026); off by default as low-value noise"),
1473
- limit: z17.number().int().positive().optional().describe("Cap the number of memories created"),
1474
- author: z17.string().optional().describe("Author handle or email"),
1475
- dry_run: z17.boolean().default(false).describe("When true, return the drafts that WOULD be created without writing them")
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 (!existsSync17(ctx.paths.haiveDir)) {
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 = path7.resolve(ctx.paths.root, input.report_path);
1486
- if (!existsSync17(file)) throw new Error(`Report file not found: ${file}`);
1487
- raw = await readFile4(file, "utf8");
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 = existsSync17(ctx.paths.memoriesDir) ? await loadMemoriesFromDir14(ctx.paths.memoriesDir) : [];
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 mkdir4(path7.dirname(file), { recursive: true });
1540
- await writeFile10(file, serializeMemory9({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
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 writeFile12, mkdir as mkdir6 } from "fs/promises";
1546
- import { existsSync as existsSync19 } from "fs";
1547
- import path9 from "path";
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 loadMemoriesFromDir15,
1649
+ loadMemoriesFromDir as loadMemoriesFromDir16,
1551
1650
  memoryFilePath as memoryFilePath4,
1552
1651
  serializeMemory as serializeMemory10
1553
1652
  } from "@hivelore/core";
1554
- import { z as z18 } from "zod";
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 mkdir5, writeFile as writeFile11, rm } from "fs/promises";
1564
- import { existsSync as existsSync18 } from "fs";
1565
- import path8 from "path";
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 path8.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
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 && existsSync18(this.ctx.paths.haiveDir)) {
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 = path8.join(this.ctx.paths.haiveDir, ".cache");
1686
- await mkdir5(cacheDir, { recursive: true });
1687
- await writeFile11(
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 (existsSync18(p)) {
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: z18.string().min(1).describe("What you were trying to accomplish this session (1\u20132 sentences)"),
1724
- accomplished: z18.string().describe("What was actually done \u2014 bullet list recommended"),
1725
- discoveries: z18.string().default("").describe(
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: z18.array(z18.string()).default([]).describe("Key files that were read or modified \u2014 used as anchor paths"),
1729
- next_steps: z18.string().default("").describe("What should happen next (for the next session or a teammate)"),
1730
- scope: z18.enum(["personal", "team", "module"]).default("personal").describe("Visibility: personal = private to you, team = shared with the team"),
1731
- module: z18.string().optional().describe("Module name (required when scope=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 (!existsSync19(ctx.paths.haiveDir)) {
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 || !path9.isAbsolute(p)) return p;
1768
- const rel = path9.relative(ctx.paths.root, p);
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) => !existsSync19(path9.resolve(ctx.paths.root, 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 = existsSync19(ctx.paths.memoriesDir) ? await loadMemoriesFromDir15(ctx.paths.memoriesDir) : [];
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 writeFile12(
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 mkdir6(path9.dirname(file), { recursive: true });
1824
- await writeFile12(file, serializeMemory10({ frontmatter, body }), "utf8");
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 readFile6, writeFile as writeFile13, readdir as readdir4 } from "fs/promises";
1837
- import { existsSync as existsSync21 } from "fs";
1838
- import path11 from "path";
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 loadMemoriesFromDir16,
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 z19 } from "zod";
1979
+ import { z as z20 } from "zod";
1881
1980
 
1882
1981
  // src/tools/briefing-helpers.ts
1883
- import { readdir as readdir3, readFile as readFile5 } from "fs/promises";
1884
- import { existsSync as existsSync20 } from "fs";
1885
- import path10 from "path";
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 (!existsSync20(ctx.paths.modulesContextDir)) return [];
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 = path10.join(ctx.paths.modulesContextDir, m, "context.md");
2026
- if (existsSync20(file)) {
2027
- out.push({ name: m, content: await readFile5(file, "utf8") });
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: z19.string().optional().describe(
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: z19.array(z19.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
2039
- max_tokens: z19.number().int().positive().default(8e3).describe(
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: z19.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
2043
- include_project_context: z19.boolean().default(true),
2044
- dedupe_project_context: z19.boolean().optional().describe(
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: z19.boolean().default(true),
2048
- semantic: z19.boolean().default(true).describe(
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: z19.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
2052
- track: z19.boolean().default(true).describe("Increment read_count on returned memories"),
2053
- format: z19.enum(["full", "compact", "actions"]).default("full").describe(
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: z19.array(z19.string()).default([]).describe(
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: z19.number().min(0).max(1).default(0).describe(
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: z19.enum(["quick", "balanced", "deep"]).optional().describe(
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 = z19.object(GetBriefingInputSchema);
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 (existsSync21(ctx.paths.memoriesDir)) {
2083
- const allLoaded = await loadMemoriesFromDir16(ctx.paths.memoriesDir);
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 writeFile13(loaded.filePath, serializeMemory11({ frontmatter: newFm, body: loaded.memory.body }), "utf8");
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 && existsSync21(ctx.paths.projectContext) ? await readFile6(ctx.paths.projectContext, "utf8") : "";
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 || !existsSync21(ctx.paths.projectContext)) && input.include_project_context) {
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 (existsSync21(ctx.paths.memoriesDir)) {
2455
- const allMems = await loadMemoriesFromDir16(ctx.paths.memoriesDir);
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 (existsSync21(pendingDistillFile)) {
2564
+ if (existsSync22(pendingDistillFile)) {
2466
2565
  try {
2467
- const raw = await readFile6(pendingDistillFile, "utf8");
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 = existsSync21(ctx.paths.memoriesDir);
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 readFile6(ctx.paths.projectContext, "utf8");
2608
+ pcRaw = await readFile7(ctx.paths.projectContext, "utf8");
2510
2609
  } catch {
2511
2610
  }
2512
- const allForBootstrap = existsSync21(ctx.paths.memoriesDir) ? await loadMemoriesFromDir16(ctx.paths.memoriesDir) : [];
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 && existsSync21(ctx.paths.haiveDir)) {
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 (existsSync21(ctx.paths.haiveDir)) {
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 = path11.join(root, "package.json");
2647
- if (!existsSync21(pkgPath)) return null;
2745
+ const pkgPath = path12.join(root, "package.json");
2746
+ if (!existsSync22(pkgPath)) return null;
2648
2747
  try {
2649
- const pkg = JSON.parse(await readFile6(pkgPath, "utf8"));
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.37.0" : "dev";
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 z20 } from "zod";
2818
+ import { z as z21 } from "zod";
2720
2819
  var CodeMapInputSchema = {
2721
- file: z20.string().optional().describe("Filter to files whose path contains this substring"),
2722
- symbol: z20.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
2723
- paths: z20.array(z20.string()).default([]).describe(
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: z20.number().int().positive().default(40).describe("Cap on returned files (hard limit, applied after token budget)"),
2727
- max_tokens: z20.number().int().positive().optional().describe(
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 = z20.object(CodeMapInputSchema);
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 existsSync22 } from "fs";
2801
- import { loadMemoriesFromDir as loadMemoriesFromDir17 } from "@hivelore/core";
2802
- import { z as z21 } from "zod";
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: z21.string().min(1).describe("First memory id"),
2805
- id_b: z21.string().min(1).describe("Second memory id")
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 (!existsSync22(ctx.paths.memoriesDir)) {
2907
+ if (!existsSync23(ctx.paths.memoriesDir)) {
2809
2908
  throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
2810
2909
  }
2811
- const all = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
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 existsSync23 } from "fs";
2846
- import { loadMemoriesFromDir as loadMemoriesFromDir18 } from "@hivelore/core";
2847
- import { z as z22 } from "zod";
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: z22.enum(["personal", "team", "any"]).default("any").describe(
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 (!existsSync23(ctx.paths.memoriesDir)) {
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 loadMemoriesFromDir18(ctx.paths.memoriesDir);
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 z23 } from "zod";
2980
+ import { z as z24 } from "zod";
2882
2981
  var MemRelevantToInputSchema = {
2883
- task: z23.string().min(1).describe("What you are about to do, in 1\u20132 sentences. Used to rank relevant memories."),
2884
- files: z23.array(z23.string()).default([]).describe("Optional: files you are about to edit \u2014 surfaces anchored memories."),
2885
- limit: z23.number().int().positive().max(30).default(8).describe("Cap on returned memories."),
2886
- min_semantic_score: z23.number().min(0).max(1).default(0.25).describe("Drop weakly-related semantic hits below this cosine threshold."),
2887
- format: z23.enum(["full", "compact", "actions"]).default("full").describe("'compact' = id + 1-line summary; 'full' = complete bodies; 'actions' = bullet-first excerpts.")
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 z24 } from "zod";
3016
+ import { z as z25 } from "zod";
2918
3017
  import { loadCodeMap as loadCodeMap3 } from "@hivelore/core";
2919
3018
  var CodeSearchInputSchema = {
2920
- query: z24.string().min(1).describe(
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: z24.number().int().positive().max(50).default(5).describe("Number of top hits to return."),
2924
- min_score: z24.number().min(0).max(1).default(0.2).describe(
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 existsSync24 } from "fs";
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 loadMemoriesFromDir19,
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 z25 } from "zod";
3085
+ import { z as z26 } from "zod";
2987
3086
  var AntiPatternsCheckInputSchema = {
2988
- diff: z25.string().optional().describe(
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: z25.array(z25.string()).default([]).describe(
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: z25.number().int().positive().max(20).default(8).describe("Cap on returned warnings."),
2995
- semantic: z25.boolean().default(true).describe(
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: z25.number().min(0).max(1).default(0.45).describe(
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 (!existsSync24(ctx.paths.memoriesDir)) {
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 loadMemoriesFromDir19(ctx.paths.memoriesDir);
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 existsSync25 } from "fs";
3300
+ import { existsSync as existsSync26 } from "fs";
3202
3301
  import {
3203
- loadMemoriesFromDir as loadMemoriesFromDir20,
3302
+ loadMemoriesFromDir as loadMemoriesFromDir21,
3204
3303
  tokenizeQuery as tokenizeQuery4
3205
3304
  } from "@hivelore/core";
3206
- import { z as z26 } from "zod";
3305
+ import { z as z27 } from "zod";
3207
3306
  var MemDistillInputSchema = {
3208
- since_days: z26.number().int().positive().default(30).describe("Only consider memories created in the last N days."),
3209
- min_cluster: z26.number().int().min(2).default(3).describe("Minimum cluster size to surface."),
3210
- type_filter: z26.enum(["gotcha", "attempt", "all"]).default("gotcha").describe(
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: z26.enum(["personal", "team", "module", "any"]).default("any").describe("Restrict to a specific 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 (!existsSync25(ctx.paths.memoriesDir)) {
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 loadMemoriesFromDir20(ctx.paths.memoriesDir);
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 z27 } from "zod";
3461
+ import { z as z28 } from "zod";
3363
3462
  var PreCommitCheckInputSchema = {
3364
- diff: z27.string().optional().describe(
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: z27.array(z27.string()).default([]).describe("Project-relative paths affected by the change. At least one of `diff` or `paths` should be provided."),
3368
- block_on: z27.enum(["any", "high-confidence", "never"]).default("high-confidence").describe(
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: z27.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index)."),
3372
- anchored_blocks: z27.boolean().default(false).describe(
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 existsSync26 } from "fs";
3777
+ import { existsSync as existsSync27 } from "fs";
3679
3778
  import {
3680
3779
  findLexicalConflictPairs,
3681
3780
  findTopicStatusConflictPairs,
3682
- loadMemoriesFromDir as loadMemoriesFromDir21,
3781
+ loadMemoriesFromDir as loadMemoriesFromDir22,
3683
3782
  planConflictResolution
3684
3783
  } from "@hivelore/core";
3685
- import { z as z28 } from "zod";
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: z28.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
3700
- types: z28.array(z28.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
3701
- min_jaccard: z28.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
3702
- max_pairs: z28.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
3703
- max_scan: z28.number().int().positive().max(2e3).default(500).describe("Maximum memories sampled for O(n\xB2) scan \u2014 excess dropped after chronological sort."),
3704
- max_topic_pairs: z28.number().int().positive().max(100).default(20).describe(
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 (!existsSync26(ctx.paths.memoriesDir)) {
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 loadMemoriesFromDir21(ctx.paths.memoriesDir);
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 z29 } from "zod";
3847
+ import { z as z30 } from "zod";
3749
3848
  var MemResolveProjectInputSchema = {
3750
- cwd: z29.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
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 z30 } from "zod";
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: z30.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
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 existsSync27 } from "fs";
3777
- import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir22 } from "@hivelore/core";
3778
- import { z as z31 } from "zod";
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: z31.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
3781
- topic: z31.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
3782
- limit: z31.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
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 (!existsSync27(ctx.paths.memoriesDir)) {
3884
+ if (!existsSync28(ctx.paths.memoriesDir)) {
3786
3885
  return { entries: [], total: 0, notice: "No .ai/memories directory." };
3787
3886
  }
3788
- const all = await loadMemoriesFromDir22(ctx.paths.memoriesDir);
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 z32 } from "zod";
3897
+ import { z as z33 } from "zod";
3799
3898
  var BootstrapProjectArgsSchema = {
3800
- module: z32.string().optional().describe(
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: z32.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
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 readFile7, readdir as readdir5 } from "fs/promises";
3886
- import { existsSync as existsSync28 } from "fs";
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 loadMemoriesFromDir23,
3989
+ loadMemoriesFromDir as loadMemoriesFromDir24,
3891
3990
  renderBootstrapChecklist as renderBootstrapChecklist2
3892
3991
  } from "@hivelore/core";
3893
- import { z as z33 } from "zod";
3992
+ import { z as z34 } from "zod";
3894
3993
  var BootstrapRepoArgsSchema = {
3895
- focus: z33.string().optional().describe("Optional area to emphasize first (e.g. 'payments', 'auth').")
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 readFile7(ctx.paths.projectContext, "utf8");
3999
+ projectContextRaw = await readFile8(ctx.paths.projectContext, "utf8");
3901
4000
  } catch {
3902
4001
  }
3903
- const memories = existsSync28(ctx.paths.memoriesDir) ? await loadMemoriesFromDir23(ctx.paths.memoriesDir) : [];
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 z34 } from "zod";
4070
+ import { z as z35 } from "zod";
3972
4071
  var PostTaskArgsSchema = {
3973
- task_summary: z34.string().optional().describe("One sentence describing what you just did"),
3974
- files_touched: z34.array(z34.string()).optional().describe("Files you created or modified during the task")
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 z35 } from "zod";
4170
+ import { z as z36 } from "zod";
4072
4171
  var ImportDocsArgsSchema = {
4073
- content: z35.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
4074
- source: z35.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
4075
- scope: z35.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
4076
- dry_run: z35.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
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.37.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