@hivelore/cli 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.
@@ -144,41 +144,53 @@ 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
+ buildProposeCommand,
153
+ loadMemoriesFromDir as loadMemoriesFromDir14,
154
+ normalizeFramework,
155
+ parseLessonFields,
156
+ pickTestFramework,
157
+ scaffoldPostIncidentTest
158
+ } from "@hivelore/core";
159
+ import { existsSync as existsSync18 } from "fs";
160
+ import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile11 } from "fs/promises";
161
+ import path8 from "path";
150
162
  import {
151
163
  draftsFromFindings,
152
164
  filterNewDrafts,
153
- loadMemoriesFromDir as loadMemoriesFromDir14,
165
+ loadMemoriesFromDir as loadMemoriesFromDir15,
154
166
  memoryFilePath as memoryFilePath3,
155
167
  parseFindings,
156
168
  serializeMemory as serializeMemory9
157
169
  } 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";
170
+ import { z as z18 } from "zod";
171
+ import { writeFile as writeFile13, mkdir as mkdir7 } from "fs/promises";
172
+ import { existsSync as existsSync20 } from "fs";
173
+ import path10 from "path";
162
174
  import {
163
175
  buildFrontmatter as buildFrontmatter3,
164
- loadMemoriesFromDir as loadMemoriesFromDir15,
176
+ loadMemoriesFromDir as loadMemoriesFromDir16,
165
177
  memoryFilePath as memoryFilePath4,
166
178
  serializeMemory as serializeMemory10
167
179
  } from "@hivelore/core";
168
- import { z as z18 } from "zod";
180
+ import { z as z19 } from "zod";
169
181
  import {
170
182
  appendUsageEvent,
171
183
  appendRuntimeJournalEntry,
172
184
  loadConfig as loadConfig2,
173
185
  writeSessionHandoff
174
186
  } 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";
187
+ import { mkdir as mkdir6, writeFile as writeFile12, rm } from "fs/promises";
188
+ import { existsSync as existsSync19 } from "fs";
189
+ import path9 from "path";
178
190
  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";
