@hivelore/cli 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.
@@ -144,41 +144,52 @@ import {
144
144
  serializeMemory as serializeMemory7
145
145
  } from "@hivelore/core";
146
146
  import { z as z15 } from "zod";
147
- import { existsSync as existsSync17 } from "fs";
147
+ import { existsSync as existsSync17, statSync } from "fs";
148
148
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile10 } from "fs/promises";
149
149
  import path7 from "path";
150
+ import { z as z17 } from "zod";
151
+ import {
152
+ loadMemoriesFromDir as loadMemoriesFromDir14,
153
+ normalizeFramework,
154
+ parseLessonFields,
155
+ pickTestFramework,
156
+ scaffoldPostIncidentTest
157
+ } from "@hivelore/core";
158
+ import { existsSync as existsSync18 } from "fs";
159
+ import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile11 } from "fs/promises";
160
+ import path8 from "path";
150
161
  import {
151
162
  draftsFromFindings,
152
163
  filterNewDrafts,
153
- loadMemoriesFromDir as loadMemoriesFromDir14,
164
+ loadMemoriesFromDir as loadMemoriesFromDir15,
154
165
  memoryFilePath as memoryFilePath3,
155
166
  parseFindings,
156
167
  serializeMemory as serializeMemory9
157
168
  } from "@hivelore/core";
158
- import { z as z17 } from "zod";
159
- import { writeFile as writeFile12, mkdir as mkdir6 } from "fs/promises";
160
- import { existsSync as existsSync19 } from "fs";
161
- import path9 from "path";
169
+ import { z as z18 } from "zod";
170
+ import { writeFile as writeFile13, mkdir as mkdir7 } from "fs/promises";
171
+ import { existsSync as existsSync20 } from "fs";
172
+ import path10 from "path";
162
173
  import {
163
174
  buildFrontmatter as buildFrontmatter3,
164
- loadMemoriesFromDir as loadMemoriesFromDir15,
175
+ loadMemoriesFromDir as loadMemoriesFromDir16,
165
176
  memoryFilePath as memoryFilePath4,
166
177
  serializeMemory as serializeMemory10
167
178
  } from "@hivelore/core";
168
- import { z as z18 } from "zod";
179
+ import { z as z19 } from "zod";
169
180
  import {
170
181
  appendUsageEvent,
171
182
  appendRuntimeJournalEntry,
172
183
  loadConfig as loadConfig2,
173
184
  writeSessionHandoff
174
185
  } from "@hivelore/core";
175
- import { mkdir as mkdir5, writeFile as writeFile11, rm } from "fs/promises";
176
- import { existsSync as existsSync18 } from "fs";
177
- import path8 from "path";
186
+ import { mkdir as mkdir6, writeFile as writeFile12, rm } from "fs/promises";
187
+ import { existsSync as existsSync19 } from "fs";
188
+ import path9 from "path";
178
189
  import { execSync as execSync2 } from "child_process";
