@hivelore/mcp 0.37.0 → 0.39.0

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