191
+ import { readFile as readFile7, writeFile as writeFile14, readdir as readdir4 } from "fs/promises";
192
+ import { existsSync as existsSync22 } from "fs";
193
+ import path12 from "path";
182
194
  import {
183
195
  allocateBudget,
184
196
  assessBootstrapState,
@@ -202,7 +214,7 @@ import {
202
214
  loadConfig as loadConfig3,
203
215
  memoryHasExcludedTag,
204
216
  hashProjectContext,
205
- loadMemoriesFromDir as loadMemoriesFromDir16,
217
+ loadMemoriesFromDir as loadMemoriesFromDir17,
206
218
  loadPreventionEvents,
207
219
  loadUsageIndex as loadUsageIndex8,
208
220
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
@@ -220,10 +232,10 @@ import {
220
232
  truncateToTokens,
221
233
  writeBriefingMarker
222
234
  } 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";
235
+ import { z as z20 } from "zod";
236
+ import { readdir as readdir3, readFile as readFile6 } from "fs/promises";
237
+ import { existsSync as existsSync21 } from "fs";
238
+ import path11 from "path";
227
239
  import {
228
240
  classifyMemoryPriority as coreClassifyPriority,
229
241
  isGlobPath,
@@ -231,17 +243,17 @@ import {
231
243
  priorityRank as corePriorityRank
232
244
  } from "@hivelore/core";
233
245
  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
246
  import { z as z21 } from "zod";
238
247
  import { existsSync as existsSync23 } from "fs";
239
248
  import { loadMemoriesFromDir as loadMemoriesFromDir18 } from "@hivelore/core";
240
249
  import { z as z22 } from "zod";
250
+ import { existsSync as existsSync24 } from "fs";
251
+ import { loadMemoriesFromDir as loadMemoriesFromDir19 } from "@hivelore/core";
241
252
  import { z as z23 } from "zod";
242
253
  import { z as z24 } from "zod";
254
+ import { z as z25 } from "zod";
243
255
  import { loadCodeMap as loadCodeMap3 } from "@hivelore/core";
244
- import { existsSync as existsSync24 } from "fs";
256
+ import { existsSync as existsSync25 } from "fs";
245
257
  import {
246
258
  addedLinesFromDiff,
247
259
  BRIDGE_TARGET_PATH,
@@ -251,7 +263,7 @@ import {
251
263
  diffHasDistinctiveOverlap,
252
264
  getUsage as getUsage7,
253
265
  isRetiredMemory as isRetiredMemory2,
254
- loadMemoriesFromDir as loadMemoriesFromDir19,
266
+ loadMemoriesFromDir as loadMemoriesFromDir20,
255
267
  loadUsageIndex as loadUsageIndex9,
256
268
  literalMatchesAnyToken as literalMatchesAnyToken3,
257
269
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths3,
@@ -260,42 +272,42 @@ import {
260
272
  sensorTargetsFromDiff,
261
273
  tokenizeQuery as tokenizeQuery3
262
274
  } from "@hivelore/core";
263
- import { z as z25 } from "zod";
264
- import { existsSync as existsSync25 } from "fs";
275
+ import { z as z26 } from "zod";
276
+ import { existsSync as existsSync26 } from "fs";
265
277
  import {
266
- loadMemoriesFromDir as loadMemoriesFromDir20,
278
+ loadMemoriesFromDir as loadMemoriesFromDir21,
267
279
  tokenizeQuery as tokenizeQuery4
268
280
  } from "@hivelore/core";
269
- import { z as z26 } from "zod";
270
- import { pathsOverlap as pathsOverlap2 } from "@hivelore/core";
271
281
  import { z as z27 } from "zod";
272
- import { existsSync as existsSync26 } from "fs";
282
+ import { pathsOverlap as pathsOverlap2 } from "@hivelore/core";
283
+ import { z as z28 } from "zod";
284
+ import { existsSync as existsSync27 } from "fs";
273
285
  import {
274
286
  findLexicalConflictPairs,
275
287
  findTopicStatusConflictPairs,
276
- loadMemoriesFromDir as loadMemoriesFromDir21,
288
+ loadMemoriesFromDir as loadMemoriesFromDir22,
277
289
  planConflictResolution
278
290
  } from "@hivelore/core";
279
- import { z as z28 } from "zod";
280
- import { resolveProjectInfo } from "@hivelore/core";
281
291
  import { z as z29 } from "zod";
282
- import { MemoryTypeSchema, suggestTopicKey } from "@hivelore/core";
292
+ import { resolveProjectInfo } from "@hivelore/core";
283
293
  import { z as z30 } from "zod";
284
- import { existsSync as existsSync27 } from "fs";
285
- import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir22 } from "@hivelore/core";
294
+ import { MemoryTypeSchema, suggestTopicKey } from "@hivelore/core";
286
295
  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
296
  import { existsSync as existsSync28 } from "fs";
297
+ import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir23 } from "@hivelore/core";
298
+ import { z as z32 } from "zod";
299
+ import { z as z33 } from "zod";
300
+ import { readFile as readFile8, readdir as readdir5 } from "fs/promises";
301
+ import { existsSync as existsSync29 } from "fs";
290
302
  import {
291
303
  assessBootstrapState as assessBootstrapState2,
292
304
  loadCodeMap as loadCodeMap4,
293
- loadMemoriesFromDir as loadMemoriesFromDir23,
305
+ loadMemoriesFromDir as loadMemoriesFromDir24,
294
306
  renderBootstrapChecklist as renderBootstrapChecklist2
295
307
  } from "@hivelore/core";
296
- import { z as z33 } from "zod";
297
308
  import { z as z34 } from "zod";
298
309
  import { z as z35 } from "zod";
310
+ import { z as z36 } from "zod";
299
311
  import { hasRecentBriefingMarker, loadConfigSync } from "@hivelore/core";
300
312
  function createContext(options = {}) {
301
313
  const env = options.env ?? process.env;
@@ -1380,6 +1392,7 @@ async function proposeSensor(input, ctx) {
1380
1392
  if (!found) {
1381
1393
  throw new Error(`No memory found with id ${input.memory_id}`);
1382
1394
  }
1395
+ 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}.` : "";
1383
1396
  if (kind !== "regex") {
1384
1397
  const verdictCmd = runCommandForValidation(input.command.trim(), ctx.paths.root, input.timeout_ms);
1385
1398
  const anchorPathsCmd = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
@@ -1414,7 +1427,7 @@ ${verdictCmd.detail}`,
1414
1427
  accepted: true,
1415
1428
  memory_id: input.memory_id,
1416
1429
  severity: input.severity,
1417
- 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}).`,
1430
+ 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,
1418
1431
  self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: null, fired_on: [] }
1419
1432
  };
1420
1433
  }
@@ -1463,6 +1476,7 @@ ${verdictCmd.detail}`,
1463
1476
  accepted: true,
1464
1477
  memory_id: input.memory_id,
1465
1478
  severity: input.severity,
1479
+ ...personalScopeNudge ? { guidance: personalScopeNudge.trim() } : {},
1466
1480
  self_check,
1467
1481
  file_path: found.filePath
1468
1482
  };
@@ -1471,7 +1485,9 @@ var MemTriedInputSchema = {
1471
1485
  what: z16.string().min(1).describe("Brief description of the approach that was tried"),
1472
1486
  why_failed: z16.string().min(1).describe("Why it failed or why it should NOT be used"),
1473
1487
  instead: z16.string().optional().describe("What to use or do instead (recommended alternative)"),
1474
- scope: z16.enum(["personal", "team", "module"]).default("personal").describe("Visibility scope"),
1488
+ scope: z16.enum(["personal", "team", "module"]).optional().describe(
1489
+ "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."
1490
+ ),
1475
1491
  module: z16.string().optional().describe("Module name (required when scope=module)"),
1476
1492
  tags: z16.array(z16.string()).default([]).describe("Tags for filtering"),
1477
1493
  paths: z16.array(z16.string()).default([]).describe("Anchor file paths this applies to"),
@@ -1495,10 +1511,11 @@ async function memTried(input, ctx) {
1495
1511
  throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
1496
1512
  }
1497
1513
  const slug = input.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 5).join("-");
1514
+ const scope = input.scope ?? (input.sensor ? "team" : "personal");
1498
1515
  const baseFm = buildFrontmatter2({
1499
1516
  type: "attempt",
1500
1517
  slug,
1501
- scope: input.scope,
1518
+ scope,
1502
1519
  module: input.module,
1503
1520
  tags: input.tags,
1504
1521
  paths: input.paths,
@@ -1546,7 +1563,7 @@ async function memTried(input, ctx) {
1546
1563
  ...verdict.reason ? { reason: verdict.reason } : {},
1547
1564
  ...verdict.guidance ? { guidance: verdict.guidance } : {}
1548
1565
  },
1549
- 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.`
1566
+ 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." : "")
1550
1567
  };
1551
1568
  }
1552
1569
  const seed = input.paths.length > 0 ? suggestSensorSeed2(body, input.paths) : null;
@@ -1566,30 +1583,158 @@ async function memTried(input, ctx) {
1566
1583
  hint
1567
1584
  };
1568
1585
  }
1586
+ var PY_SIGNALS = ["pyproject.toml", "setup.py", "pytest.ini", "requirements.txt", "tox.ini"];
1587
+ async function detectForAnchor(root, rel) {
1588
+ let dir = path7.resolve(root, rel);
1589
+ try {
1590
+ if (!statSync(dir).isDirectory()) dir = path7.dirname(dir);
1591
+ } catch {
1592
+ if (path7.extname(dir)) dir = path7.dirname(dir);
1593
+ }
1594
+ while (dir.startsWith(root)) {
1595
+ const pkgJson = path7.join(dir, "package.json");
1596
+ const hasPkg = existsSync17(pkgJson);
1597
+ const goMod = existsSync17(path7.join(dir, "go.mod"));
1598
+ const pySignal = PY_SIGNALS.some((s) => existsSync17(path7.join(dir, s)));
1599
+ if (hasPkg || goMod || pySignal) {
1600
+ let pkg = null;
1601
+ if (hasPkg) {
1602
+ try {
1603
+ pkg = JSON.parse(await readFile4(pkgJson, "utf8"));
1604
+ } catch {
1605
+ pkg = null;
1606
+ }
1607
+ }
1608
+ const baseDir = path7.relative(root, dir).split(path7.sep).join("/");
1609
+ return { framework: pickTestFramework(pkg, { goMod, pySignal }), baseDir };
1610
+ }
1611
+ const parent = path7.dirname(dir);
1612
+ if (parent === dir || dir === root) break;
1613
+ dir = parent;
1614
+ }
1615
+ return null;
1616
+ }
1617
+ async function detectTestFrameworkForPaths(root, anchorPaths) {
1618
+ const starts = anchorPaths.length > 0 ? anchorPaths : ["."];
1619
+ for (const rel of starts) {
1620
+ const found = await detectForAnchor(root, rel);
1621
+ if (found) return found;
1622
+ }
1623
+ return { framework: "vitest", baseDir: "" };
1624
+ }
1625
+ async function detectTestFrameworksForAnchors(root, anchorPaths) {
1626
+ const starts = anchorPaths.length > 0 ? anchorPaths : ["."];
1627
+ const groups = /* @__PURE__ */ new Map();
1628
+ for (const rel of starts) {
1629
+ const found = await detectForAnchor(root, rel) ?? { framework: "vitest", baseDir: "" };
1630
+ const existing = groups.get(found.baseDir);
1631
+ if (existing) existing.anchors.push(rel);
1632
+ else groups.set(found.baseDir, { ...found, anchors: [rel] });
1633
+ }
1634
+ return [...groups.values()];
1635
+ }
1636
+ var ScaffoldTestInputSchema = {
1637
+ memory_id: z17.string().min(1).describe("Id of the attempt/gotcha lesson to scaffold a post-incident test from."),
1638
+ 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."),
1639
+ out_path: z17.string().optional().describe("Override the generated test file path (repo-relative)."),
1640
+ write: z17.boolean().default(true).describe("Write the file to disk (default). false = return the content for preview without writing.")
1641
+ };
1642
+ async function scaffoldTest(input, ctx) {
1643
+ const loaded = existsSync17(ctx.paths.memoriesDir) ? await loadMemoriesFromDir14(ctx.paths.memoriesDir) : [];
1644
+ const found = loaded.find(({ memory }) => memory.frontmatter.id === input.memory_id);
1645
+ if (!found) {
1646
+ return { ok: false, error: `No memory found with id ${input.memory_id}`, memory_id: input.memory_id };
1647
+ }
1648
+ const anchorPaths = found.memory.frontmatter.anchor.paths ?? [];
1649
+ const allGroups = await detectTestFrameworksForAnchors(ctx.paths.root, anchorPaths);
1650
+ const groups = input.out_path ? allGroups.slice(0, 1) : allGroups;
1651
+ const frameworkFor = (detected) => input.framework ? normalizeFramework(input.framework) ?? detected : detected;
1652
+ const fields = parseLessonFields(found.memory.body);
1653
+ const lesson = {
1654
+ memoryId: input.memory_id,
1655
+ title: fields.title || input.memory_id,
1656
+ whyFailed: fields.whyFailed,
1657
+ instead: fields.instead,
1658
+ incident: found.memory.frontmatter.sensor?.incident,
1659
+ paths: anchorPaths
1660
+ };
1661
+ let scaffolds = groups.map(
1662
+ (g) => scaffoldPostIncidentTest(lesson, { framework: frameworkFor(g.framework), outPath: input.out_path, baseDir: g.baseDir })
1663
+ );
1664
+ let proposeCommand = scaffolds[0].proposeCommand;
1665
+ if (scaffolds.length > 1) {
1666
+ proposeCommand = buildProposeCommand(lesson, scaffolds.map((s) => s.runCommand).join(" && "));
1667
+ scaffolds = groups.map(
1668
+ (g) => scaffoldPostIncidentTest(lesson, {
1669
+ framework: frameworkFor(g.framework),
1670
+ baseDir: g.baseDir,
1671
+ proposeCommandOverride: proposeCommand
1672
+ })
1673
+ );
1674
+ }
1675
+ const results = [];
1676
+ for (const scaffold of scaffolds) {
1677
+ const abs = path7.isAbsolute(scaffold.relPath) ? scaffold.relPath : path7.resolve(ctx.paths.root, scaffold.relPath);
1678
+ let written = false;
1679
+ let alreadyExists = false;
1680
+ if (input.write) {
1681
+ if (existsSync17(abs)) {
1682
+ alreadyExists = true;
1683
+ } else {
1684
+ await mkdir4(path7.dirname(abs), { recursive: true });
1685
+ await writeFile10(abs, scaffold.content, "utf8");
1686
+ written = true;
1687
+ }
1688
+ }
1689
+ results.push({
1690
+ framework: scaffold.framework,
1691
+ path: scaffold.relPath,
1692
+ run_command: scaffold.runCommand,
1693
+ content: scaffold.content,
1694
+ written,
1695
+ already_exists: alreadyExists
1696
+ });
1697
+ }
1698
+ const first = results[0];
1699
+ const anyExisting = results.some((r) => r.already_exists);
1700
+ return {
1701
+ ok: true,
1702
+ memory_id: input.memory_id,
1703
+ framework: first.framework,
1704
+ path: first.path,
1705
+ run_command: first.run_command,
1706
+ propose_command: proposeCommand,
1707
+ content: first.content,
1708
+ written: first.written,
1709
+ already_exists: first.already_exists,
1710
+ ...results.length > 1 ? { scaffolds: results } : {},
1711
+ 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.")
1712
+ };
1713
+ }
1569
1714
  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")
1715
+ format: z18.enum(["sarif", "sonar"]).describe("Report format: 'sarif' (ESLint/Semgrep/CodeQL) or 'sonar' (SonarQube issues JSON)"),
1716
+ report_path: z18.string().optional().describe("Project-relative path to the findings JSON file. Provide this OR `report`."),
1717
+ report: z18.string().optional().describe("Inline findings JSON content. Provide this OR `report_path`."),
1718
+ type: z18.enum(["gotcha", "convention"]).default("gotcha").describe("Memory type for the created drafts"),
1719
+ scope: z18.enum(["personal", "team", "module"]).default("team").describe("Visibility scope for the created memories"),
1720
+ module: z18.string().optional().describe("Module name (required when scope=module)"),
1721
+ min_severity: z18.enum(["info", "minor", "major", "critical", "blocker"]).optional().describe("Ignore findings below this severity"),
1722
+ include_stylistic: z18.boolean().optional().describe("Also ingest auto-fixable stylistic rules (semi/quotes/prefer-const\u2026); off by default as low-value noise"),
1723
+ limit: z18.number().int().positive().optional().describe("Cap the number of memories created"),
1724
+ author: z18.string().optional().describe("Author handle or email"),
1725
+ dry_run: z18.boolean().default(false).describe("When true, return the drafts that WOULD be created without writing them")
1581
1726
  };
1582
1727
  async function ingestFindings(input, ctx) {
1583
- if (!existsSync17(ctx.paths.haiveDir)) {
1728
+ if (!existsSync18(ctx.paths.haiveDir)) {
1584
1729
  throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
1585
1730
  }
1586
1731
  let raw;
1587
1732
  if (input.report && input.report.trim()) {
1588
1733
  raw = input.report;
1589
1734
  } 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");
1735
+ const file = path8.resolve(ctx.paths.root, input.report_path);
1736
+ if (!existsSync18(file)) throw new Error(`Report file not found: ${file}`);
1737
+ raw = await readFile5(file, "utf8");
1593
1738
  } else {
1594
1739
  throw new Error("Provide either `report_path` or `report`.");
1595
1740
  }
@@ -1603,7 +1748,7 @@ async function ingestFindings(input, ctx) {
1603
1748
  ...input.include_stylistic ? { includeStylistic: true } : {},
1604
1749
  ...input.limit ? { limit: input.limit } : {}
1605
1750
  });
1606
- const existing = existsSync17(ctx.paths.memoriesDir) ? await loadMemoriesFromDir14(ctx.paths.memoriesDir) : [];
1751
+ const existing = existsSync18(ctx.paths.memoriesDir) ? await loadMemoriesFromDir15(ctx.paths.memoriesDir) : [];
1607
1752
  const existingTopics = new Set(
1608
1753
  existing.map(({ memory }) => memory.frontmatter.topic).filter((t) => Boolean(t))
1609
1754
  );
@@ -1641,12 +1786,12 @@ async function writeDraft(ctx, draft) {
1641
1786
  draft.frontmatter.id,
1642
1787
  draft.frontmatter.module
1643
1788
  );
1644
- await mkdir4(path7.dirname(file), { recursive: true });
1645
- await writeFile10(file, serializeMemory9({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
1789
+ await mkdir5(path8.dirname(file), { recursive: true });
1790
+ await writeFile11(file, serializeMemory9({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
1646
1791
  return file;
1647
1792
  }
1648
1793
  function pendingDistillPath(ctx) {
1649
- return path8.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
1794
+ return path9.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
1650
1795
  }
1651
1796
  var SessionTracker = class {
1652
1797
  events = [];
@@ -1750,7 +1895,7 @@ var SessionTracker = class {
1750
1895
  (e) => e.tool === "mem_session_end" && !e.summary?.startsWith("Auto-captured")
1751
1896
  );
1752
1897
  const isSubstantialSession = totalCalls >= 3 || writingTools.length > 0;
1753
- if (!ranPostTask && isSubstantialSession && existsSync18(this.ctx.paths.haiveDir)) {
1898
+ if (!ranPostTask && isSubstantialSession && existsSync19(this.ctx.paths.haiveDir)) {
1754
1899
  try {
1755
1900
  const memoriesSaved = writingTools.map((e) => e.summary ?? "").filter(Boolean).slice(0, 20);
1756
1901
  const payload = {
@@ -1763,9 +1908,9 @@ var SessionTracker = class {
1763
1908
  ...gitDiff ? { git_diff: gitDiff } : {},
1764
1909
  ...recapId ? { recap_id: recapId } : {}
1765
1910
  };
1766
- const cacheDir = path8.join(this.ctx.paths.haiveDir, ".cache");
1767
- await mkdir5(cacheDir, { recursive: true });
1768
- await writeFile11(
1911
+ const cacheDir = path9.join(this.ctx.paths.haiveDir, ".cache");
1912
+ await mkdir6(cacheDir, { recursive: true });
1913
+ await writeFile12(
1769
1914
  pendingDistillPath(this.ctx),
1770
1915
  JSON.stringify(payload, null, 2) + "\n",
1771
1916
  "utf8"
@@ -1784,7 +1929,7 @@ var SessionTracker = class {
1784
1929
  };
1785
1930
  async function clearPendingDistill(ctx) {
1786
1931
  const p = pendingDistillPath(ctx);
1787
- if (existsSync18(p)) {
1932
+ if (existsSync19(p)) {
1788
1933
  try {
1789
1934
  await rm(p);
1790
1935
  } catch {
@@ -1799,15 +1944,15 @@ function summarizeTools(events) {
1799
1944
  return [...counts.entries()].sort((a, b) => b[1] - a[1]).map(([t, n]) => `${t} \xD7${n}`).join(", ");
1800
1945
  }
1801
1946
  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(
1947
+ goal: z19.string().min(1).describe("What you were trying to accomplish this session (1\u20132 sentences)"),
1948
+ accomplished: z19.string().describe("What was actually done \u2014 bullet list recommended"),
1949
+ discoveries: z19.string().default("").describe(
1805
1950
  "Any bugs, inconsistencies, surprises, or missing knowledge found during this session. Empty if nothing surprising was found."
1806
1951
  ),
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)")
1952
+ files_touched: z19.array(z19.string()).default([]).describe("Key files that were read or modified \u2014 used as anchor paths"),
1953
+ next_steps: z19.string().default("").describe("What should happen next (for the next session or a teammate)"),
1954
+ scope: z19.enum(["personal", "team", "module"]).default("personal").describe("Visibility: personal = private to you, team = shared with the team"),
1955
+ module: z19.string().optional().describe("Module name (required when scope=module)")
1811
1956
  };
1812
1957
  function recapTopic(scope, module) {
1813
1958
  return module ? `session-recap-${scope}-${module}` : `session-recap-${scope}`;
@@ -1837,23 +1982,23 @@ ${input.next_steps}`);
1837
1982
  return lines.join("\n");
1838
1983
  }
1839
1984
  async function memSessionEnd(input, ctx) {
1840
- if (!existsSync19(ctx.paths.haiveDir)) {
1985
+ if (!existsSync20(ctx.paths.haiveDir)) {
1841
1986
  throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
1842
1987
  }
1843
1988
  const body = buildBody(input);
1844
1989
  const topic = recapTopic(input.scope, input.module);
1845
1990
  const normalizedFiles = input.files_touched.map((p) => {
1846
- if (!p || !path9.isAbsolute(p)) return p;
1847
- const rel = path9.relative(ctx.paths.root, p);
1991
+ if (!p || !path10.isAbsolute(p)) return p;
1992
+ const rel = path10.relative(ctx.paths.root, p);
1848
1993
  return rel.startsWith("..") ? p : rel;
1849
1994
  });
1850
1995
  const invalidPaths = normalizedFiles.filter(
1851
- (p) => !existsSync19(path9.resolve(ctx.paths.root, p))
1996
+ (p) => !existsSync20(path10.resolve(ctx.paths.root, p))
1852
1997
  );
1853
1998
  if (invalidPaths.length > 0) {
1854
1999
  console.warn(`[haive] session end: anchor path(s) not found: ${invalidPaths.join(", ")}`);
1855
2000
  }
1856
- const existing = existsSync19(ctx.paths.memoriesDir) ? await loadMemoriesFromDir15(ctx.paths.memoriesDir) : [];
2001
+ const existing = existsSync20(ctx.paths.memoriesDir) ? await loadMemoriesFromDir16(ctx.paths.memoriesDir) : [];
1857
2002
  const topicMatch = existing.find(
1858
2003
  ({ memory }) => memory.frontmatter.topic === topic && memory.frontmatter.scope === input.scope && (!input.module || memory.frontmatter.module === input.module)
1859
2004
  );
@@ -1869,7 +2014,7 @@ async function memSessionEnd(input, ctx) {
1869
2014
  paths: normalizedFiles.length ? normalizedFiles : fm.anchor.paths
1870
2015
  }
1871
2016
  };
1872
- await writeFile12(
2017
+ await writeFile13(
1873
2018
  topicMatch.filePath,
1874
2019
  serializeMemory10({ frontmatter: newFrontmatter, body }),
1875
2020
  "utf8"
@@ -1899,8 +2044,8 @@ async function memSessionEnd(input, ctx) {
1899
2044
  frontmatter.id,
1900
2045
  frontmatter.module
1901
2046
  );
1902
- await mkdir6(path9.dirname(file), { recursive: true });
1903
- await writeFile12(file, serializeMemory10({ frontmatter, body }), "utf8");
2047
+ await mkdir7(path10.dirname(file), { recursive: true });
2048
+ await writeFile13(file, serializeMemory10({ frontmatter, body }), "utf8");
1904
2049
  await clearPendingDistill(ctx);
1905
2050
  return {
1906
2051
  id: frontmatter.id,
@@ -2036,53 +2181,53 @@ async function trySemanticHits(ctx, task, limit) {
2036
2181
  }
2037
2182
  async function loadModuleContexts2(ctx, modules) {
2038
2183
  if (modules.length === 0) return [];
2039
- if (!existsSync20(ctx.paths.modulesContextDir)) return [];
2184
+ if (!existsSync21(ctx.paths.modulesContextDir)) return [];
2040
2185
  const available = new Set(
2041
2186
  (await readdir3(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
2042
2187
  );
2043
2188
  const out = [];
2044
2189
  for (const m of modules) {
2045
2190
  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") });
2191
+ const file = path11.join(ctx.paths.modulesContextDir, m, "context.md");
2192
+ if (existsSync21(file)) {
2193
+ out.push({ name: m, content: await readFile6(file, "utf8") });
2049
2194
  }
2050
2195
  }
2051
2196
  return out;
2052
2197
  }
2053
2198
  var GetBriefingInputSchema = {
2054
- task: z19.string().optional().describe(
2199
+ task: z20.string().optional().describe(
2055
2200
  "What you are about to do, in 1\u20132 sentences. Used to rank relevant memories semantically."
2056
2201
  ),
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(
2202
+ files: z20.array(z20.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
2203
+ max_tokens: z20.number().int().positive().default(8e3).describe(
2059
2204
  "Approximate token budget for the entire briefing. Each section is allocated a share and truncated to fit."
2060
2205
  ),
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(
2206
+ max_memories: z20.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
2207
+ include_project_context: z20.boolean().default(true),
2208
+ dedupe_project_context: z20.boolean().optional().describe(
2064
2209
  "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
2210
  ),
2066
- include_module_contexts: z19.boolean().default(true),
2067
- semantic: z19.boolean().default(true).describe(
2211
+ include_module_contexts: z20.boolean().default(true),
2212
+ semantic: z20.boolean().default(true).describe(
2068
2213
  "Use semantic ranking when a task is provided (requires `hivelore embeddings index`)."
2069
2214
  ),
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(
2215
+ include_stale: z20.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
2216
+ track: z20.boolean().default(true).describe("Increment read_count on returned memories"),
2217
+ format: z20.enum(["full", "compact", "actions"]).default("full").describe(
2073
2218
  "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
2219
  ),
2075
- symbols: z19.array(z19.string()).default([]).describe(
2220
+ symbols: z20.array(z20.string()).default([]).describe(
2076
2221
  "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
2222
  ),
2078
- min_semantic_score: z19.number().min(0).max(1).default(0).describe(
2223
+ min_semantic_score: z20.number().min(0).max(1).default(0).describe(
2079
2224
  "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
2225
  ),
2081
- budget_preset: z19.enum(["quick", "balanced", "deep"]).optional().describe(
2226
+ budget_preset: z20.enum(["quick", "balanced", "deep"]).optional().describe(
2082
2227
  "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
2228
  )
2084
2229
  };
2085
- var GetBriefingZod = z19.object(GetBriefingInputSchema);
2230
+ var GetBriefingZod = z20.object(GetBriefingInputSchema);
2086
2231
  async function getBriefing(input, ctx) {
2087
2232
  const resolvedBudget = resolveBriefingBudget(input.budget_preset, {
2088
2233
  max_tokens: input.max_tokens,
@@ -2098,8 +2243,8 @@ async function getBriefing(input, ctx) {
2098
2243
  let usage = { version: 1, updated_at: "", by_id: {} };
2099
2244
  let byId = /* @__PURE__ */ new Map();
2100
2245
  let lastSession;
2101
- if (existsSync21(ctx.paths.memoriesDir)) {
2102
- const allLoaded = await loadMemoriesFromDir16(ctx.paths.memoriesDir);
2246
+ if (existsSync22(ctx.paths.memoriesDir)) {
2247
+ const allLoaded = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
2103
2248
  const recaps = allLoaded.filter(({ memory }) => memory.frontmatter.type === "session_recap").sort(
2104
2249
  (a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
2105
2250
  );
@@ -2273,7 +2418,7 @@ async function getBriefing(input, ctx) {
2273
2418
  if (!isAutoPromoteEligible(loaded.memory.frontmatter, u, rule)) continue;
2274
2419
  const newFm = { ...loaded.memory.frontmatter, status: "validated", validated_by: "auto" };
2275
2420
  try {
2276
- await writeFile13(loaded.filePath, serializeMemory11({ frontmatter: newFm, body: loaded.memory.body }), "utf8");
2421
+ await writeFile14(loaded.filePath, serializeMemory11({ frontmatter: newFm, body: loaded.memory.body }), "utf8");
2277
2422
  m.status = "validated";
2278
2423
  m.confidence = "trusted";
2279
2424
  } catch {
@@ -2281,7 +2426,7 @@ async function getBriefing(input, ctx) {
2281
2426
  }
2282
2427
  }
2283
2428
  }
2284
- let projectContextRaw = input.include_project_context && existsSync21(ctx.paths.projectContext) ? await readFile6(ctx.paths.projectContext, "utf8") : "";
2429
+ let projectContextRaw = input.include_project_context && existsSync22(ctx.paths.projectContext) ? await readFile7(ctx.paths.projectContext, "utf8") : "";
2285
2430
  let contextOmittedRecent = false;
2286
2431
  if (projectContextRaw && input.dedupe_project_context !== false) {
2287
2432
  const ctxHash = hashProjectContext(projectContextRaw);
@@ -2296,7 +2441,7 @@ async function getBriefing(input, ctx) {
2296
2441
  const setupWarnings = [];
2297
2442
  let autoContextGenerated = false;
2298
2443
  let projectContext = isTemplateContext ? "" : projectContextRaw;
2299
- if ((isTemplateContext || !existsSync21(ctx.paths.projectContext)) && input.include_project_context) {
2444
+ if ((isTemplateContext || !existsSync22(ctx.paths.projectContext)) && input.include_project_context) {
2300
2445
  const haiveConfig = await loadConfig3(ctx.paths);
2301
2446
  if (haiveConfig.autoContext) {
2302
2447
  const codeMap = await loadCodeMap(ctx.paths);
@@ -2470,8 +2615,8 @@ ${m.content}`).join("\n\n---\n\n"),
2470
2615
  actionRequired.push(extractActionItem(m.id, loaded.memory.body));
2471
2616
  }
2472
2617
  }
2473
- if (existsSync21(ctx.paths.memoriesDir)) {
2474
- const allMems = await loadMemoriesFromDir16(ctx.paths.memoriesDir);
2618
+ if (existsSync22(ctx.paths.memoriesDir)) {
2619
+ const allMems = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
2475
2620
  for (const { memory } of allMems) {
2476
2621
  const fm = memory.frontmatter;
2477
2622
  if (!fm.requires_human_approval) continue;
@@ -2481,9 +2626,9 @@ ${m.content}`).join("\n\n---\n\n"),
2481
2626
  }
2482
2627
  }
2483
2628
  const pendingDistillFile = pendingDistillPath(ctx);
2484
- if (existsSync21(pendingDistillFile)) {
2629
+ if (existsSync22(pendingDistillFile)) {
2485
2630
  try {
2486
- const raw = await readFile6(pendingDistillFile, "utf8");
2631
+ const raw = await readFile7(pendingDistillFile, "utf8");
2487
2632
  const pd = JSON.parse(raw);
2488
2633
  const ageMs = Date.now() - new Date(pd.session_end).getTime();
2489
2634
  const SEVEN_DAYS = 7 * 24 * 60 * 60 * 1e3;
@@ -2510,7 +2655,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
2510
2655
  }
2511
2656
  }
2512
2657
  const memoriesEmpty = outputMemories.length === 0;
2513
- const hasMemoriesDir = existsSync21(ctx.paths.memoriesDir);
2658
+ const hasMemoriesDir = existsSync22(ctx.paths.memoriesDir);
2514
2659
  const isColdStart = isTemplateContext && memoriesEmpty && !lastSession && !autoContextGenerated;
2515
2660
  const hasUnguessableSignal = outputMemories.some(
2516
2661
  (m) => (m.priority === "must_read" || m.priority === "useful") && specificityScore(m.body) >= GUESSABLE_THRESHOLD
@@ -2525,10 +2670,10 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
2525
2670
  try {
2526
2671
  let pcRaw = "";
2527
2672
  try {
2528
- pcRaw = await readFile6(ctx.paths.projectContext, "utf8");
2673
+ pcRaw = await readFile7(ctx.paths.projectContext, "utf8");
2529
2674
  } catch {
2530
2675
  }
2531
- const allForBootstrap = existsSync21(ctx.paths.memoriesDir) ? await loadMemoriesFromDir16(ctx.paths.memoriesDir) : [];
2676
+ const allForBootstrap = existsSync22(ctx.paths.memoriesDir) ? await loadMemoriesFromDir17(ctx.paths.memoriesDir) : [];
2532
2677
  const cmForBootstrap = await loadCodeMap(ctx.paths);
2533
2678
  let existingModules = [];
2534
2679
  try {
@@ -2592,7 +2737,7 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
2592
2737
  "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
2738
  );
2594
2739
  }
2595
- if (outputMemories.length > 0 && existsSync21(ctx.paths.haiveDir)) {
2740
+ if (outputMemories.length > 0 && existsSync22(ctx.paths.haiveDir)) {
2596
2741
  const proof = briefingProofLine(await loadPreventionEvents(ctx.paths));
2597
2742
  if (proof) hints.push(proof);
2598
2743
  }
@@ -2606,7 +2751,7 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
2606
2751
  adaptiveTrim
2607
2752
  });
2608
2753
  const breadcrumbTokens = breadcrumbs ? estimateTokens([...breadcrumbs.start_here, ...breadcrumbs.drill_down, breadcrumbs.note ?? ""].join("\n")) : 0;
2609
- if (existsSync21(ctx.paths.haiveDir)) {
2754
+ if (existsSync22(ctx.paths.haiveDir)) {
2610
2755
  await writeBriefingMarker(ctx.paths, {
2611
2756
  sessionId: process.env.HAIVE_SESSION_ID,
2612
2757
  ...input.task ? { task: input.task } : {},
@@ -2662,10 +2807,10 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
2662
2807
  };
2663
2808
  }
2664
2809
  async function detectRunCommands(root) {
2665
- const pkgPath = path11.join(root, "package.json");
2666
- if (!existsSync21(pkgPath)) return null;
2810
+ const pkgPath = path12.join(root, "package.json");
2811
+ if (!existsSync22(pkgPath)) return null;
2667
2812
  try {
2668
- const pkg = JSON.parse(await readFile6(pkgPath, "utf8"));
2813
+ const pkg = JSON.parse(await readFile7(pkgPath, "utf8"));
2669
2814
  const scripts = pkg.scripts ?? {};
2670
2815
  const order = ["test", "build", "lint", "typecheck", "type-check", "dev", "start"];
2671
2816
  const lines = order.filter((name) => typeof scripts[name] === "string" && scripts[name].trim() !== "").map((name) => `- \`${name}\`: \`${scripts[name]}\``);
@@ -2730,20 +2875,20 @@ function oneLine(value) {
2730
2875
  return value.replace(/\s+/g, " ").replace(/"/g, '\\"').trim().slice(0, 120);
2731
2876
  }
2732
2877
  function serverVersion() {
2733
- return true ? "0.37.0" : "dev";
2878
+ return true ? "0.39.0" : "dev";
2734
2879
  }
2735
2880
  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(
2881
+ file: z21.string().optional().describe("Filter to files whose path contains this substring"),
2882
+ symbol: z21.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
2883
+ paths: z21.array(z21.string()).default([]).describe(
2739
2884
  "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
2885
  ),
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(
2886
+ max_files: z21.number().int().positive().default(40).describe("Cap on returned files (hard limit, applied after token budget)"),
2887
+ max_tokens: z21.number().int().positive().optional().describe(
2743
2888
  "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
2889
  )
2745
2890
  };
2746
- var CodeMapInputZod = z20.object(CodeMapInputSchema);
2891
+ var CodeMapInputZod = z21.object(CodeMapInputSchema);
2747
2892
  async function codeMapTool(input, ctx) {
2748
2893
  const map = await loadCodeMap2(ctx.paths);
2749
2894
  if (!map) {
@@ -2811,14 +2956,14 @@ function estimateFileEntryTokens(f) {
2811
2956
  return estimateTokens2(f.path) + estimateTokens2(f.entry.summary ?? "") + exportsCost + 4;
2812
2957
  }
2813
2958
  var MemDiffInputSchema = {
2814
- id_a: z21.string().min(1).describe("First memory id"),
2815
- id_b: z21.string().min(1).describe("Second memory id")
2959
+ id_a: z22.string().min(1).describe("First memory id"),
2960
+ id_b: z22.string().min(1).describe("Second memory id")
2816
2961
  };
2817
2962
  async function memDiff(input, ctx) {
2818
- if (!existsSync22(ctx.paths.memoriesDir)) {
2963
+ if (!existsSync23(ctx.paths.memoriesDir)) {
2819
2964
  throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
2820
2965
  }
2821
- const all = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
2966
+ const all = await loadMemoriesFromDir18(ctx.paths.memoriesDir);
2822
2967
  const foundA = all.find((m) => m.memory.frontmatter.id === input.id_a);
2823
2968
  const foundB = all.find((m) => m.memory.frontmatter.id === input.id_b);
2824
2969
  if (!foundA) throw new Error(`No memory with id "${input.id_a}".`);
@@ -2851,15 +2996,15 @@ async function memDiff(input, ctx) {
2851
2996
  };
2852
2997
  }
2853
2998
  var GetRecapInputSchema = {
2854
- scope: z22.enum(["personal", "team", "any"]).default("any").describe(
2999
+ scope: z23.enum(["personal", "team", "any"]).default("any").describe(
2855
3000
  "Limit to a specific scope's recap. Default 'any' returns the most recent recap across both personal and team scopes."
2856
3001
  )
2857
3002
  };
2858
3003
  async function getRecap(input, ctx) {
2859
- if (!existsSync23(ctx.paths.memoriesDir)) {
3004
+ if (!existsSync24(ctx.paths.memoriesDir)) {
2860
3005
  return { recap: null, notice: "No .ai/memories directory \u2014 haive not initialized here." };
2861
3006
  }
2862
- const all = await loadMemoriesFromDir18(ctx.paths.memoriesDir);
3007
+ const all = await loadMemoriesFromDir19(ctx.paths.memoriesDir);
2863
3008
  const recaps = all.filter(({ memory }) => memory.frontmatter.type === "session_recap").filter(({ memory }) => input.scope === "any" || memory.frontmatter.scope === input.scope).sort(
2864
3009
  (a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
2865
3010
  );
@@ -2882,11 +3027,11 @@ async function getRecap(input, ctx) {
2882
3027
  };
2883
3028
  }
2884
3029
  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.")
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.")
2890
3035
  };
2891
3036
  async function memRelevantTo(input, ctx) {
2892
3037
  const briefingInput = {
@@ -2915,11 +3060,11 @@ async function memRelevantTo(input, ctx) {
2915
3060
  return out;
2916
3061
  }
2917
3062
  var CodeSearchInputSchema = {
2918
- query: z24.string().min(1).describe(
3063
+ query: z25.string().min(1).describe(
2919
3064
  "Natural-language description of what you are looking for in the codebase (e.g. 'function that hashes passwords', 'JWT signing logic', 'route registration')."
2920
3065
  ),
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(
3066
+ k: z25.number().int().positive().max(50).default(5).describe("Number of top hits to return."),
3067
+ min_score: z25.number().min(0).max(1).default(0.2).describe(
2923
3068
  "Minimum cosine similarity. Hits below this threshold are dropped to avoid noise. Try 0.3+ for stricter matching."
2924
3069
  )
2925
3070
  };
@@ -2961,17 +3106,17 @@ async function codeSearch(input, ctx) {
2961
3106
  };
2962
3107
  }
2963
3108
  var AntiPatternsCheckInputSchema = {
2964
- diff: z25.string().optional().describe(
3109
+ diff: z26.string().optional().describe(
2965
3110
  "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
3111
  ),
2967
- paths: z25.array(z25.string()).default([]).describe(
3112
+ paths: z26.array(z26.string()).default([]).describe(
2968
3113
  "File paths affected by the change. Memories anchored to any of these paths are surfaced regardless of the diff content."
2969
3114
  ),
2970
- limit: z25.number().int().positive().max(20).default(8).describe("Cap on returned warnings."),
2971
- semantic: z25.boolean().default(true).describe(
3115
+ limit: z26.number().int().positive().max(20).default(8).describe("Cap on returned warnings."),
3116
+ semantic: z26.boolean().default(true).describe(
2972
3117
  "When true, also use semantic search (requires @hivelore/embeddings + memory index) to find related anti-patterns."
2973
3118
  ),
2974
- min_semantic_score: z25.number().min(0).max(1).default(0.45).describe(
3119
+ min_semantic_score: z26.number().min(0).max(1).default(0.45).describe(
2975
3120
  "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
3121
  )
2977
3122
  };
@@ -3055,10 +3200,10 @@ async function antiPatternsCheck(input, ctx) {
3055
3200
  notice: "Nothing to check \u2014 provide either `diff` text or `paths`."
3056
3201
  };
3057
3202
  }
3058
- if (!existsSync24(ctx.paths.memoriesDir)) {
3203
+ if (!existsSync25(ctx.paths.memoriesDir)) {
3059
3204
  return { scanned: 0, warnings: [], notice: "No .ai/memories directory \u2014 nothing to check against." };
3060
3205
  }
3061
- const all = await loadMemoriesFromDir19(ctx.paths.memoriesDir);
3206
+ const all = await loadMemoriesFromDir20(ctx.paths.memoriesDir);
3062
3207
  const minSemanticScore = input.min_semantic_score ?? 0.45;
3063
3208
  const negative = all.filter(({ memory }) => {
3064
3209
  const t = memory.frontmatter.type;
@@ -3173,12 +3318,12 @@ async function antiPatternsCheck(input, ctx) {
3173
3318
  };
3174
3319
  }
3175
3320
  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(
3321
+ since_days: z27.number().int().positive().default(30).describe("Only consider memories created in the last N days."),
3322
+ min_cluster: z27.number().int().min(2).default(3).describe("Minimum cluster size to surface."),
3323
+ type_filter: z27.enum(["gotcha", "attempt", "all"]).default("gotcha").describe(
3179
3324
  "Memory type to scan. 'gotcha' targets observe-style discoveries that recur, 'attempt' surfaces failed approaches that repeat, 'all' considers both."
3180
3325
  ),
3181
- scope: z26.enum(["personal", "team", "module", "any"]).default("any").describe("Restrict to a specific scope.")
3326
+ scope: z27.enum(["personal", "team", "module", "any"]).default("any").describe("Restrict to a specific scope.")
3182
3327
  };
3183
3328
  var MS_PER_DAY = 24 * 60 * 60 * 1e3;
3184
3329
  var STOP_WORDS = /* @__PURE__ */ new Set([
@@ -3218,11 +3363,11 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
3218
3363
  "error"
3219
3364
  ]);
3220
3365
  async function memDistill(input, ctx) {
3221
- if (!existsSync25(ctx.paths.memoriesDir)) {
3366
+ if (!existsSync26(ctx.paths.memoriesDir)) {
3222
3367
  return { scanned: 0, singletons: 0, clusters: [], notice: "No .ai/memories directory." };
3223
3368
  }
3224
3369
  const cutoff = Date.now() - input.since_days * MS_PER_DAY;
3225
- const all = await loadMemoriesFromDir20(ctx.paths.memoriesDir);
3370
+ const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
3226
3371
  const candidates = all.filter(({ memory }) => {
3227
3372
  const fm = memory.frontmatter;
3228
3373
  if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
@@ -3325,15 +3470,15 @@ function firstHeading(body) {
3325
3470
  return void 0;
3326
3471
  }
3327
3472
  var PreCommitCheckInputSchema = {
3328
- diff: z27.string().optional().describe(
3473
+ diff: z28.string().optional().describe(
3329
3474
  "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
3475
  ),
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(
3476
+ paths: z28.array(z28.string()).default([]).describe("Project-relative paths affected by the change. At least one of `diff` or `paths` should be provided."),
3477
+ block_on: z28.enum(["any", "high-confidence", "never"]).default("high-confidence").describe(
3333
3478
  "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
3479
  ),
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(
3480
+ semantic: z28.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index)."),
3481
+ anchored_blocks: z28.boolean().default(false).describe(
3337
3482
  "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
3483
  )
3339
3484
  };
@@ -3650,17 +3795,17 @@ function suggestResolution(byId, idA, idB) {
3650
3795
  };
3651
3796
  }
3652
3797
  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(
3798
+ since_days: z29.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
3799
+ types: z29.array(z29.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
3800
+ min_jaccard: z29.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
3801
+ max_pairs: z29.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
3802
+ max_scan: z29.number().int().positive().max(2e3).default(500).describe("Maximum memories sampled for O(n\xB2) scan \u2014 excess dropped after chronological sort."),
3803
+ max_topic_pairs: z29.number().int().positive().max(100).default(20).describe(
3659
3804
  "Cap for extra signal: memories sharing the same topic with validated vs rejected status."
3660
3805
  )
3661
3806
  };
3662
3807
  async function memConflictCandidates(input, ctx) {
3663
- if (!existsSync26(ctx.paths.memoriesDir)) {
3808
+ if (!existsSync27(ctx.paths.memoriesDir)) {
3664
3809
  return {
3665
3810
  pairs: [],
3666
3811
  topic_status_pairs: [],
@@ -3669,7 +3814,7 @@ async function memConflictCandidates(input, ctx) {
3669
3814
  notice: "No .ai/memories directory."
3670
3815
  };
3671
3816
  }
3672
- const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
3817
+ const all = await loadMemoriesFromDir22(ctx.paths.memoriesDir);
3673
3818
  const byId = new Map(all.map((m) => [m.memory.frontmatter.id, m]));
3674
3819
  const { pairs, scanned, truncated } = findLexicalConflictPairs(all, {
3675
3820
  sinceDays: input.since_days,
@@ -3697,7 +3842,7 @@ async function memConflictCandidates(input, ctx) {
3697
3842
  };
3698
3843
  }
3699
3844
  var MemResolveProjectInputSchema = {
3700
- cwd: z29.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
3845
+ cwd: z30.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
3701
3846
  };
3702
3847
  async function memResolveProject(input, _ctx) {
3703
3848
  void _ctx;
@@ -3710,7 +3855,7 @@ async function memResolveProject(input, _ctx) {
3710
3855
  }
3711
3856
  var MemSuggestTopicInputSchema = {
3712
3857
  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")
3858
+ title: z31.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
3714
3859
  };
3715
3860
  async function memSuggestTopic(input, _ctx) {
3716
3861
  void _ctx;
@@ -3718,15 +3863,15 @@ async function memSuggestTopic(input, _ctx) {
3718
3863
  return { topic_key: suggestion.topic_key, family: suggestion.family, type: input.type };
3719
3864
  }
3720
3865
  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")
3866
+ memory_id: z32.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
3867
+ topic: z32.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
3868
+ limit: z32.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
3724
3869
  };
3725
3870
  async function memTimeline(input, ctx) {
3726
- if (!existsSync27(ctx.paths.memoriesDir)) {
3871
+ if (!existsSync28(ctx.paths.memoriesDir)) {
3727
3872
  return { entries: [], total: 0, notice: "No .ai/memories directory." };
3728
3873
  }
3729
- const all = await loadMemoriesFromDir22(ctx.paths.memoriesDir);
3874
+ const all = await loadMemoriesFromDir23(ctx.paths.memoriesDir);
3730
3875
  const { entries, notice } = collectTimelineEntries(all, {
3731
3876
  memoryId: input.memory_id,
3732
3877
  topic: input.topic,
@@ -3735,10 +3880,10 @@ async function memTimeline(input, ctx) {
3735
3880
  return { entries, total: entries.length, notice };
3736
3881
  }
3737
3882
  var BootstrapProjectArgsSchema = {
3738
- module: z32.string().optional().describe(
3883
+ module: z33.string().optional().describe(
3739
3884
  "Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
3740
3885
  ),
3741
- focus: z32.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
3886
+ focus: z33.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
3742
3887
  };
3743
3888
  var ROOT_TEMPLATE = `# Project context
3744
3889
 
@@ -3819,15 +3964,15 @@ ${template}\`\`\`
3819
3964
  };
3820
3965
  }
3821
3966
  var BootstrapRepoArgsSchema = {
3822
- focus: z33.string().optional().describe("Optional area to emphasize first (e.g. 'payments', 'auth').")
3967
+ focus: z34.string().optional().describe("Optional area to emphasize first (e.g. 'payments', 'auth').")
3823
3968
  };
3824
3969
  async function currentAssessment(ctx) {
3825
3970
  let projectContextRaw = "";
3826
3971
  try {
3827
- projectContextRaw = await readFile7(ctx.paths.projectContext, "utf8");
3972
+ projectContextRaw = await readFile8(ctx.paths.projectContext, "utf8");
3828
3973
  } catch {
3829
3974
  }
3830
- const memories = existsSync28(ctx.paths.memoriesDir) ? await loadMemoriesFromDir23(ctx.paths.memoriesDir) : [];
3975
+ const memories = existsSync29(ctx.paths.memoriesDir) ? await loadMemoriesFromDir24(ctx.paths.memoriesDir) : [];
3831
3976
  const codeMap = await loadCodeMap4(ctx.paths);
3832
3977
  let existingModules = [];
3833
3978
  try {
@@ -3894,8 +4039,8 @@ Main code areas detected: ${areas}
3894
4039
  };
3895
4040
  }
3896
4041
  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")
4042
+ task_summary: z35.string().optional().describe("One sentence describing what you just did"),
4043
+ files_touched: z35.array(z35.string()).optional().describe("Files you created or modified during the task")
3899
4044
  };
3900
4045
  function postTaskPrompt(args, ctx) {
3901
4046
  const taskLine = args.task_summary ? `
@@ -3991,10 +4136,10 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
3991
4136
  };
3992
4137
  }
3993
4138
  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")
4139
+ content: z36.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
4140
+ source: z36.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
4141
+ scope: z36.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
4142
+ dry_run: z36.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
3998
4143
  };
3999
4144
  function importDocsPrompt(args, ctx) {
4000
4145
  const sourceLine = args.source ? `
@@ -4057,7 +4202,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
4057
4202
  };
4058
4203
  }
4059
4204
  var SERVER_NAME = "hivelore";
4060
- var SERVER_VERSION = "0.37.0";
4205
+ var SERVER_VERSION = "0.39.0";
4061
4206
  function jsonResult(data) {
4062
4207
  return {
4063
4208
  content: [
@@ -4080,7 +4225,8 @@ var ENFORCEMENT_PROFILE_TOOLS = [
4080
4225
  "code_search",
4081
4226
  "pre_commit_check",
4082
4227
  "mem_session_end",
4083
- "propose_sensor"
4228
+ "propose_sensor",
4229
+ "scaffold_test"
4084
4230
  ];
4085
4231
  var MAINTENANCE_PROFILE_TOOLS = [
4086
4232
  ...ENFORCEMENT_PROFILE_TOOLS,
@@ -4286,6 +4432,35 @@ function createHaiveServer(options = {}) {
4286
4432
  return jsonResult(await proposeSensor(input, context));
4287
4433
  }
4288
4434
  );
4435
+ registerTool(
4436
+ "scaffold_test",
4437
+ [
4438
+ "Generate a PENDING post-incident test from a lesson (attempt/gotcha) \u2014 the on-ramp to a command",
4439
+ "sensor. A command sensor routes YOUR test as its oracle, but someone has to write it; this writes",
4440
+ "the skeleton so you only fill in the assertion.",
4441
+ "",
4442
+ "USE THIS right after mem_tried when the mistake is behavioural (a regex can't express it): it",
4443
+ "writes a stub carrying the incident's provenance and returns the exact `sensors propose --kind",
4444
+ "test` command to arm it.",
4445
+ "",
4446
+ "It DOES NOT arm a sensor \u2014 propose_sensor stays the sole validated writer, and the stub is left",
4447
+ "PENDING (todo/skip) so the suite stays green until you write the assertion. Monorepo-aware: the",
4448
+ "framework and location come from the package that owns the lesson's anchor paths.",
4449
+ "",
4450
+ "PARAMETERS:",
4451
+ " memory_id \u2014 the attempt/gotcha to scaffold from",
4452
+ " framework \u2014 vitest | jest | pytest | gotest (auto-detected when omitted)",
4453
+ " out_path \u2014 override the test file path (repo-relative)",
4454
+ " write \u2014 write the file (default true); false returns the content for preview",
4455
+ "",
4456
+ "RETURNS: { ok, path, run_command, propose_command, content, written, already_exists, notice }"
4457
+ ].join("\n"),
4458
+ ScaffoldTestInputSchema,
4459
+ async (input) => {
4460
+ tracker.record("scaffold_test", input.memory_id);
4461
+ return jsonResult(await scaffoldTest(input, context));
4462
+ }
4463
+ );
4289
4464
  registerTool(
4290
4465
  "ingest_findings",
4291
4466
  [
@@ -4976,6 +5151,9 @@ export {
4976
5151
  readPresumedCorrectTargets,
4977
5152
  proposeSensor,
4978
5153
  memTried,
5154
+ detectTestFrameworkForPaths,
5155
+ detectTestFrameworksForAnchors,
5156
+ scaffoldTest,
4979
5157
  getBriefing,
4980
5158
  codeMapTool,
4981
5159
  getRecap,
@@ -5000,4 +5178,4 @@ export {
5000
5178
  printHaiveMcpVersion,
5001
5179
  runHaiveMcpStdio
5002
5180
  };
5003
- //# sourceMappingURL=chunk-VLRQ4MRO.js.map
5181
+ //# sourceMappingURL=chunk-I4VELI5K.js.map