179
- import { readFile as readFile6, writeFile as writeFile13, readdir as readdir4 } from "fs/promises";
180
- import { existsSync as existsSync21 } from "fs";
181
- import path11 from "path";
190
+ import { readFile as readFile7, writeFile as writeFile14, readdir as readdir4 } from "fs/promises";
191
+ import { existsSync as existsSync22 } from "fs";
192
+ import path12 from "path";
182
193
  import {
183
194
  allocateBudget,
184
195
  assessBootstrapState,
@@ -202,7 +213,7 @@ import {
202
213
  loadConfig as loadConfig3,
203
214
  memoryHasExcludedTag,
204
215
  hashProjectContext,
205
- loadMemoriesFromDir as loadMemoriesFromDir16,
216
+ loadMemoriesFromDir as loadMemoriesFromDir17,
206
217
  loadPreventionEvents,
207
218
  loadUsageIndex as loadUsageIndex8,
208
219
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
@@ -220,10 +231,10 @@ import {
220
231
  truncateToTokens,
221
232
  writeBriefingMarker
222
233
  } from "@hivelore/core";
223
- import { z as z19 } from "zod";
224
- import { readdir as readdir3, readFile as readFile5 } from "fs/promises";
225
- import { existsSync as existsSync20 } from "fs";
226
- import path10 from "path";
234
+ import { z as z20 } from "zod";
235
+ import { readdir as readdir3, readFile as readFile6 } from "fs/promises";
236
+ import { existsSync as existsSync21 } from "fs";
237
+ import path11 from "path";
227
238
  import {
228
239
  classifyMemoryPriority as coreClassifyPriority,
229
240
  isGlobPath,
@@ -231,17 +242,17 @@ import {
231
242
  priorityRank as corePriorityRank
232
243
  } from "@hivelore/core";
233
244
  import { estimateTokens as estimateTokens2, loadCodeMap as loadCodeMap2, queryCodeMap as queryCodeMap2 } from "@hivelore/core";
234
- import { z as z20 } from "zod";
235
- import { existsSync as existsSync22 } from "fs";
236
- import { loadMemoriesFromDir as loadMemoriesFromDir17 } from "@hivelore/core";
237
245
  import { z as z21 } from "zod";
238
246
  import { existsSync as existsSync23 } from "fs";
239
247
  import { loadMemoriesFromDir as loadMemoriesFromDir18 } from "@hivelore/core";
240
248
  import { z as z22 } from "zod";
249
+ import { existsSync as existsSync24 } from "fs";
250
+ import { loadMemoriesFromDir as loadMemoriesFromDir19 } from "@hivelore/core";
241
251
  import { z as z23 } from "zod";
242
252
  import { z as z24 } from "zod";
253
+ import { z as z25 } from "zod";
243
254
  import { loadCodeMap as loadCodeMap3 } from "@hivelore/core";
244
- import { existsSync as existsSync24 } from "fs";
255
+ import { existsSync as existsSync25 } from "fs";
245
256
  import {
246
257
  addedLinesFromDiff,
247
258
  BRIDGE_TARGET_PATH,
@@ -251,7 +262,7 @@ import {
251
262
  diffHasDistinctiveOverlap,
252
263
  getUsage as getUsage7,
253
264
  isRetiredMemory as isRetiredMemory2,
254
- loadMemoriesFromDir as loadMemoriesFromDir19,
265
+ loadMemoriesFromDir as loadMemoriesFromDir20,
255
266
  loadUsageIndex as loadUsageIndex9,
256
267
  literalMatchesAnyToken as literalMatchesAnyToken3,
257
268
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths3,
@@ -260,42 +271,42 @@ import {
260
271
  sensorTargetsFromDiff,
261
272
  tokenizeQuery as tokenizeQuery3
262
273
  } from "@hivelore/core";
263
- import { z as z25 } from "zod";
264
- import { existsSync as existsSync25 } from "fs";
274
+ import { z as z26 } from "zod";
275
+ import { existsSync as existsSync26 } from "fs";
265
276
  import {
266
- loadMemoriesFromDir as loadMemoriesFromDir20,
277
+ loadMemoriesFromDir as loadMemoriesFromDir21,
267
278
  tokenizeQuery as tokenizeQuery4
268
279
  } from "@hivelore/core";
269
- import { z as z26 } from "zod";
270
- import { pathsOverlap as pathsOverlap2 } from "@hivelore/core";
271
280
  import { z as z27 } from "zod";
272
- import { existsSync as existsSync26 } from "fs";
281
+ import { pathsOverlap as pathsOverlap2 } from "@hivelore/core";
282
+ import { z as z28 } from "zod";
283
+ import { existsSync as existsSync27 } from "fs";
273
284
  import {
274
285
  findLexicalConflictPairs,
275
286
  findTopicStatusConflictPairs,
276
- loadMemoriesFromDir as loadMemoriesFromDir21,
287
+ loadMemoriesFromDir as loadMemoriesFromDir22,
277
288
  planConflictResolution
278
289
  } from "@hivelore/core";
279
- import { z as z28 } from "zod";
280
- import { resolveProjectInfo } from "@hivelore/core";
281
290
  import { z as z29 } from "zod";
282
- import { MemoryTypeSchema, suggestTopicKey } from "@hivelore/core";
291
+ import { resolveProjectInfo } from "@hivelore/core";
283
292
  import { z as z30 } from "zod";
284
- import { existsSync as existsSync27 } from "fs";
285
- import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir22 } from "@hivelore/core";
293
+ import { MemoryTypeSchema, suggestTopicKey } from "@hivelore/core";
286
294
  import { z as z31 } from "zod";
287
- import { z as z32 } from "zod";
288
- import { readFile as readFile7, readdir as readdir5 } from "fs/promises";
289
295
  import { existsSync as existsSync28 } from "fs";
296
+ import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir23 } from "@hivelore/core";
297
+ import { z as z32 } from "zod";
298
+ import { z as z33 } from "zod";
299
+ import { readFile as readFile8, readdir as readdir5 } from "fs/promises";
300
+ import { existsSync as existsSync29 } from "fs";
290
301
  import {
291
302
  assessBootstrapState as assessBootstrapState2,
292
303
  loadCodeMap as loadCodeMap4,
293
- loadMemoriesFromDir as loadMemoriesFromDir23,
304
+ loadMemoriesFromDir as loadMemoriesFromDir24,
294
305
  renderBootstrapChecklist as renderBootstrapChecklist2
295
306
  } from "@hivelore/core";
296
- import { z as z33 } from "zod";
297
307
  import { z as z34 } from "zod";
298
308
  import { z as z35 } from "zod";
309
+ import { z as z36 } from "zod";
299
310
  import { hasRecentBriefingMarker, loadConfigSync } from "@hivelore/core";
300
311
  function createContext(options = {}) {
301
312
  const env = options.env ?? process.env;
@@ -1566,30 +1577,116 @@ async function memTried(input, ctx) {
1566
1577
  hint
1567
1578
  };
1568
1579
  }
1580
+ var PY_SIGNALS = ["pyproject.toml", "setup.py", "pytest.ini", "requirements.txt", "tox.ini"];
1581
+ async function detectTestFrameworkForPaths(root, anchorPaths) {
1582
+ const starts = anchorPaths.length > 0 ? anchorPaths : ["."];
1583
+ for (const rel of starts) {
1584
+ let dir = path7.resolve(root, rel);
1585
+ try {
1586
+ if (!statSync(dir).isDirectory()) dir = path7.dirname(dir);
1587
+ } catch {
1588
+ if (path7.extname(dir)) dir = path7.dirname(dir);
1589
+ }
1590
+ while (dir.startsWith(root)) {
1591
+ const pkgJson = path7.join(dir, "package.json");
1592
+ const hasPkg = existsSync17(pkgJson);
1593
+ const goMod = existsSync17(path7.join(dir, "go.mod"));
1594
+ const pySignal = PY_SIGNALS.some((s) => existsSync17(path7.join(dir, s)));
1595
+ if (hasPkg || goMod || pySignal) {
1596
+ let pkg = null;
1597
+ if (hasPkg) {
1598
+ try {
1599
+ pkg = JSON.parse(await readFile4(pkgJson, "utf8"));
1600
+ } catch {
1601
+ pkg = null;
1602
+ }
1603
+ }
1604
+ const baseDir = path7.relative(root, dir).split(path7.sep).join("/");
1605
+ return { framework: pickTestFramework(pkg, { goMod, pySignal }), baseDir };
1606
+ }
1607
+ const parent = path7.dirname(dir);
1608
+ if (parent === dir || dir === root) break;
1609
+ dir = parent;
1610
+ }
1611
+ }
1612
+ return { framework: "vitest", baseDir: "" };
1613
+ }
1614
+ var ScaffoldTestInputSchema = {
1615
+ memory_id: z17.string().min(1).describe("Id of the attempt/gotcha lesson to scaffold a post-incident test from."),
1616
+ 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."),
1617
+ out_path: z17.string().optional().describe("Override the generated test file path (repo-relative)."),
1618
+ write: z17.boolean().default(true).describe("Write the file to disk (default). false = return the content for preview without writing.")
1619
+ };
1620
+ async function scaffoldTest(input, ctx) {
1621
+ const loaded = existsSync17(ctx.paths.memoriesDir) ? await loadMemoriesFromDir14(ctx.paths.memoriesDir) : [];
1622
+ const found = loaded.find(({ memory }) => memory.frontmatter.id === input.memory_id);
1623
+ if (!found) {
1624
+ return { ok: false, error: `No memory found with id ${input.memory_id}`, memory_id: input.memory_id };
1625
+ }
1626
+ const anchorPaths = found.memory.frontmatter.anchor.paths ?? [];
1627
+ const detected = await detectTestFrameworkForPaths(ctx.paths.root, anchorPaths);
1628
+ const framework = input.framework ? normalizeFramework(input.framework) ?? detected.framework : detected.framework;
1629
+ const fields = parseLessonFields(found.memory.body);
1630
+ const scaffold = scaffoldPostIncidentTest(
1631
+ {
1632
+ memoryId: input.memory_id,
1633
+ title: fields.title || input.memory_id,
1634
+ whyFailed: fields.whyFailed,
1635
+ instead: fields.instead,
1636
+ incident: found.memory.frontmatter.sensor?.incident,
1637
+ paths: anchorPaths
1638
+ },
1639
+ { framework, outPath: input.out_path, baseDir: detected.baseDir }
1640
+ );
1641
+ const abs = path7.isAbsolute(scaffold.relPath) ? scaffold.relPath : path7.resolve(ctx.paths.root, scaffold.relPath);
1642
+ let written = false;
1643
+ let alreadyExists = false;
1644
+ if (input.write) {
1645
+ if (existsSync17(abs)) {
1646
+ alreadyExists = true;
1647
+ } else {
1648
+ await mkdir4(path7.dirname(abs), { recursive: true });
1649
+ await writeFile10(abs, scaffold.content, "utf8");
1650
+ written = true;
1651
+ }
1652
+ }
1653
+ return {
1654
+ ok: true,
1655
+ memory_id: input.memory_id,
1656
+ framework,
1657
+ path: scaffold.relPath,
1658
+ run_command: scaffold.runCommand,
1659
+ propose_command: scaffold.proposeCommand,
1660
+ content: scaffold.content,
1661
+ written,
1662
+ already_exists: alreadyExists,
1663
+ 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."
1664
+ };
1665
+ }
1569
1666
  var IngestFindingsInputSchema = {
1570
- format: z17.enum(["sarif", "sonar"]).describe("Report format: 'sarif' (ESLint/Semgrep/CodeQL) or 'sonar' (SonarQube issues JSON)"),
1571
- report_path: z17.string().optional().describe("Project-relative path to the findings JSON file. Provide this OR `report`."),
1572
- report: z17.string().optional().describe("Inline findings JSON content. Provide this OR `report_path`."),
1573
- type: z17.enum(["gotcha", "convention"]).default("gotcha").describe("Memory type for the created drafts"),
1574
- scope: z17.enum(["personal", "team", "module"]).default("team").describe("Visibility scope for the created memories"),
1575
- module: z17.string().optional().describe("Module name (required when scope=module)"),
1576
- min_severity: z17.enum(["info", "minor", "major", "critical", "blocker"]).optional().describe("Ignore findings below this severity"),
1577
- include_stylistic: z17.boolean().optional().describe("Also ingest auto-fixable stylistic rules (semi/quotes/prefer-const\u2026); off by default as low-value noise"),
1578
- limit: z17.number().int().positive().optional().describe("Cap the number of memories created"),
1579
- author: z17.string().optional().describe("Author handle or email"),
1580
- dry_run: z17.boolean().default(false).describe("When true, return the drafts that WOULD be created without writing them")
1667
+ format: z18.enum(["sarif", "sonar"]).describe("Report format: 'sarif' (ESLint/Semgrep/CodeQL) or 'sonar' (SonarQube issues JSON)"),
1668
+ report_path: z18.string().optional().describe("Project-relative path to the findings JSON file. Provide this OR `report`."),
1669
+ report: z18.string().optional().describe("Inline findings JSON content. Provide this OR `report_path`."),
1670
+ type: z18.enum(["gotcha", "convention"]).default("gotcha").describe("Memory type for the created drafts"),
1671
+ scope: z18.enum(["personal", "team", "module"]).default("team").describe("Visibility scope for the created memories"),
1672
+ module: z18.string().optional().describe("Module name (required when scope=module)"),
1673
+ min_severity: z18.enum(["info", "minor", "major", "critical", "blocker"]).optional().describe("Ignore findings below this severity"),
1674
+ include_stylistic: z18.boolean().optional().describe("Also ingest auto-fixable stylistic rules (semi/quotes/prefer-const\u2026); off by default as low-value noise"),
1675
+ limit: z18.number().int().positive().optional().describe("Cap the number of memories created"),
1676
+ author: z18.string().optional().describe("Author handle or email"),
1677
+ dry_run: z18.boolean().default(false).describe("When true, return the drafts that WOULD be created without writing them")
1581
1678
  };
1582
1679
  async function ingestFindings(input, ctx) {
1583
- if (!existsSync17(ctx.paths.haiveDir)) {
1680
+ if (!existsSync18(ctx.paths.haiveDir)) {
1584
1681
  throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
1585
1682
  }
1586
1683
  let raw;
1587
1684
  if (input.report && input.report.trim()) {
1588
1685
  raw = input.report;
1589
1686
  } else if (input.report_path) {
1590
- const file = path7.resolve(ctx.paths.root, input.report_path);
1591
- if (!existsSync17(file)) throw new Error(`Report file not found: ${file}`);
1592
- raw = await readFile4(file, "utf8");
1687
+ const file = path8.resolve(ctx.paths.root, input.report_path);
1688
+ if (!existsSync18(file)) throw new Error(`Report file not found: ${file}`);
1689
+ raw = await readFile5(file, "utf8");
1593
1690
  } else {
1594
1691
  throw new Error("Provide either `report_path` or `report`.");
1595
1692
  }
@@ -1603,7 +1700,7 @@ async function ingestFindings(input, ctx) {
1603
1700
  ...input.include_stylistic ? { includeStylistic: true } : {},
1604
1701
  ...input.limit ? { limit: input.limit } : {}
1605
1702
  });
1606
- const existing = existsSync17(ctx.paths.memoriesDir) ? await loadMemoriesFromDir14(ctx.paths.memoriesDir) : [];
1703
+ const existing = existsSync18(ctx.paths.memoriesDir) ? await loadMemoriesFromDir15(ctx.paths.memoriesDir) : [];
1607
1704
  const existingTopics = new Set(
1608
1705
  existing.map(({ memory }) => memory.frontmatter.topic).filter((t) => Boolean(t))
1609
1706
  );
@@ -1641,12 +1738,12 @@ async function writeDraft(ctx, draft) {
1641
1738
  draft.frontmatter.id,
1642
1739
  draft.frontmatter.module
1643
1740
  );
1644
- await mkdir4(path7.dirname(file), { recursive: true });
1645
- await writeFile10(file, serializeMemory9({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
1741
+ await mkdir5(path8.dirname(file), { recursive: true });
1742
+ await writeFile11(file, serializeMemory9({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
1646
1743
  return file;
1647
1744
  }
1648
1745
  function pendingDistillPath(ctx) {
1649
- return path8.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
1746
+ return path9.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
1650
1747
  }
1651
1748
  var SessionTracker = class {
1652
1749
  events = [];
@@ -1750,7 +1847,7 @@ var SessionTracker = class {
1750
1847
  (e) => e.tool === "mem_session_end" && !e.summary?.startsWith("Auto-captured")
1751
1848
  );
1752
1849
  const isSubstantialSession = totalCalls >= 3 || writingTools.length > 0;
1753
- if (!ranPostTask && isSubstantialSession && existsSync18(this.ctx.paths.haiveDir)) {
1850
+ if (!ranPostTask && isSubstantialSession && existsSync19(this.ctx.paths.haiveDir)) {
1754
1851
  try {
1755
1852
  const memoriesSaved = writingTools.map((e) => e.summary ?? "").filter(Boolean).slice(0, 20);
1756
1853
  const payload = {
@@ -1763,9 +1860,9 @@ var SessionTracker = class {
1763
1860
  ...gitDiff ? { git_diff: gitDiff } : {},
1764
1861
  ...recapId ? { recap_id: recapId } : {}
1765
1862
  };
1766
- const cacheDir = path8.join(this.ctx.paths.haiveDir, ".cache");
1767
- await mkdir5(cacheDir, { recursive: true });
1768
- await writeFile11(
1863
+ const cacheDir = path9.join(this.ctx.paths.haiveDir, ".cache");
1864
+ await mkdir6(cacheDir, { recursive: true });
1865
+ await writeFile12(
1769
1866
  pendingDistillPath(this.ctx),
1770
1867
  JSON.stringify(payload, null, 2) + "\n",
1771
1868
  "utf8"
@@ -1784,7 +1881,7 @@ var SessionTracker = class {
1784
1881
  };
1785
1882
  async function clearPendingDistill(ctx) {
1786
1883
  const p = pendingDistillPath(ctx);
1787
- if (existsSync18(p)) {
1884
+ if (existsSync19(p)) {
1788
1885
  try {
1789
1886
  await rm(p);
1790
1887
  } catch {
@@ -1799,15 +1896,15 @@ function summarizeTools(events) {
1799
1896
  return [...counts.entries()].sort((a, b) => b[1] - a[1]).map(([t, n]) => `${t} \xD7${n}`).join(", ");
1800
1897
  }
1801
1898
  var MemSessionEndInputSchema = {
1802
- goal: z18.string().min(1).describe("What you were trying to accomplish this session (1\u20132 sentences)"),
1803
- accomplished: z18.string().describe("What was actually done \u2014 bullet list recommended"),
1804
- discoveries: z18.string().default("").describe(
1899
+ goal: z19.string().min(1).describe("What you were trying to accomplish this session (1\u20132 sentences)"),
1900
+ accomplished: z19.string().describe("What was actually done \u2014 bullet list recommended"),
1901
+ discoveries: z19.string().default("").describe(
1805
1902
  "Any bugs, inconsistencies, surprises, or missing knowledge found during this session. Empty if nothing surprising was found."
1806
1903
  ),
1807
- files_touched: z18.array(z18.string()).default([]).describe("Key files that were read or modified \u2014 used as anchor paths"),
1808
- next_steps: z18.string().default("").describe("What should happen next (for the next session or a teammate)"),
1809
- scope: z18.enum(["personal", "team", "module"]).default("personal").describe("Visibility: personal = private to you, team = shared with the team"),
1810
- module: z18.string().optional().describe("Module name (required when scope=module)")
1904
+ files_touched: z19.array(z19.string()).default([]).describe("Key files that were read or modified \u2014 used as anchor paths"),
1905
+ next_steps: z19.string().default("").describe("What should happen next (for the next session or a teammate)"),
1906
+ scope: z19.enum(["personal", "team", "module"]).default("personal").describe("Visibility: personal = private to you, team = shared with the team"),
1907
+ module: z19.string().optional().describe("Module name (required when scope=module)")
1811
1908
  };
1812
1909
  function recapTopic(scope, module) {
1813
1910
  return module ? `session-recap-${scope}-${module}` : `session-recap-${scope}`;
@@ -1837,23 +1934,23 @@ ${input.next_steps}`);
1837
1934
  return lines.join("\n");
1838
1935
  }
1839
1936
  async function memSessionEnd(input, ctx) {
1840
- if (!existsSync19(ctx.paths.haiveDir)) {
1937
+ if (!existsSync20(ctx.paths.haiveDir)) {
1841
1938
  throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
1842
1939
  }
1843
1940
  const body = buildBody(input);
1844
1941
  const topic = recapTopic(input.scope, input.module);
1845
1942
  const normalizedFiles = input.files_touched.map((p) => {
1846
- if (!p || !path9.isAbsolute(p)) return p;
1847
- const rel = path9.relative(ctx.paths.root, p);
1943
+ if (!p || !path10.isAbsolute(p)) return p;
1944
+ const rel = path10.relative(ctx.paths.root, p);
1848
1945
  return rel.startsWith("..") ? p : rel;
1849
1946
  });
1850
1947
  const invalidPaths = normalizedFiles.filter(
1851
- (p) => !existsSync19(path9.resolve(ctx.paths.root, p))
1948
+ (p) => !existsSync20(path10.resolve(ctx.paths.root, p))
1852
1949
  );
1853
1950
  if (invalidPaths.length > 0) {
1854
1951
  console.warn(`[haive] session end: anchor path(s) not found: ${invalidPaths.join(", ")}`);
1855
1952
  }
1856
- const existing = existsSync19(ctx.paths.memoriesDir) ? await loadMemoriesFromDir15(ctx.paths.memoriesDir) : [];
1953
+ const existing = existsSync20(ctx.paths.memoriesDir) ? await loadMemoriesFromDir16(ctx.paths.memoriesDir) : [];
1857
1954
  const topicMatch = existing.find(
1858
1955
  ({ memory }) => memory.frontmatter.topic === topic && memory.frontmatter.scope === input.scope && (!input.module || memory.frontmatter.module === input.module)
1859
1956
  );
@@ -1869,7 +1966,7 @@ async function memSessionEnd(input, ctx) {
1869
1966
  paths: normalizedFiles.length ? normalizedFiles : fm.anchor.paths
1870
1967
  }
1871
1968
  };
1872
- await writeFile12(
1969
+ await writeFile13(
1873
1970
  topicMatch.filePath,
1874
1971
  serializeMemory10({ frontmatter: newFrontmatter, body }),
1875
1972
  "utf8"
@@ -1899,8 +1996,8 @@ async function memSessionEnd(input, ctx) {
1899
1996
  frontmatter.id,
1900
1997
  frontmatter.module
1901
1998
  );
1902
- await mkdir6(path9.dirname(file), { recursive: true });
1903
- await writeFile12(file, serializeMemory10({ frontmatter, body }), "utf8");
1999
+ await mkdir7(path10.dirname(file), { recursive: true });
2000
+ await writeFile13(file, serializeMemory10({ frontmatter, body }), "utf8");
1904
2001
  await clearPendingDistill(ctx);
1905
2002
  return {
1906
2003
  id: frontmatter.id,
@@ -2036,53 +2133,53 @@ async function trySemanticHits(ctx, task, limit) {
2036
2133
  }
2037
2134
  async function loadModuleContexts2(ctx, modules) {
2038
2135
  if (modules.length === 0) return [];
2039
- if (!existsSync20(ctx.paths.modulesContextDir)) return [];
2136
+ if (!existsSync21(ctx.paths.modulesContextDir)) return [];
2040
2137
  const available = new Set(
2041
2138
  (await readdir3(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
2042
2139
  );
2043
2140
  const out = [];
2044
2141
  for (const m of modules) {
2045
2142
  if (!available.has(m)) continue;
2046
- const file = path10.join(ctx.paths.modulesContextDir, m, "context.md");
2047
- if (existsSync20(file)) {
2048
- out.push({ name: m, content: await readFile5(file, "utf8") });
2143
+ const file = path11.join(ctx.paths.modulesContextDir, m, "context.md");
2144
+ if (existsSync21(file)) {
2145
+ out.push({ name: m, content: await readFile6(file, "utf8") });
2049
2146
  }
2050
2147
  }
2051
2148
  return out;
2052
2149
  }
2053
2150
  var GetBriefingInputSchema = {
2054
- task: z19.string().optional().describe(
2151
+ task: z20.string().optional().describe(
2055
2152
  "What you are about to do, in 1\u20132 sentences. Used to rank relevant memories semantically."
2056
2153
  ),
2057
- files: z19.array(z19.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
2058
- max_tokens: z19.number().int().positive().default(8e3).describe(
2154
+ files: z20.array(z20.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
2155
+ max_tokens: z20.number().int().positive().default(8e3).describe(
2059
2156
  "Approximate token budget for the entire briefing. Each section is allocated a share and truncated to fit."
2060
2157
  ),
2061
- max_memories: z19.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
2062
- include_project_context: z19.boolean().default(true),
2063
- dedupe_project_context: z19.boolean().optional().describe(
2158
+ max_memories: z20.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
2159
+ include_project_context: z20.boolean().default(true),
2160
+ dedupe_project_context: z20.boolean().optional().describe(
2064
2161
  "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."
2065
2162
  ),
2066
- include_module_contexts: z19.boolean().default(true),
2067
- semantic: z19.boolean().default(true).describe(
2163
+ include_module_contexts: z20.boolean().default(true),
2164
+ semantic: z20.boolean().default(true).describe(
2068
2165
  "Use semantic ranking when a task is provided (requires `hivelore embeddings index`)."
2069
2166
  ),
2070
- include_stale: z19.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
2071
- track: z19.boolean().default(true).describe("Increment read_count on returned memories"),
2072
- format: z19.enum(["full", "compact", "actions"]).default("full").describe(
2167
+ include_stale: z20.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
2168
+ track: z20.boolean().default(true).describe("Increment read_count on returned memories"),
2169
+ format: z20.enum(["full", "compact", "actions"]).default("full").describe(
2073
2170
  "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."
2074
2171
  ),
2075
- symbols: z19.array(z19.string()).default([]).describe(
2172
+ symbols: z20.array(z20.string()).default([]).describe(
2076
2173
  "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."
2077
2174
  ),
2078
- min_semantic_score: z19.number().min(0).max(1).default(0).describe(
2175
+ min_semantic_score: z20.number().min(0).max(1).default(0).describe(
2079
2176
  "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."
2080
2177
  ),
2081
- budget_preset: z19.enum(["quick", "balanced", "deep"]).optional().describe(
2178
+ budget_preset: z20.enum(["quick", "balanced", "deep"]).optional().describe(
2082
2179
  "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."
2083
2180
  )
2084
2181
  };
2085
- var GetBriefingZod = z19.object(GetBriefingInputSchema);
2182
+ var GetBriefingZod = z20.object(GetBriefingInputSchema);
2086
2183
  async function getBriefing(input, ctx) {
2087
2184
  const resolvedBudget = resolveBriefingBudget(input.budget_preset, {
2088
2185
  max_tokens: input.max_tokens,
@@ -2098,8 +2195,8 @@ async function getBriefing(input, ctx) {
2098
2195
  let usage = { version: 1, updated_at: "", by_id: {} };
2099
2196
  let byId = /* @__PURE__ */ new Map();
2100
2197
  let lastSession;
2101
- if (existsSync21(ctx.paths.memoriesDir)) {
2102
- const allLoaded = await loadMemoriesFromDir16(ctx.paths.memoriesDir);
2198
+ if (existsSync22(ctx.paths.memoriesDir)) {
2199
+ const allLoaded = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
2103
2200
  const recaps = allLoaded.filter(({ memory }) => memory.frontmatter.type === "session_recap").sort(
2104
2201
  (a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
2105
2202
  );
@@ -2273,7 +2370,7 @@ async function getBriefing(input, ctx) {
2273
2370
  if (!isAutoPromoteEligible(loaded.memory.frontmatter, u, rule)) continue;
2274
2371
  const newFm = { ...loaded.memory.frontmatter, status: "validated", validated_by: "auto" };
2275
2372
  try {
2276
- await writeFile13(loaded.filePath, serializeMemory11({ frontmatter: newFm, body: loaded.memory.body }), "utf8");
2373
+ await writeFile14(loaded.filePath, serializeMemory11({ frontmatter: newFm, body: loaded.memory.body }), "utf8");
2277
2374
  m.status = "validated";
2278
2375
  m.confidence = "trusted";
2279
2376
  } catch {
@@ -2281,7 +2378,7 @@ async function getBriefing(input, ctx) {
2281
2378
  }
2282
2379
  }
2283
2380
  }
2284
- let projectContextRaw = input.include_project_context && existsSync21(ctx.paths.projectContext) ? await readFile6(ctx.paths.projectContext, "utf8") : "";
2381
+ let projectContextRaw = input.include_project_context && existsSync22(ctx.paths.projectContext) ? await readFile7(ctx.paths.projectContext, "utf8") : "";
2285
2382
  let contextOmittedRecent = false;
2286
2383
  if (projectContextRaw && input.dedupe_project_context !== false) {
2287
2384
  const ctxHash = hashProjectContext(projectContextRaw);
@@ -2296,7 +2393,7 @@ async function getBriefing(input, ctx) {
2296
2393
  const setupWarnings = [];
2297
2394
  let autoContextGenerated = false;
2298
2395
  let projectContext = isTemplateContext ? "" : projectContextRaw;
2299
- if ((isTemplateContext || !existsSync21(ctx.paths.projectContext)) && input.include_project_context) {
2396
+ if ((isTemplateContext || !existsSync22(ctx.paths.projectContext)) && input.include_project_context) {
2300
2397
  const haiveConfig = await loadConfig3(ctx.paths);
2301
2398
  if (haiveConfig.autoContext) {
2302
2399
  const codeMap = await loadCodeMap(ctx.paths);
@@ -2470,8 +2567,8 @@ ${m.content}`).join("\n\n---\n\n"),
2470
2567
  actionRequired.push(extractActionItem(m.id, loaded.memory.body));
2471
2568
  }
2472
2569
  }
2473
- if (existsSync21(ctx.paths.memoriesDir)) {
2474
- const allMems = await loadMemoriesFromDir16(ctx.paths.memoriesDir);
2570
+ if (existsSync22(ctx.paths.memoriesDir)) {
2571
+ const allMems = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
2475
2572
  for (const { memory } of allMems) {
2476
2573
  const fm = memory.frontmatter;
2477
2574
  if (!fm.requires_human_approval) continue;
@@ -2481,9 +2578,9 @@ ${m.content}`).join("\n\n---\n\n"),
2481
2578
  }
2482
2579
  }
2483
2580
  const pendingDistillFile = pendingDistillPath(ctx);
2484
- if (existsSync21(pendingDistillFile)) {
2581
+ if (existsSync22(pendingDistillFile)) {
2485
2582
  try {
2486
- const raw = await readFile6(pendingDistillFile, "utf8");
2583
+ const raw = await readFile7(pendingDistillFile, "utf8");
2487
2584
  const pd = JSON.parse(raw);
2488
2585
  const ageMs = Date.now() - new Date(pd.session_end).getTime();
2489
2586
  const SEVEN_DAYS = 7 * 24 * 60 * 60 * 1e3;
@@ -2510,7 +2607,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
2510
2607
  }
2511
2608
  }
2512
2609
  const memoriesEmpty = outputMemories.length === 0;
2513
- const hasMemoriesDir = existsSync21(ctx.paths.memoriesDir);
2610
+ const hasMemoriesDir = existsSync22(ctx.paths.memoriesDir);
2514
2611
  const isColdStart = isTemplateContext && memoriesEmpty && !lastSession && !autoContextGenerated;
2515
2612
  const hasUnguessableSignal = outputMemories.some(
2516
2613
  (m) => (m.priority === "must_read" || m.priority === "useful") && specificityScore(m.body) >= GUESSABLE_THRESHOLD
@@ -2525,10 +2622,10 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
2525
2622
  try {
2526
2623
  let pcRaw = "";
2527
2624
  try {
2528
- pcRaw = await readFile6(ctx.paths.projectContext, "utf8");
2625
+ pcRaw = await readFile7(ctx.paths.projectContext, "utf8");
2529
2626
  } catch {
2530
2627
  }
2531
- const allForBootstrap = existsSync21(ctx.paths.memoriesDir) ? await loadMemoriesFromDir16(ctx.paths.memoriesDir) : [];
2628
+ const allForBootstrap = existsSync22(ctx.paths.memoriesDir) ? await loadMemoriesFromDir17(ctx.paths.memoriesDir) : [];
2532
2629
  const cmForBootstrap = await loadCodeMap(ctx.paths);
2533
2630
  let existingModules = [];
2534
2631
  try {
@@ -2592,7 +2689,7 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
2592
2689
  "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."
2593
2690
  );
2594
2691
  }
2595
- if (outputMemories.length > 0 && existsSync21(ctx.paths.haiveDir)) {
2692
+ if (outputMemories.length > 0 && existsSync22(ctx.paths.haiveDir)) {
2596
2693
  const proof = briefingProofLine(await loadPreventionEvents(ctx.paths));
2597
2694
  if (proof) hints.push(proof);
2598
2695
  }
@@ -2606,7 +2703,7 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
2606
2703
  adaptiveTrim
2607
2704
  });
2608
2705
  const breadcrumbTokens = breadcrumbs ? estimateTokens([...breadcrumbs.start_here, ...breadcrumbs.drill_down, breadcrumbs.note ?? ""].join("\n")) : 0;
2609
- if (existsSync21(ctx.paths.haiveDir)) {
2706
+ if (existsSync22(ctx.paths.haiveDir)) {
2610
2707
  await writeBriefingMarker(ctx.paths, {
2611
2708
  sessionId: process.env.HAIVE_SESSION_ID,
2612
2709
  ...input.task ? { task: input.task } : {},
@@ -2662,10 +2759,10 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
2662
2759
  };
2663
2760
  }
2664
2761
  async function detectRunCommands(root) {
2665
- const pkgPath = path11.join(root, "package.json");
2666
- if (!existsSync21(pkgPath)) return null;
2762
+ const pkgPath = path12.join(root, "package.json");
2763
+ if (!existsSync22(pkgPath)) return null;
2667
2764
  try {
2668
- const pkg = JSON.parse(await readFile6(pkgPath, "utf8"));
2765
+ const pkg = JSON.parse(await readFile7(pkgPath, "utf8"));
2669
2766
  const scripts = pkg.scripts ?? {};
2670
2767
  const order = ["test", "build", "lint", "typecheck", "type-check", "dev", "start"];
2671
2768
  const lines = order.filter((name) => typeof scripts[name] === "string" && scripts[name].trim() !== "").map((name) => `- \`${name}\`: \`${scripts[name]}\``);
@@ -2730,20 +2827,20 @@ function oneLine(value) {
2730
2827
  return value.replace(/\s+/g, " ").replace(/"/g, '\\"').trim().slice(0, 120);
2731
2828
  }
2732
2829
  function serverVersion() {
2733
- return true ? "0.37.0" : "dev";
2830
+ return true ? "0.38.0" : "dev";
2734
2831
  }
2735
2832
  var CodeMapInputSchema = {
2736
- file: z20.string().optional().describe("Filter to files whose path contains this substring"),
2737
- symbol: z20.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
2738
- paths: z20.array(z20.string()).default([]).describe(
2833
+ file: z21.string().optional().describe("Filter to files whose path contains this substring"),
2834
+ symbol: z21.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
2835
+ paths: z21.array(z21.string()).default([]).describe(
2739
2836
  "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."
2740
2837
  ),
2741
- max_files: z20.number().int().positive().default(40).describe("Cap on returned files (hard limit, applied after token budget)"),
2742
- max_tokens: z20.number().int().positive().optional().describe(
2838
+ max_files: z21.number().int().positive().default(40).describe("Cap on returned files (hard limit, applied after token budget)"),
2839
+ max_tokens: z21.number().int().positive().optional().describe(
2743
2840
  "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)."
2744
2841
  )
2745
2842
  };
2746
- var CodeMapInputZod = z20.object(CodeMapInputSchema);
2843
+ var CodeMapInputZod = z21.object(CodeMapInputSchema);
2747
2844
  async function codeMapTool(input, ctx) {
2748
2845
  const map = await loadCodeMap2(ctx.paths);
2749
2846
  if (!map) {
@@ -2811,14 +2908,14 @@ function estimateFileEntryTokens(f) {
2811
2908
  return estimateTokens2(f.path) + estimateTokens2(f.entry.summary ?? "") + exportsCost + 4;
2812
2909
  }
2813
2910
  var MemDiffInputSchema = {
2814
- id_a: z21.string().min(1).describe("First memory id"),
2815
- id_b: z21.string().min(1).describe("Second memory id")
2911
+ id_a: z22.string().min(1).describe("First memory id"),
2912
+ id_b: z22.string().min(1).describe("Second memory id")
2816
2913
  };
2817
2914
  async function memDiff(input, ctx) {
2818
- if (!existsSync22(ctx.paths.memoriesDir)) {
2915
+ if (!existsSync23(ctx.paths.memoriesDir)) {
2819
2916
  throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
2820
2917
  }
2821
- const all = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
2918
+ const all = await loadMemoriesFromDir18(ctx.paths.memoriesDir);
2822
2919
  const foundA = all.find((m) => m.memory.frontmatter.id === input.id_a);
2823
2920
  const foundB = all.find((m) => m.memory.frontmatter.id === input.id_b);
2824
2921
  if (!foundA) throw new Error(`No memory with id "${input.id_a}".`);
@@ -2851,15 +2948,15 @@ async function memDiff(input, ctx) {
2851
2948
  };
2852
2949
  }
2853
2950
  var GetRecapInputSchema = {
2854
- scope: z22.enum(["personal", "team", "any"]).default("any").describe(
2951
+ scope: z23.enum(["personal", "team", "any"]).default("any").describe(
2855
2952
  "Limit to a specific scope's recap. Default 'any' returns the most recent recap across both personal and team scopes."
2856
2953
  )
2857
2954
  };
2858
2955
  async function getRecap(input, ctx) {
2859
- if (!existsSync23(ctx.paths.memoriesDir)) {
2956
+ if (!existsSync24(ctx.paths.memoriesDir)) {
2860
2957
  return { recap: null, notice: "No .ai/memories directory \u2014 haive not initialized here." };
2861
2958
  }
2862
- const all = await loadMemoriesFromDir18(ctx.paths.memoriesDir);
2959
+ const all = await loadMemoriesFromDir19(ctx.paths.memoriesDir);
2863
2960
  const recaps = all.filter(({ memory }) => memory.frontmatter.type === "session_recap").filter(({ memory }) => input.scope === "any" || memory.frontmatter.scope === input.scope).sort(
2864
2961
  (a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
2865
2962
  );
@@ -2882,11 +2979,11 @@ async function getRecap(input, ctx) {
2882
2979
  };
2883
2980
  }
2884
2981
  var MemRelevantToInputSchema = {
2885
- task: z23.string().min(1).describe("What you are about to do, in 1\u20132 sentences. Used to rank relevant memories."),
2886
- files: z23.array(z23.string()).default([]).describe("Optional: files you are about to edit \u2014 surfaces anchored memories."),
2887
- limit: z23.number().int().positive().max(30).default(8).describe("Cap on returned memories."),
2888
- min_semantic_score: z23.number().min(0).max(1).default(0.25).describe("Drop weakly-related semantic hits below this cosine threshold."),
2889
- 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.")
2890
2987
  };
2891
2988
  async function memRelevantTo(input, ctx) {
2892
2989
  const briefingInput = {
@@ -2915,11 +3012,11 @@ async function memRelevantTo(input, ctx) {
2915
3012
  return out;
2916
3013
  }
2917
3014
  var CodeSearchInputSchema = {
2918
- query: z24.string().min(1).describe(
3015
+ query: z25.string().min(1).describe(
2919
3016
  "Natural-language description of what you are looking for in the codebase (e.g. 'function that hashes passwords', 'JWT signing logic', 'route registration')."
2920
3017
  ),
2921
- k: z24.number().int().positive().max(50).default(5).describe("Number of top hits to return."),
2922
- min_score: z24.number().min(0).max(1).default(0.2).describe(
3018
+ k: z25.number().int().positive().max(50).default(5).describe("Number of top hits to return."),
3019
+ min_score: z25.number().min(0).max(1).default(0.2).describe(
2923
3020
  "Minimum cosine similarity. Hits below this threshold are dropped to avoid noise. Try 0.3+ for stricter matching."
2924
3021
  )
2925
3022
  };
@@ -2961,17 +3058,17 @@ async function codeSearch(input, ctx) {
2961
3058
  };
2962
3059
  }
2963
3060
  var AntiPatternsCheckInputSchema = {
2964
- diff: z25.string().optional().describe(
3061
+ diff: z26.string().optional().describe(
2965
3062
  "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."
2966
3063
  ),
2967
- paths: z25.array(z25.string()).default([]).describe(
3064
+ paths: z26.array(z26.string()).default([]).describe(
2968
3065
  "File paths affected by the change. Memories anchored to any of these paths are surfaced regardless of the diff content."
2969
3066
  ),
2970
- limit: z25.number().int().positive().max(20).default(8).describe("Cap on returned warnings."),
2971
- semantic: z25.boolean().default(true).describe(
3067
+ limit: z26.number().int().positive().max(20).default(8).describe("Cap on returned warnings."),
3068
+ semantic: z26.boolean().default(true).describe(
2972
3069
  "When true, also use semantic search (requires @hivelore/embeddings + memory index) to find related anti-patterns."
2973
3070
  ),
2974
- min_semantic_score: z25.number().min(0).max(1).default(0.45).describe(
3071
+ min_semantic_score: z26.number().min(0).max(1).default(0.45).describe(
2975
3072
  "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."
2976
3073
  )
2977
3074
  };
@@ -3055,10 +3152,10 @@ async function antiPatternsCheck(input, ctx) {
3055
3152
  notice: "Nothing to check \u2014 provide either `diff` text or `paths`."
3056
3153
  };
3057
3154
  }
3058
- if (!existsSync24(ctx.paths.memoriesDir)) {
3155
+ if (!existsSync25(ctx.paths.memoriesDir)) {
3059
3156
  return { scanned: 0, warnings: [], notice: "No .ai/memories directory \u2014 nothing to check against." };
3060
3157
  }
3061
- const all = await loadMemoriesFromDir19(ctx.paths.memoriesDir);
3158
+ const all = await loadMemoriesFromDir20(ctx.paths.memoriesDir);
3062
3159
  const minSemanticScore = input.min_semantic_score ?? 0.45;
3063
3160
  const negative = all.filter(({ memory }) => {
3064
3161
  const t = memory.frontmatter.type;
@@ -3173,12 +3270,12 @@ async function antiPatternsCheck(input, ctx) {
3173
3270
  };
3174
3271
  }
3175
3272
  var MemDistillInputSchema = {
3176
- since_days: z26.number().int().positive().default(30).describe("Only consider memories created in the last N days."),
3177
- min_cluster: z26.number().int().min(2).default(3).describe("Minimum cluster size to surface."),
3178
- type_filter: z26.enum(["gotcha", "attempt", "all"]).default("gotcha").describe(
3273
+ since_days: z27.number().int().positive().default(30).describe("Only consider memories created in the last N days."),
3274
+ min_cluster: z27.number().int().min(2).default(3).describe("Minimum cluster size to surface."),
3275
+ type_filter: z27.enum(["gotcha", "attempt", "all"]).default("gotcha").describe(
3179
3276
  "Memory type to scan. 'gotcha' targets observe-style discoveries that recur, 'attempt' surfaces failed approaches that repeat, 'all' considers both."
3180
3277
  ),
3181
- scope: z26.enum(["personal", "team", "module", "any"]).default("any").describe("Restrict to a specific scope.")
3278
+ scope: z27.enum(["personal", "team", "module", "any"]).default("any").describe("Restrict to a specific scope.")
3182
3279
  };
3183
3280
  var MS_PER_DAY = 24 * 60 * 60 * 1e3;
3184
3281
  var STOP_WORDS = /* @__PURE__ */ new Set([
@@ -3218,11 +3315,11 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
3218
3315
  "error"
3219
3316
  ]);
3220
3317
  async function memDistill(input, ctx) {
3221
- if (!existsSync25(ctx.paths.memoriesDir)) {
3318
+ if (!existsSync26(ctx.paths.memoriesDir)) {
3222
3319
  return { scanned: 0, singletons: 0, clusters: [], notice: "No .ai/memories directory." };
3223
3320
  }
3224
3321
  const cutoff = Date.now() - input.since_days * MS_PER_DAY;
3225
- const all = await loadMemoriesFromDir20(ctx.paths.memoriesDir);
3322
+ const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
3226
3323
  const candidates = all.filter(({ memory }) => {
3227
3324
  const fm = memory.frontmatter;
3228
3325
  if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
@@ -3325,15 +3422,15 @@ function firstHeading(body) {
3325
3422
  return void 0;
3326
3423
  }
3327
3424
  var PreCommitCheckInputSchema = {
3328
- diff: z27.string().optional().describe(
3425
+ diff: z28.string().optional().describe(
3329
3426
  "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`."
3330
3427
  ),
3331
- paths: z27.array(z27.string()).default([]).describe("Project-relative paths affected by the change. At least one of `diff` or `paths` should be provided."),
3332
- block_on: z27.enum(["any", "high-confidence", "never"]).default("high-confidence").describe(
3428
+ paths: z28.array(z28.string()).default([]).describe("Project-relative paths affected by the change. At least one of `diff` or `paths` should be provided."),
3429
+ block_on: z28.enum(["any", "high-confidence", "never"]).default("high-confidence").describe(
3333
3430
  "When to set should_block=true: 'any' = any warning blocks; 'high-confidence' = only warnings from authoritative/trusted memories block; 'never' = report only, never block."
3334
3431
  ),
3335
- semantic: z27.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index)."),
3336
- anchored_blocks: z27.boolean().default(false).describe(
3432
+ semantic: z28.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index)."),
3433
+ anchored_blocks: z28.boolean().default(false).describe(
3337
3434
  "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."
3338
3435
  )
3339
3436
  };
@@ -3650,17 +3747,17 @@ function suggestResolution(byId, idA, idB) {
3650
3747
  };
3651
3748
  }
3652
3749
  var MemConflictCandidatesInputSchema = {
3653
- since_days: z28.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
3654
- types: z28.array(z28.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
3655
- min_jaccard: z28.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
3656
- max_pairs: z28.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
3657
- 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."),
3658
- max_topic_pairs: z28.number().int().positive().max(100).default(20).describe(
3750
+ since_days: z29.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
3751
+ types: z29.array(z29.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
3752
+ min_jaccard: z29.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
3753
+ max_pairs: z29.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
3754
+ 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."),
3755
+ max_topic_pairs: z29.number().int().positive().max(100).default(20).describe(
3659
3756
  "Cap for extra signal: memories sharing the same topic with validated vs rejected status."
3660
3757
  )
3661
3758
  };
3662
3759
  async function memConflictCandidates(input, ctx) {
3663
- if (!existsSync26(ctx.paths.memoriesDir)) {
3760
+ if (!existsSync27(ctx.paths.memoriesDir)) {
3664
3761
  return {
3665
3762
  pairs: [],
3666
3763
  topic_status_pairs: [],
@@ -3669,7 +3766,7 @@ async function memConflictCandidates(input, ctx) {
3669
3766
  notice: "No .ai/memories directory."
3670
3767
  };
3671
3768
  }
3672
- const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
3769
+ const all = await loadMemoriesFromDir22(ctx.paths.memoriesDir);
3673
3770
  const byId = new Map(all.map((m) => [m.memory.frontmatter.id, m]));
3674
3771
  const { pairs, scanned, truncated } = findLexicalConflictPairs(all, {
3675
3772
  sinceDays: input.since_days,
@@ -3697,7 +3794,7 @@ async function memConflictCandidates(input, ctx) {
3697
3794
  };
3698
3795
  }
3699
3796
  var MemResolveProjectInputSchema = {
3700
- cwd: z29.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
3797
+ cwd: z30.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
3701
3798
  };
3702
3799
  async function memResolveProject(input, _ctx) {
3703
3800
  void _ctx;
@@ -3710,7 +3807,7 @@ async function memResolveProject(input, _ctx) {
3710
3807
  }
3711
3808
  var MemSuggestTopicInputSchema = {
3712
3809
  type: MemoryTypeSchema.describe("Memory kind \u2014 drives the suggested topic family."),
3713
- title: z30.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
3810
+ title: z31.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
3714
3811
  };
3715
3812
  async function memSuggestTopic(input, _ctx) {
3716
3813
  void _ctx;
@@ -3718,15 +3815,15 @@ async function memSuggestTopic(input, _ctx) {
3718
3815
  return { topic_key: suggestion.topic_key, family: suggestion.family, type: input.type };
3719
3816
  }
3720
3817
  var MemTimelineInputSchema = {
3721
- memory_id: z31.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
3722
- topic: z31.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
3723
- limit: z31.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
3818
+ memory_id: z32.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
3819
+ topic: z32.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
3820
+ limit: z32.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
3724
3821
  };
3725
3822
  async function memTimeline(input, ctx) {
3726
- if (!existsSync27(ctx.paths.memoriesDir)) {
3823
+ if (!existsSync28(ctx.paths.memoriesDir)) {
3727
3824
  return { entries: [], total: 0, notice: "No .ai/memories directory." };
3728
3825
  }
3729
- const all = await loadMemoriesFromDir22(ctx.paths.memoriesDir);
3826
+ const all = await loadMemoriesFromDir23(ctx.paths.memoriesDir);
3730
3827
  const { entries, notice } = collectTimelineEntries(all, {
3731
3828
  memoryId: input.memory_id,
3732
3829
  topic: input.topic,
@@ -3735,10 +3832,10 @@ async function memTimeline(input, ctx) {
3735
3832
  return { entries, total: entries.length, notice };
3736
3833
  }
3737
3834
  var BootstrapProjectArgsSchema = {
3738
- module: z32.string().optional().describe(
3835
+ module: z33.string().optional().describe(
3739
3836
  "Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
3740
3837
  ),
3741
- focus: z32.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
3838
+ focus: z33.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
3742
3839
  };
3743
3840
  var ROOT_TEMPLATE = `# Project context
3744
3841
 
@@ -3819,15 +3916,15 @@ ${template}\`\`\`
3819
3916
  };
3820
3917
  }
3821
3918
  var BootstrapRepoArgsSchema = {
3822
- focus: z33.string().optional().describe("Optional area to emphasize first (e.g. 'payments', 'auth').")
3919
+ focus: z34.string().optional().describe("Optional area to emphasize first (e.g. 'payments', 'auth').")
3823
3920
  };
3824
3921
  async function currentAssessment(ctx) {
3825
3922
  let projectContextRaw = "";
3826
3923
  try {
3827
- projectContextRaw = await readFile7(ctx.paths.projectContext, "utf8");
3924
+ projectContextRaw = await readFile8(ctx.paths.projectContext, "utf8");
3828
3925
  } catch {
3829
3926
  }
3830
- const memories = existsSync28(ctx.paths.memoriesDir) ? await loadMemoriesFromDir23(ctx.paths.memoriesDir) : [];
3927
+ const memories = existsSync29(ctx.paths.memoriesDir) ? await loadMemoriesFromDir24(ctx.paths.memoriesDir) : [];
3831
3928
  const codeMap = await loadCodeMap4(ctx.paths);
3832
3929
  let existingModules = [];
3833
3930
  try {
@@ -3894,8 +3991,8 @@ Main code areas detected: ${areas}
3894
3991
  };
3895
3992
  }
3896
3993
  var PostTaskArgsSchema = {
3897
- task_summary: z34.string().optional().describe("One sentence describing what you just did"),
3898
- files_touched: z34.array(z34.string()).optional().describe("Files you created or modified during the task")
3994
+ task_summary: z35.string().optional().describe("One sentence describing what you just did"),
3995
+ files_touched: z35.array(z35.string()).optional().describe("Files you created or modified during the task")
3899
3996
  };
3900
3997
  function postTaskPrompt(args, ctx) {
3901
3998
  const taskLine = args.task_summary ? `
@@ -3991,10 +4088,10 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
3991
4088
  };
3992
4089
  }
3993
4090
  var ImportDocsArgsSchema = {
3994
- content: z35.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
3995
- source: z35.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
3996
- scope: z35.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
3997
- dry_run: z35.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
4091
+ content: z36.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
4092
+ source: z36.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
4093
+ scope: z36.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
4094
+ dry_run: z36.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
3998
4095
  };
3999
4096
  function importDocsPrompt(args, ctx) {
4000
4097
  const sourceLine = args.source ? `
@@ -4057,7 +4154,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
4057
4154
  };
4058
4155
  }
4059
4156
  var SERVER_NAME = "hivelore";
4060
- var SERVER_VERSION = "0.37.0";
4157
+ var SERVER_VERSION = "0.38.0";
4061
4158
  function jsonResult(data) {
4062
4159
  return {
4063
4160
  content: [
@@ -4080,7 +4177,8 @@ var ENFORCEMENT_PROFILE_TOOLS = [
4080
4177
  "code_search",
4081
4178
  "pre_commit_check",
4082
4179
  "mem_session_end",
4083
- "propose_sensor"
4180
+ "propose_sensor",
4181
+ "scaffold_test"
4084
4182
  ];
4085
4183
  var MAINTENANCE_PROFILE_TOOLS = [
4086
4184
  ...ENFORCEMENT_PROFILE_TOOLS,
@@ -4286,6 +4384,35 @@ function createHaiveServer(options = {}) {
4286
4384
  return jsonResult(await proposeSensor(input, context));
4287
4385
  }
4288
4386
  );
4387
+ registerTool(
4388
+ "scaffold_test",
4389
+ [
4390
+ "Generate a PENDING post-incident test from a lesson (attempt/gotcha) \u2014 the on-ramp to a command",
4391
+ "sensor. A command sensor routes YOUR test as its oracle, but someone has to write it; this writes",
4392
+ "the skeleton so you only fill in the assertion.",
4393
+ "",
4394
+ "USE THIS right after mem_tried when the mistake is behavioural (a regex can't express it): it",
4395
+ "writes a stub carrying the incident's provenance and returns the exact `sensors propose --kind",
4396
+ "test` command to arm it.",
4397
+ "",
4398
+ "It DOES NOT arm a sensor \u2014 propose_sensor stays the sole validated writer, and the stub is left",
4399
+ "PENDING (todo/skip) so the suite stays green until you write the assertion. Monorepo-aware: the",
4400
+ "framework and location come from the package that owns the lesson's anchor paths.",
4401
+ "",
4402
+ "PARAMETERS:",
4403
+ " memory_id \u2014 the attempt/gotcha to scaffold from",
4404
+ " framework \u2014 vitest | jest | pytest | gotest (auto-detected when omitted)",
4405
+ " out_path \u2014 override the test file path (repo-relative)",
4406
+ " write \u2014 write the file (default true); false returns the content for preview",
4407
+ "",
4408
+ "RETURNS: { ok, path, run_command, propose_command, content, written, already_exists, notice }"
4409
+ ].join("\n"),
4410
+ ScaffoldTestInputSchema,
4411
+ async (input) => {
4412
+ tracker.record("scaffold_test", input.memory_id);
4413
+ return jsonResult(await scaffoldTest(input, context));
4414
+ }
4415
+ );
4289
4416
  registerTool(
4290
4417
  "ingest_findings",
4291
4418
  [
@@ -4976,6 +5103,8 @@ export {
4976
5103
  readPresumedCorrectTargets,
4977
5104
  proposeSensor,
4978
5105
  memTried,
5106
+ detectTestFrameworkForPaths,
5107
+ scaffoldTest,
4979
5108
  getBriefing,
4980
5109
  codeMapTool,
4981
5110
  getRecap,
@@ -5000,4 +5129,4 @@ export {
5000
5129
  printHaiveMcpVersion,
5001
5130
  runHaiveMcpStdio
5002
5131
  };
5003
- //# sourceMappingURL=chunk-VLRQ4MRO.js.map
5132
+ //# sourceMappingURL=chunk-UOMGIXZN.js.map