@hivelore/mcp 0.31.0 → 0.34.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1147,7 +1147,12 @@ import {
1147
1147
  import { z as z15 } from "zod";
1148
1148
  var ProposeSensorInputSchema = {
1149
1149
  memory_id: z15.string().min(1).describe("Id of the gotcha/attempt memory this sensor protects."),
1150
- pattern: z15.string().min(1).describe("Regex matching the FAULTY usage (the risky call/token), e.g. 'stripe\\.paymentIntents\\.create'."),
1150
+ kind: z15.enum(["regex", "shell", "test"]).default("regex").describe(
1151
+ "regex = pattern matched on added diff lines (default). shell|test = a COMMAND the gate runs when the diff touches the sensor's paths \u2014 routes the team's own oracle (an existing test, an invariant script) to this lesson. Command sensors only execute where enforcement.runCommandSensors=true."
1152
+ ),
1153
+ pattern: z15.string().optional().describe("kind=regex: regex matching the FAULTY usage (the risky call/token), e.g. 'stripe\\.paymentIntents\\.create'."),
1154
+ command: z15.string().optional().describe("kind=shell|test: command to execute (e.g. 'npx vitest run tests/payments/refund.spec.ts'). Non-zero exit = the lesson fires."),
1155
+ timeout_ms: z15.number().int().positive().optional().describe("kind=shell|test: max runtime before the executor kills the command (default 120000)."),
1151
1156
  absent: z15.string().optional().describe(
1152
1157
  "Regex for the CORRECT-usage marker (e.g. 'idempotencyKey'). When it appears in the window around a match, the catch is suppressed \u2014 this is what makes the sensor discriminate the faulty call from the correct one. STRONGLY recommended for 'X without Y' lessons."
1153
1158
  ),
@@ -1189,20 +1194,62 @@ async function readPresumedCorrectTargets(root, relPaths) {
1189
1194
  }
1190
1195
  return targets;
1191
1196
  }
1197
+ function runCommandForValidation(command, root, timeoutMs = 12e4) {
1198
+ try {
1199
+ execSync(`bash -c ${JSON.stringify(command)}`, {
1200
+ cwd: root,
1201
+ timeout: timeoutMs,
1202
+ maxBuffer: 8 * 1024 * 1024,
1203
+ stdio: ["ignore", "pipe", "pipe"]
1204
+ });
1205
+ return { status: "passed", detail: "exit 0" };
1206
+ } catch (err) {
1207
+ const e = err;
1208
+ const out = `${e.stdout?.toString() ?? ""}
1209
+ ${e.stderr?.toString() ?? ""}`.split("\n").filter(Boolean).slice(-8).join("\n");
1210
+ if (e.killed) return { status: "unrunnable", detail: `timed out after ${timeoutMs}ms` };
1211
+ if (e.status === 127 || e.status === 126 || e.code === "ENOENT") {
1212
+ return { status: "unrunnable", detail: e.status === 126 ? "command found but not executable" : "command not found" };
1213
+ }
1214
+ return { status: "failed", detail: out || `exit ${e.status ?? "?"}` };
1215
+ }
1216
+ }
1192
1217
  async function proposeSensor(input, ctx) {
1193
1218
  if (!existsSync15(ctx.paths.memoriesDir)) {
1194
1219
  throw new Error(`No .ai/memories at ${ctx.paths.root}. Run 'hivelore init' first.`);
1195
1220
  }
1196
- try {
1197
- new RegExp(input.pattern, input.flags ?? "");
1198
- if (input.absent) new RegExp(input.absent, input.flags ?? "");
1199
- } catch (err) {
1221
+ const kind = input.kind ?? "regex";
1222
+ if (kind === "regex") {
1223
+ if (!input.pattern) {
1224
+ return {
1225
+ accepted: false,
1226
+ memory_id: input.memory_id,
1227
+ severity: input.severity,
1228
+ reason: "invalid-regex",
1229
+ guidance: "kind=regex requires a `pattern`.",
1230
+ self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1231
+ };
1232
+ }
1233
+ try {
1234
+ new RegExp(input.pattern, input.flags ?? "");
1235
+ if (input.absent) new RegExp(input.absent, input.flags ?? "");
1236
+ } catch (err) {
1237
+ return {
1238
+ accepted: false,
1239
+ memory_id: input.memory_id,
1240
+ severity: input.severity,
1241
+ reason: "invalid-regex",
1242
+ guidance: `The pattern or absent regex does not compile: ${String(err)}`,
1243
+ self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1244
+ };
1245
+ }
1246
+ } else if (!input.command?.trim()) {
1200
1247
  return {
1201
1248
  accepted: false,
1202
1249
  memory_id: input.memory_id,
1203
1250
  severity: input.severity,
1204
- reason: "invalid-regex",
1205
- guidance: `The pattern or absent regex does not compile: ${String(err)}`,
1251
+ reason: "invalid-command",
1252
+ guidance: "kind=shell|test requires a `command` (the check the gate will execute).",
1206
1253
  self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1207
1254
  };
1208
1255
  }
@@ -1211,6 +1258,43 @@ async function proposeSensor(input, ctx) {
1211
1258
  if (!found) {
1212
1259
  throw new Error(`No memory found with id ${input.memory_id}`);
1213
1260
  }
1261
+ if (kind !== "regex") {
1262
+ const verdictCmd = runCommandForValidation(input.command.trim(), ctx.paths.root, input.timeout_ms);
1263
+ const anchorPathsCmd = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
1264
+ if (verdictCmd.status !== "passed" && input.severity === "block") {
1265
+ return {
1266
+ accepted: false,
1267
+ memory_id: input.memory_id,
1268
+ severity: input.severity,
1269
+ reason: verdictCmd.status === "unrunnable" ? "command-unrunnable" : "fails-on-current",
1270
+ guidance: verdictCmd.status === "unrunnable" ? `The command could not run (${verdictCmd.detail}). Fix the command (or timeout_ms), then re-propose.` : `The command FAILS on the current tree \u2014 the presumed-correct state must pass, or the gate would block every commit. Revert the faulty diff (or fix the check), then re-propose. Output tail:
1271
+ ${verdictCmd.detail}`,
1272
+ self_check: { silent_on_current: false, fires_on_bad: null, fired_on: anchorPathsCmd }
1273
+ };
1274
+ }
1275
+ const sensorCmd = {
1276
+ kind,
1277
+ command: input.command.trim(),
1278
+ ...input.timeout_ms ? { timeout_ms: input.timeout_ms } : {},
1279
+ paths: anchorPathsCmd,
1280
+ message: input.message?.trim() || deriveMessage(found.memory.body, input.command.trim(), void 0),
1281
+ severity: input.severity,
1282
+ autogen: false,
1283
+ last_fired: null
1284
+ };
1285
+ const nextCmd = {
1286
+ frontmatter: { ...found.memory.frontmatter, sensor: sensorCmd },
1287
+ body: found.memory.body
1288
+ };
1289
+ await writeFile8(found.filePath, serializeMemory7(nextCmd), "utf8");
1290
+ return {
1291
+ accepted: true,
1292
+ memory_id: input.memory_id,
1293
+ severity: input.severity,
1294
+ 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}).`,
1295
+ self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: null, fired_on: [] }
1296
+ };
1297
+ }
1214
1298
  const anchorPaths = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
1215
1299
  const currentTargets = await readPresumedCorrectTargets(ctx.paths.root, anchorPaths);
1216
1300
  const badExamples = [
@@ -1271,11 +1355,14 @@ var MemTriedInputSchema = {
1271
1355
  paths: z16.array(z16.string()).default([]).describe("Anchor file paths this applies to"),
1272
1356
  author: z16.string().optional().describe("Author handle or email"),
1273
1357
  sensor: z16.object({
1274
- pattern: z16.string().min(1).describe("Regex matching the FAULTY usage (added diff lines)"),
1275
- absent: z16.string().optional().describe("Regex marking CORRECT usage nearby \u2014 excludes it from firing"),
1358
+ kind: z16.enum(["regex", "shell", "test"]).default("regex").describe("regex pattern, or a shell/test COMMAND the gate executes (behaviour bridge)"),
1359
+ pattern: z16.string().optional().describe("kind=regex: regex matching the FAULTY usage (added diff lines)"),
1360
+ command: z16.string().optional().describe("kind=shell|test: command the gate runs when the diff touches the sensor's paths (non-zero exit = lesson fires)"),
1361
+ timeout_ms: z16.number().int().positive().optional().describe("kind=shell|test: max runtime (default 120000)"),
1362
+ absent: z16.string().optional().describe("kind=regex: regex marking CORRECT usage nearby \u2014 excludes it from firing"),
1276
1363
  severity: z16.enum(["warn", "block"]).default("block").describe("block = deterministic gate refusal"),
1277
1364
  message: z16.string().optional().describe("Self-correction message shown when the sensor fires"),
1278
- bad_example: z16.string().optional().describe("Code snippet the sensor MUST fire on (validation)")
1365
+ bad_example: z16.string().optional().describe("kind=regex: code snippet the sensor MUST fire on (validation)")
1279
1366
  }).optional().describe(
1280
1367
  "ONE-SHOT loop close: validate and attach a sensor in the same call (equivalent to a follow-up propose_sensor). Validated against HEAD \u2014 silent on current code, fires on the bad example. If rejected, the attempt is still saved and the verdict tells you how to revise."
1281
1368
  )
@@ -1311,7 +1398,10 @@ async function memTried(input, ctx) {
1311
1398
  const verdict = await proposeSensor(
1312
1399
  {
1313
1400
  memory_id: frontmatter.id,
1401
+ kind: input.sensor.kind ?? "regex",
1314
1402
  pattern: input.sensor.pattern,
1403
+ command: input.sensor.command,
1404
+ timeout_ms: input.sensor.timeout_ms,
1315
1405
  absent: input.sensor.absent,
1316
1406
  severity: input.sensor.severity ?? "block",
1317
1407
  message: input.sensor.message,
@@ -1446,83 +1536,17 @@ async function writeDraft(ctx, draft) {
1446
1536
  return file;
1447
1537
  }
1448
1538
 
1449
- // src/tools/mem-observe.ts
1450
- import { mkdir as mkdir5, writeFile as writeFile11 } from "fs/promises";
1451
- import { existsSync as existsSync18 } from "fs";
1452
- import path8 from "path";
1539
+ // src/tools/mem-session-end.ts
1540
+ import { writeFile as writeFile12, mkdir as mkdir6 } from "fs/promises";
1541
+ import { existsSync as existsSync19 } from "fs";
1542
+ import path9 from "path";
1453
1543
  import {
1454
1544
  buildFrontmatter as buildFrontmatter3,
1455
- isLikelyGuessable,
1545
+ loadMemoriesFromDir as loadMemoriesFromDir15,
1456
1546
  memoryFilePath as memoryFilePath4,
1457
1547
  serializeMemory as serializeMemory10
1458
1548
  } from "@hivelore/core";
1459
1549
  import { z as z18 } from "zod";
1460
- var MemObserveInputSchema = {
1461
- what: z18.string().min(1).describe("Short title: what did you observe? (e.g. 'MobilePaymentController has two @RequestBody on handleWebhook')"),
1462
- where: z18.string().min(1).describe("File path(s) where the issue lives \u2014 be specific"),
1463
- impact: z18.string().min(1).describe("What breaks or could break because of this (e.g. 'Spring MVC rejects the handler at startup')"),
1464
- fix: z18.string().optional().describe("Suggested fix or workaround (optional \u2014 leave empty if unknown)"),
1465
- scope: z18.enum(["personal", "team", "module"]).default("team").describe("Visibility scope \u2014 defaults to team since discoveries benefit everyone"),
1466
- module: z18.string().optional().describe("Module name (required when scope=module)"),
1467
- tags: z18.array(z18.string()).default([]).describe("Tags for filtering"),
1468
- author: z18.string().optional().describe("Author handle or email"),
1469
- force: z18.boolean().default(false).describe(
1470
- "Save even if the observation looks like generic, guessable knowledge. By default, low-specificity observations (things a capable model already knows) are SKIPPED to keep the corpus high-signal \u2014 only unguessable, team-specific discoveries are worth storing."
1471
- )
1472
- };
1473
- async function memObserve(input, ctx) {
1474
- if (!existsSync18(ctx.paths.haiveDir)) {
1475
- throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
1476
- }
1477
- const signalText = [input.what, input.impact, input.fix ?? ""].join(" ");
1478
- if (!input.force && isLikelyGuessable(signalText)) {
1479
- return {
1480
- id: "",
1481
- scope: input.scope,
1482
- file_path: "",
1483
- skipped: true,
1484
- reason: "Observation looks like generic, guessable knowledge (low specificity) \u2014 not saved. Capture only arbitrary, team-specific facts (exact names, values, formats). Pass force=true to override."
1485
- };
1486
- }
1487
- const slug = input.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 6).join("-");
1488
- const anchorPaths = input.where.split(/[,\n]/).map((s) => s.trim()).filter(Boolean);
1489
- const baseFm = buildFrontmatter3({
1490
- type: "gotcha",
1491
- slug,
1492
- scope: input.scope,
1493
- module: input.module,
1494
- tags: input.tags,
1495
- paths: anchorPaths,
1496
- author: input.author
1497
- });
1498
- const frontmatter = { ...baseFm, status: "validated" };
1499
- const lines = [`# ${input.what}`, ""];
1500
- lines.push(`**Where:** \`${input.where}\``);
1501
- lines.push("", `**Impact:** ${input.impact}`);
1502
- if (input.fix) {
1503
- lines.push("", `**Fix/workaround:** ${input.fix}`);
1504
- }
1505
- const body = lines.join("\n") + "\n";
1506
- const file = memoryFilePath4(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
1507
- await mkdir5(path8.dirname(file), { recursive: true });
1508
- if (existsSync18(file)) {
1509
- throw new Error(`Memory already exists at ${file}`);
1510
- }
1511
- await writeFile11(file, serializeMemory10({ frontmatter, body }), "utf8");
1512
- return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
1513
- }
1514
-
1515
- // src/tools/mem-session-end.ts
1516
- import { writeFile as writeFile13, mkdir as mkdir7 } from "fs/promises";
1517
- import { existsSync as existsSync20 } from "fs";
1518
- import path10 from "path";
1519
- import {
1520
- buildFrontmatter as buildFrontmatter4,
1521
- loadMemoriesFromDir as loadMemoriesFromDir15,
1522
- memoryFilePath as memoryFilePath5,
1523
- serializeMemory as serializeMemory11
1524
- } from "@hivelore/core";
1525
- import { z as z19 } from "zod";
1526
1550
 
1527
1551
  // src/session-tracker.ts
1528
1552
  import {
@@ -1531,12 +1555,12 @@ import {
1531
1555
  loadConfig as loadConfig2,
1532
1556
  writeSessionHandoff
1533
1557
  } from "@hivelore/core";
1534
- import { mkdir as mkdir6, writeFile as writeFile12, rm } from "fs/promises";
1535
- import { existsSync as existsSync19 } from "fs";
1536
- import path9 from "path";
1558
+ import { mkdir as mkdir5, writeFile as writeFile11, rm } from "fs/promises";
1559
+ import { existsSync as existsSync18 } from "fs";
1560
+ import path8 from "path";
1537
1561
  import { execSync as execSync2 } from "child_process";
1538
1562
  function pendingDistillPath(ctx) {
1539
- return path9.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
1563
+ return path8.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
1540
1564
  }
1541
1565
  var SessionTracker = class {
1542
1566
  events = [];
@@ -1563,7 +1587,7 @@ var SessionTracker = class {
1563
1587
  this.shutdownRegistered = true;
1564
1588
  const save = async () => {
1565
1589
  const writingTools = this.events.filter(
1566
- (e) => ["mem_save", "mem_tried", "mem_observe", "mem_update", "bootstrap_project_save"].includes(e.tool)
1590
+ (e) => ["mem_save", "mem_tried", "mem_update", "bootstrap_project_save"].includes(e.tool)
1567
1591
  );
1568
1592
  const totalCalls = this.events.length;
1569
1593
  if (totalCalls === 0) return;
@@ -1640,7 +1664,7 @@ var SessionTracker = class {
1640
1664
  (e) => e.tool === "mem_session_end" && !e.summary?.startsWith("Auto-captured")
1641
1665
  );
1642
1666
  const isSubstantialSession = totalCalls >= 3 || writingTools.length > 0;
1643
- if (!ranPostTask && isSubstantialSession && existsSync19(this.ctx.paths.haiveDir)) {
1667
+ if (!ranPostTask && isSubstantialSession && existsSync18(this.ctx.paths.haiveDir)) {
1644
1668
  try {
1645
1669
  const memoriesSaved = writingTools.map((e) => e.summary ?? "").filter(Boolean).slice(0, 20);
1646
1670
  const payload = {
@@ -1653,9 +1677,9 @@ var SessionTracker = class {
1653
1677
  ...gitDiff ? { git_diff: gitDiff } : {},
1654
1678
  ...recapId ? { recap_id: recapId } : {}
1655
1679
  };
1656
- const cacheDir = path9.join(this.ctx.paths.haiveDir, ".cache");
1657
- await mkdir6(cacheDir, { recursive: true });
1658
- await writeFile12(
1680
+ const cacheDir = path8.join(this.ctx.paths.haiveDir, ".cache");
1681
+ await mkdir5(cacheDir, { recursive: true });
1682
+ await writeFile11(
1659
1683
  pendingDistillPath(this.ctx),
1660
1684
  JSON.stringify(payload, null, 2) + "\n",
1661
1685
  "utf8"
@@ -1674,7 +1698,7 @@ var SessionTracker = class {
1674
1698
  };
1675
1699
  async function clearPendingDistill(ctx) {
1676
1700
  const p = pendingDistillPath(ctx);
1677
- if (existsSync19(p)) {
1701
+ if (existsSync18(p)) {
1678
1702
  try {
1679
1703
  await rm(p);
1680
1704
  } catch {
@@ -1691,15 +1715,15 @@ function summarizeTools(events) {
1691
1715
 
1692
1716
  // src/tools/mem-session-end.ts
1693
1717
  var MemSessionEndInputSchema = {
1694
- goal: z19.string().min(1).describe("What you were trying to accomplish this session (1\u20132 sentences)"),
1695
- accomplished: z19.string().describe("What was actually done \u2014 bullet list recommended"),
1696
- discoveries: z19.string().default("").describe(
1718
+ goal: z18.string().min(1).describe("What you were trying to accomplish this session (1\u20132 sentences)"),
1719
+ accomplished: z18.string().describe("What was actually done \u2014 bullet list recommended"),
1720
+ discoveries: z18.string().default("").describe(
1697
1721
  "Any bugs, inconsistencies, surprises, or missing knowledge found during this session. Empty if nothing surprising was found."
1698
1722
  ),
1699
- files_touched: z19.array(z19.string()).default([]).describe("Key files that were read or modified \u2014 used as anchor paths"),
1700
- next_steps: z19.string().default("").describe("What should happen next (for the next session or a teammate)"),
1701
- scope: z19.enum(["personal", "team", "module"]).default("personal").describe("Visibility: personal = private to you, team = shared with the team"),
1702
- module: z19.string().optional().describe("Module name (required when scope=module)")
1723
+ files_touched: z18.array(z18.string()).default([]).describe("Key files that were read or modified \u2014 used as anchor paths"),
1724
+ next_steps: z18.string().default("").describe("What should happen next (for the next session or a teammate)"),
1725
+ scope: z18.enum(["personal", "team", "module"]).default("personal").describe("Visibility: personal = private to you, team = shared with the team"),
1726
+ module: z18.string().optional().describe("Module name (required when scope=module)")
1703
1727
  };
1704
1728
  function recapTopic(scope, module) {
1705
1729
  return module ? `session-recap-${scope}-${module}` : `session-recap-${scope}`;
@@ -1729,23 +1753,23 @@ ${input.next_steps}`);
1729
1753
  return lines.join("\n");
1730
1754
  }
1731
1755
  async function memSessionEnd(input, ctx) {
1732
- if (!existsSync20(ctx.paths.haiveDir)) {
1756
+ if (!existsSync19(ctx.paths.haiveDir)) {
1733
1757
  throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
1734
1758
  }
1735
1759
  const body = buildBody(input);
1736
1760
  const topic = recapTopic(input.scope, input.module);
1737
1761
  const normalizedFiles = input.files_touched.map((p) => {
1738
- if (!p || !path10.isAbsolute(p)) return p;
1739
- const rel = path10.relative(ctx.paths.root, p);
1762
+ if (!p || !path9.isAbsolute(p)) return p;
1763
+ const rel = path9.relative(ctx.paths.root, p);
1740
1764
  return rel.startsWith("..") ? p : rel;
1741
1765
  });
1742
1766
  const invalidPaths = normalizedFiles.filter(
1743
- (p) => !existsSync20(path10.resolve(ctx.paths.root, p))
1767
+ (p) => !existsSync19(path9.resolve(ctx.paths.root, p))
1744
1768
  );
1745
1769
  if (invalidPaths.length > 0) {
1746
1770
  console.warn(`[haive] session end: anchor path(s) not found: ${invalidPaths.join(", ")}`);
1747
1771
  }
1748
- const existing = existsSync20(ctx.paths.memoriesDir) ? await loadMemoriesFromDir15(ctx.paths.memoriesDir) : [];
1772
+ const existing = existsSync19(ctx.paths.memoriesDir) ? await loadMemoriesFromDir15(ctx.paths.memoriesDir) : [];
1749
1773
  const topicMatch = existing.find(
1750
1774
  ({ memory }) => memory.frontmatter.topic === topic && memory.frontmatter.scope === input.scope && (!input.module || memory.frontmatter.module === input.module)
1751
1775
  );
@@ -1761,9 +1785,9 @@ async function memSessionEnd(input, ctx) {
1761
1785
  paths: normalizedFiles.length ? normalizedFiles : fm.anchor.paths
1762
1786
  }
1763
1787
  };
1764
- await writeFile13(
1788
+ await writeFile12(
1765
1789
  topicMatch.filePath,
1766
- serializeMemory11({ frontmatter: newFrontmatter, body }),
1790
+ serializeMemory10({ frontmatter: newFrontmatter, body }),
1767
1791
  "utf8"
1768
1792
  );
1769
1793
  await clearPendingDistill(ctx);
@@ -1775,7 +1799,7 @@ async function memSessionEnd(input, ctx) {
1775
1799
  revision_count: revisionCount
1776
1800
  };
1777
1801
  }
1778
- const frontmatter = buildFrontmatter4({
1802
+ const frontmatter = buildFrontmatter3({
1779
1803
  type: "session_recap",
1780
1804
  slug: "recap",
1781
1805
  scope: input.scope,
@@ -1785,14 +1809,14 @@ async function memSessionEnd(input, ctx) {
1785
1809
  topic,
1786
1810
  status: "validated"
1787
1811
  });
1788
- const file = memoryFilePath5(
1812
+ const file = memoryFilePath4(
1789
1813
  ctx.paths,
1790
1814
  frontmatter.scope,
1791
1815
  frontmatter.id,
1792
1816
  frontmatter.module
1793
1817
  );
1794
- await mkdir7(path10.dirname(file), { recursive: true });
1795
- await writeFile13(file, serializeMemory11({ frontmatter, body }), "utf8");
1818
+ await mkdir6(path9.dirname(file), { recursive: true });
1819
+ await writeFile12(file, serializeMemory10({ frontmatter, body }), "utf8");
1796
1820
  await clearPendingDistill(ctx);
1797
1821
  return {
1798
1822
  id: frontmatter.id,
@@ -1804,9 +1828,9 @@ async function memSessionEnd(input, ctx) {
1804
1828
  }
1805
1829
 
1806
1830
  // src/tools/get-briefing.ts
1807
- import { readFile as readFile6, writeFile as writeFile14, readdir as readdir4 } from "fs/promises";
1808
- import { existsSync as existsSync22 } from "fs";
1809
- import path12 from "path";
1831
+ import { readFile as readFile6, writeFile as writeFile13, readdir as readdir4 } from "fs/promises";
1832
+ import { existsSync as existsSync21 } from "fs";
1833
+ import path11 from "path";
1810
1834
  import {
1811
1835
  allocateBudget,
1812
1836
  assessBootstrapState,
@@ -1840,7 +1864,7 @@ import {
1840
1864
  queryCodeMap,
1841
1865
  readSessionHandoff,
1842
1866
  resolveBriefingBudget,
1843
- serializeMemory as serializeMemory12,
1867
+ serializeMemory as serializeMemory11,
1844
1868
  specificityScore,
1845
1869
  GUESSABLE_THRESHOLD,
1846
1870
  tokenizeQuery as tokenizeQuery2,
@@ -1848,12 +1872,12 @@ import {
1848
1872
  truncateToTokens,
1849
1873
  writeBriefingMarker
1850
1874
  } from "@hivelore/core";
1851
- import { z as z20 } from "zod";
1875
+ import { z as z19 } from "zod";
1852
1876
 
1853
1877
  // src/tools/briefing-helpers.ts
1854
1878
  import { readdir as readdir3, readFile as readFile5 } from "fs/promises";
1855
- import { existsSync as existsSync21 } from "fs";
1856
- import path11 from "path";
1879
+ import { existsSync as existsSync20 } from "fs";
1880
+ import path10 from "path";
1857
1881
  import {
1858
1882
  classifyMemoryPriority as coreClassifyPriority,
1859
1883
  isGlobPath,
@@ -1986,15 +2010,15 @@ async function trySemanticHits(ctx, task, limit) {
1986
2010
  }
1987
2011
  async function loadModuleContexts2(ctx, modules) {
1988
2012
  if (modules.length === 0) return [];
1989
- if (!existsSync21(ctx.paths.modulesContextDir)) return [];
2013
+ if (!existsSync20(ctx.paths.modulesContextDir)) return [];
1990
2014
  const available = new Set(
1991
2015
  (await readdir3(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
1992
2016
  );
1993
2017
  const out = [];
1994
2018
  for (const m of modules) {
1995
2019
  if (!available.has(m)) continue;
1996
- const file = path11.join(ctx.paths.modulesContextDir, m, "context.md");
1997
- if (existsSync21(file)) {
2020
+ const file = path10.join(ctx.paths.modulesContextDir, m, "context.md");
2021
+ if (existsSync20(file)) {
1998
2022
  out.push({ name: m, content: await readFile5(file, "utf8") });
1999
2023
  }
2000
2024
  }
@@ -2003,38 +2027,38 @@ async function loadModuleContexts2(ctx, modules) {
2003
2027
 
2004
2028
  // src/tools/get-briefing.ts
2005
2029
  var GetBriefingInputSchema = {
2006
- task: z20.string().optional().describe(
2030
+ task: z19.string().optional().describe(
2007
2031
  "What you are about to do, in 1\u20132 sentences. Used to rank relevant memories semantically."
2008
2032
  ),
2009
- files: z20.array(z20.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
2010
- max_tokens: z20.number().int().positive().default(8e3).describe(
2033
+ files: z19.array(z19.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
2034
+ max_tokens: z19.number().int().positive().default(8e3).describe(
2011
2035
  "Approximate token budget for the entire briefing. Each section is allocated a share and truncated to fit."
2012
2036
  ),
2013
- max_memories: z20.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
2014
- include_project_context: z20.boolean().default(true),
2015
- dedupe_project_context: z20.boolean().optional().describe(
2037
+ max_memories: z19.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
2038
+ include_project_context: z19.boolean().default(true),
2039
+ dedupe_project_context: z19.boolean().optional().describe(
2016
2040
  "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."
2017
2041
  ),
2018
- include_module_contexts: z20.boolean().default(true),
2019
- semantic: z20.boolean().default(true).describe(
2042
+ include_module_contexts: z19.boolean().default(true),
2043
+ semantic: z19.boolean().default(true).describe(
2020
2044
  "Use semantic ranking when a task is provided (requires `hivelore embeddings index`)."
2021
2045
  ),
2022
- include_stale: z20.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
2023
- track: z20.boolean().default(true).describe("Increment read_count on returned memories"),
2024
- format: z20.enum(["full", "compact", "actions"]).default("full").describe(
2046
+ include_stale: z19.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
2047
+ track: z19.boolean().default(true).describe("Increment read_count on returned memories"),
2048
+ format: z19.enum(["full", "compact", "actions"]).default("full").describe(
2025
2049
  "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."
2026
2050
  ),
2027
- symbols: z20.array(z20.string()).default([]).describe(
2051
+ symbols: z19.array(z19.string()).default([]).describe(
2028
2052
  "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."
2029
2053
  ),
2030
- min_semantic_score: z20.number().min(0).max(1).default(0).describe(
2054
+ min_semantic_score: z19.number().min(0).max(1).default(0).describe(
2031
2055
  "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."
2032
2056
  ),
2033
- budget_preset: z20.enum(["quick", "balanced", "deep"]).optional().describe(
2057
+ budget_preset: z19.enum(["quick", "balanced", "deep"]).optional().describe(
2034
2058
  "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."
2035
2059
  )
2036
2060
  };
2037
- var GetBriefingZod = z20.object(GetBriefingInputSchema);
2061
+ var GetBriefingZod = z19.object(GetBriefingInputSchema);
2038
2062
  async function getBriefing(input, ctx) {
2039
2063
  const resolvedBudget = resolveBriefingBudget(input.budget_preset, {
2040
2064
  max_tokens: input.max_tokens,
@@ -2050,7 +2074,7 @@ async function getBriefing(input, ctx) {
2050
2074
  let usage = { version: 1, updated_at: "", by_id: {} };
2051
2075
  let byId = /* @__PURE__ */ new Map();
2052
2076
  let lastSession;
2053
- if (existsSync22(ctx.paths.memoriesDir)) {
2077
+ if (existsSync21(ctx.paths.memoriesDir)) {
2054
2078
  const allLoaded = await loadMemoriesFromDir16(ctx.paths.memoriesDir);
2055
2079
  const recaps = allLoaded.filter(({ memory }) => memory.frontmatter.type === "session_recap").sort(
2056
2080
  (a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
@@ -2225,7 +2249,7 @@ async function getBriefing(input, ctx) {
2225
2249
  if (!isAutoPromoteEligible(loaded.memory.frontmatter, u, rule)) continue;
2226
2250
  const newFm = { ...loaded.memory.frontmatter, status: "validated", validated_by: "auto" };
2227
2251
  try {
2228
- await writeFile14(loaded.filePath, serializeMemory12({ frontmatter: newFm, body: loaded.memory.body }), "utf8");
2252
+ await writeFile13(loaded.filePath, serializeMemory11({ frontmatter: newFm, body: loaded.memory.body }), "utf8");
2229
2253
  m.status = "validated";
2230
2254
  m.confidence = "trusted";
2231
2255
  } catch {
@@ -2233,7 +2257,7 @@ async function getBriefing(input, ctx) {
2233
2257
  }
2234
2258
  }
2235
2259
  }
2236
- let projectContextRaw = input.include_project_context && existsSync22(ctx.paths.projectContext) ? await readFile6(ctx.paths.projectContext, "utf8") : "";
2260
+ let projectContextRaw = input.include_project_context && existsSync21(ctx.paths.projectContext) ? await readFile6(ctx.paths.projectContext, "utf8") : "";
2237
2261
  let contextOmittedRecent = false;
2238
2262
  if (projectContextRaw && input.dedupe_project_context !== false) {
2239
2263
  const ctxHash = hashProjectContext(projectContextRaw);
@@ -2248,7 +2272,7 @@ async function getBriefing(input, ctx) {
2248
2272
  const setupWarnings = [];
2249
2273
  let autoContextGenerated = false;
2250
2274
  let projectContext = isTemplateContext ? "" : projectContextRaw;
2251
- if ((isTemplateContext || !existsSync22(ctx.paths.projectContext)) && input.include_project_context) {
2275
+ if ((isTemplateContext || !existsSync21(ctx.paths.projectContext)) && input.include_project_context) {
2252
2276
  const haiveConfig = await loadConfig3(ctx.paths);
2253
2277
  if (haiveConfig.autoContext) {
2254
2278
  const codeMap = await loadCodeMap(ctx.paths);
@@ -2422,7 +2446,7 @@ ${m.content}`).join("\n\n---\n\n"),
2422
2446
  actionRequired.push(extractActionItem(m.id, loaded.memory.body));
2423
2447
  }
2424
2448
  }
2425
- if (existsSync22(ctx.paths.memoriesDir)) {
2449
+ if (existsSync21(ctx.paths.memoriesDir)) {
2426
2450
  const allMems = await loadMemoriesFromDir16(ctx.paths.memoriesDir);
2427
2451
  for (const { memory } of allMems) {
2428
2452
  const fm = memory.frontmatter;
@@ -2433,7 +2457,7 @@ ${m.content}`).join("\n\n---\n\n"),
2433
2457
  }
2434
2458
  }
2435
2459
  const pendingDistillFile = pendingDistillPath(ctx);
2436
- if (existsSync22(pendingDistillFile)) {
2460
+ if (existsSync21(pendingDistillFile)) {
2437
2461
  try {
2438
2462
  const raw = await readFile6(pendingDistillFile, "utf8");
2439
2463
  const pd = JSON.parse(raw);
@@ -2462,7 +2486,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
2462
2486
  }
2463
2487
  }
2464
2488
  const memoriesEmpty = outputMemories.length === 0;
2465
- const hasMemoriesDir = existsSync22(ctx.paths.memoriesDir);
2489
+ const hasMemoriesDir = existsSync21(ctx.paths.memoriesDir);
2466
2490
  const isColdStart = isTemplateContext && memoriesEmpty && !lastSession && !autoContextGenerated;
2467
2491
  const hasUnguessableSignal = outputMemories.some(
2468
2492
  (m) => (m.priority === "must_read" || m.priority === "useful") && specificityScore(m.body) >= GUESSABLE_THRESHOLD
@@ -2480,7 +2504,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
2480
2504
  pcRaw = await readFile6(ctx.paths.projectContext, "utf8");
2481
2505
  } catch {
2482
2506
  }
2483
- const allForBootstrap = existsSync22(ctx.paths.memoriesDir) ? await loadMemoriesFromDir16(ctx.paths.memoriesDir) : [];
2507
+ const allForBootstrap = existsSync21(ctx.paths.memoriesDir) ? await loadMemoriesFromDir16(ctx.paths.memoriesDir) : [];
2484
2508
  const cmForBootstrap = await loadCodeMap(ctx.paths);
2485
2509
  let existingModules = [];
2486
2510
  try {
@@ -2530,7 +2554,7 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
2530
2554
  }
2531
2555
  if (input.task && outputMemories.length > 0 && actionRequired.length === 0) {
2532
2556
  hints.push(
2533
- "After completing the task: capture new gotchas with mem_observe, failed approaches with mem_tried, validated patterns with mem_save."
2557
+ "After completing the task: capture failed approaches with mem_tried, new gotchas and validated patterns with mem_save."
2534
2558
  );
2535
2559
  }
2536
2560
  if (outputMemories.length > 2 && !input.budget_preset && input.task && !hints.some((h) => h.includes("budget_preset"))) {
@@ -2544,7 +2568,7 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
2544
2568
  "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."
2545
2569
  );
2546
2570
  }
2547
- if (outputMemories.length > 0 && existsSync22(ctx.paths.haiveDir)) {
2571
+ if (outputMemories.length > 0 && existsSync21(ctx.paths.haiveDir)) {
2548
2572
  const proof = briefingProofLine(await loadPreventionEvents(ctx.paths));
2549
2573
  if (proof) hints.push(proof);
2550
2574
  }
@@ -2558,7 +2582,7 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
2558
2582
  adaptiveTrim
2559
2583
  });
2560
2584
  const breadcrumbTokens = breadcrumbs ? estimateTokens([...breadcrumbs.start_here, ...breadcrumbs.drill_down, breadcrumbs.note ?? ""].join("\n")) : 0;
2561
- if (existsSync22(ctx.paths.haiveDir)) {
2585
+ if (existsSync21(ctx.paths.haiveDir)) {
2562
2586
  await writeBriefingMarker(ctx.paths, {
2563
2587
  sessionId: process.env.HAIVE_SESSION_ID,
2564
2588
  ...input.task ? { task: input.task } : {},
@@ -2614,8 +2638,8 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
2614
2638
  };
2615
2639
  }
2616
2640
  async function detectRunCommands(root) {
2617
- const pkgPath = path12.join(root, "package.json");
2618
- if (!existsSync22(pkgPath)) return null;
2641
+ const pkgPath = path11.join(root, "package.json");
2642
+ if (!existsSync21(pkgPath)) return null;
2619
2643
  try {
2620
2644
  const pkg = JSON.parse(await readFile6(pkgPath, "utf8"));
2621
2645
  const scripts = pkg.scripts ?? {};
@@ -2682,24 +2706,24 @@ function oneLine(value) {
2682
2706
  return value.replace(/\s+/g, " ").replace(/"/g, '\\"').trim().slice(0, 120);
2683
2707
  }
2684
2708
  function serverVersion() {
2685
- return true ? "0.31.0" : "dev";
2709
+ return true ? "0.34.1" : "dev";
2686
2710
  }
2687
2711
 
2688
2712
  // src/tools/code-map.ts
2689
2713
  import { estimateTokens as estimateTokens2, loadCodeMap as loadCodeMap2, queryCodeMap as queryCodeMap2 } from "@hivelore/core";
2690
- import { z as z21 } from "zod";
2714
+ import { z as z20 } from "zod";
2691
2715
  var CodeMapInputSchema = {
2692
- file: z21.string().optional().describe("Filter to files whose path contains this substring"),
2693
- symbol: z21.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
2694
- paths: z21.array(z21.string()).default([]).describe(
2716
+ file: z20.string().optional().describe("Filter to files whose path contains this substring"),
2717
+ symbol: z20.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
2718
+ paths: z20.array(z20.string()).default([]).describe(
2695
2719
  "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."
2696
2720
  ),
2697
- max_files: z21.number().int().positive().default(40).describe("Cap on returned files (hard limit, applied after token budget)"),
2698
- max_tokens: z21.number().int().positive().optional().describe(
2721
+ max_files: z20.number().int().positive().default(40).describe("Cap on returned files (hard limit, applied after token budget)"),
2722
+ max_tokens: z20.number().int().positive().optional().describe(
2699
2723
  "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)."
2700
2724
  )
2701
2725
  };
2702
- var CodeMapInputZod = z21.object(CodeMapInputSchema);
2726
+ var CodeMapInputZod = z20.object(CodeMapInputSchema);
2703
2727
  async function codeMapTool(input, ctx) {
2704
2728
  const map = await loadCodeMap2(ctx.paths);
2705
2729
  if (!map) {
@@ -2768,15 +2792,15 @@ function estimateFileEntryTokens(f) {
2768
2792
  }
2769
2793
 
2770
2794
  // src/tools/mem-diff.ts
2771
- import { existsSync as existsSync23 } from "fs";
2795
+ import { existsSync as existsSync22 } from "fs";
2772
2796
  import { loadMemoriesFromDir as loadMemoriesFromDir17 } from "@hivelore/core";
2773
- import { z as z22 } from "zod";
2797
+ import { z as z21 } from "zod";
2774
2798
  var MemDiffInputSchema = {
2775
- id_a: z22.string().min(1).describe("First memory id"),
2776
- id_b: z22.string().min(1).describe("Second memory id")
2799
+ id_a: z21.string().min(1).describe("First memory id"),
2800
+ id_b: z21.string().min(1).describe("Second memory id")
2777
2801
  };
2778
2802
  async function memDiff(input, ctx) {
2779
- if (!existsSync23(ctx.paths.memoriesDir)) {
2803
+ if (!existsSync22(ctx.paths.memoriesDir)) {
2780
2804
  throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
2781
2805
  }
2782
2806
  const all = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
@@ -2813,16 +2837,16 @@ async function memDiff(input, ctx) {
2813
2837
  }
2814
2838
 
2815
2839
  // src/tools/get-recap.ts
2816
- import { existsSync as existsSync24 } from "fs";
2840
+ import { existsSync as existsSync23 } from "fs";
2817
2841
  import { loadMemoriesFromDir as loadMemoriesFromDir18 } from "@hivelore/core";
2818
- import { z as z23 } from "zod";
2842
+ import { z as z22 } from "zod";
2819
2843
  var GetRecapInputSchema = {
2820
- scope: z23.enum(["personal", "team", "any"]).default("any").describe(
2844
+ scope: z22.enum(["personal", "team", "any"]).default("any").describe(
2821
2845
  "Limit to a specific scope's recap. Default 'any' returns the most recent recap across both personal and team scopes."
2822
2846
  )
2823
2847
  };
2824
2848
  async function getRecap(input, ctx) {
2825
- if (!existsSync24(ctx.paths.memoriesDir)) {
2849
+ if (!existsSync23(ctx.paths.memoriesDir)) {
2826
2850
  return { recap: null, notice: "No .ai/memories directory \u2014 haive not initialized here." };
2827
2851
  }
2828
2852
  const all = await loadMemoriesFromDir18(ctx.paths.memoriesDir);
@@ -2849,13 +2873,13 @@ async function getRecap(input, ctx) {
2849
2873
  }
2850
2874
 
2851
2875
  // src/tools/mem-relevant-to.ts
2852
- import { z as z24 } from "zod";
2876
+ import { z as z23 } from "zod";
2853
2877
  var MemRelevantToInputSchema = {
2854
- task: z24.string().min(1).describe("What you are about to do, in 1\u20132 sentences. Used to rank relevant memories."),
2855
- files: z24.array(z24.string()).default([]).describe("Optional: files you are about to edit \u2014 surfaces anchored memories."),
2856
- limit: z24.number().int().positive().max(30).default(8).describe("Cap on returned memories."),
2857
- min_semantic_score: z24.number().min(0).max(1).default(0.25).describe("Drop weakly-related semantic hits below this cosine threshold."),
2858
- format: z24.enum(["full", "compact", "actions"]).default("full").describe("'compact' = id + 1-line summary; 'full' = complete bodies; 'actions' = bullet-first excerpts.")
2878
+ task: z23.string().min(1).describe("What you are about to do, in 1\u20132 sentences. Used to rank relevant memories."),
2879
+ files: z23.array(z23.string()).default([]).describe("Optional: files you are about to edit \u2014 surfaces anchored memories."),
2880
+ limit: z23.number().int().positive().max(30).default(8).describe("Cap on returned memories."),
2881
+ min_semantic_score: z23.number().min(0).max(1).default(0.25).describe("Drop weakly-related semantic hits below this cosine threshold."),
2882
+ format: z23.enum(["full", "compact", "actions"]).default("full").describe("'compact' = id + 1-line summary; 'full' = complete bodies; 'actions' = bullet-first excerpts.")
2859
2883
  };
2860
2884
  async function memRelevantTo(input, ctx) {
2861
2885
  const briefingInput = {
@@ -2885,14 +2909,14 @@ async function memRelevantTo(input, ctx) {
2885
2909
  }
2886
2910
 
2887
2911
  // src/tools/code-search.ts
2888
- import { z as z25 } from "zod";
2912
+ import { z as z24 } from "zod";
2889
2913
  import { loadCodeMap as loadCodeMap3 } from "@hivelore/core";
2890
2914
  var CodeSearchInputSchema = {
2891
- query: z25.string().min(1).describe(
2915
+ query: z24.string().min(1).describe(
2892
2916
  "Natural-language description of what you are looking for in the codebase (e.g. 'function that hashes passwords', 'JWT signing logic', 'route registration')."
2893
2917
  ),
2894
- k: z25.number().int().positive().max(50).default(5).describe("Number of top hits to return."),
2895
- min_score: z25.number().min(0).max(1).default(0.2).describe(
2918
+ k: z24.number().int().positive().max(50).default(5).describe("Number of top hits to return."),
2919
+ min_score: z24.number().min(0).max(1).default(0.2).describe(
2896
2920
  "Minimum cosine similarity. Hits below this threshold are dropped to avoid noise. Try 0.3+ for stricter matching."
2897
2921
  )
2898
2922
  };
@@ -2934,155 +2958,39 @@ async function codeSearch(input, ctx) {
2934
2958
  };
2935
2959
  }
2936
2960
 
2937
- // src/tools/why-this-file.ts
2938
- import { existsSync as existsSync25 } from "fs";
2939
- import { spawn } from "child_process";
2940
- import path13 from "path";
2941
- import {
2942
- deriveConfidence as deriveConfidence5,
2943
- getUsage as getUsage7,
2944
- loadCodeMap as loadCodeMap4,
2945
- loadMemoriesFromDir as loadMemoriesFromDir19,
2946
- loadUsageIndex as loadUsageIndex9,
2947
- memoryMatchesAnchorPaths as memoryMatchesAnchorPaths3
2948
- } from "@hivelore/core";
2949
- import { z as z26 } from "zod";
2950
- var WhyThisFileInputSchema = {
2951
- path: z26.string().min(1).describe(
2952
- "Project-relative path to the file you want context on (e.g. 'packages/mcp/src/tools/mem-save.ts')."
2953
- ),
2954
- git_log_limit: z26.number().int().positive().max(20).default(5).describe("How many recent commits touching this file to include."),
2955
- memory_limit: z26.number().int().positive().max(20).default(5).describe("Cap on memories anchored to this path.")
2956
- };
2957
- async function whyThisFile(input, ctx) {
2958
- const fileExists = existsSync25(path13.join(ctx.paths.root, input.path));
2959
- const [commits, memories, codeMap] = await Promise.all([
2960
- runGitLog(ctx.paths.root, input.path, input.git_log_limit).catch(() => []),
2961
- collectAnchoredMemories(ctx, input.path, input.memory_limit),
2962
- loadCodeMap4(ctx.paths)
2963
- ]);
2964
- const codeMapEntry = codeMap?.files[input.path];
2965
- const hints = [];
2966
- if (!fileExists) {
2967
- hints.push(`File '${input.path}' does not exist on disk \u2014 path may be wrong or file removed.`);
2968
- }
2969
- if (commits.length === 0 && fileExists) {
2970
- hints.push("No git history found \u2014 file may be untracked or git not initialized.");
2971
- }
2972
- if (memories.length === 0 && fileExists) {
2973
- hints.push(
2974
- "No memories anchored here. If you discover something non-obvious while editing, use mem_observe (with where=" + input.path + ") to capture it."
2975
- );
2976
- }
2977
- if (memories.some((m) => m.type === "attempt" || m.type === "gotcha")) {
2978
- hints.push("\u26A0\uFE0F attempt/gotcha memories anchored to this file \u2014 read them BEFORE editing.");
2979
- }
2980
- return {
2981
- file: input.path,
2982
- exists: fileExists,
2983
- recent_commits: commits,
2984
- memories,
2985
- code_map_entry: codeMapEntry ? {
2986
- ...codeMapEntry.summary ? { summary: codeMapEntry.summary } : {},
2987
- loc: codeMapEntry.loc,
2988
- exports: codeMapEntry.exports.map((e) => ({
2989
- name: e.name,
2990
- kind: e.kind,
2991
- line: e.line,
2992
- ...e.description ? { description: e.description } : {}
2993
- }))
2994
- } : null,
2995
- ...hints.length > 0 ? { hints } : {}
2996
- };
2997
- }
2998
- async function collectAnchoredMemories(ctx, filePath, limit) {
2999
- if (!existsSync25(ctx.paths.memoriesDir)) return [];
3000
- const all = await loadMemoriesFromDir19(ctx.paths.memoriesDir);
3001
- const usage = await loadUsageIndex9(ctx.paths);
3002
- const out = [];
3003
- for (const { memory } of all) {
3004
- const fm = memory.frontmatter;
3005
- if (fm.status === "rejected" || fm.status === "deprecated") continue;
3006
- if (fm.type === "session_recap") continue;
3007
- if (!memoryMatchesAnchorPaths3(memory, [filePath])) continue;
3008
- const u = getUsage7(usage, fm.id);
3009
- out.push({
3010
- id: fm.id,
3011
- type: fm.type,
3012
- scope: fm.scope,
3013
- confidence: deriveConfidence5(fm, u),
3014
- body_preview: memory.body.split("\n").slice(0, 6).join("\n")
3015
- });
3016
- if (out.length >= limit) break;
3017
- }
3018
- return out;
3019
- }
3020
- async function runGitLog(cwd, filePath, limit) {
3021
- const sep = "<<HV>>";
3022
- const fmt = `%h${sep}%an${sep}%ar${sep}%s`;
3023
- const output = await runCommand(
3024
- "git",
3025
- ["log", `-n`, String(limit), `--pretty=format:${fmt}`, "--", filePath],
3026
- cwd
3027
- );
3028
- if (!output.trim()) return [];
3029
- return output.split("\n").map((line) => {
3030
- const [sha = "", author = "", relative_date = "", subject = ""] = line.split(sep);
3031
- return { sha, author, relative_date, subject };
3032
- }).filter((c) => c.sha);
3033
- }
3034
- function runCommand(cmd, args, cwd) {
3035
- return new Promise((resolve, reject) => {
3036
- const proc = spawn(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
3037
- let stdout = "";
3038
- let stderr = "";
3039
- proc.stdout.on("data", (chunk) => {
3040
- stdout += chunk.toString();
3041
- });
3042
- proc.stderr.on("data", (chunk) => {
3043
- stderr += chunk.toString();
3044
- });
3045
- proc.on("error", reject);
3046
- proc.on("close", (code) => {
3047
- if (code === 0) resolve(stdout);
3048
- else reject(new Error(stderr || `${cmd} exited with code ${code}`));
3049
- });
3050
- });
3051
- }
3052
-
3053
2961
  // src/tools/anti-patterns-check.ts
3054
- import { existsSync as existsSync26 } from "fs";
2962
+ import { existsSync as existsSync24 } from "fs";
3055
2963
  import {
3056
2964
  addedLinesFromDiff,
3057
2965
  BRIDGE_TARGET_PATH,
3058
2966
  buildDocFrequency,
3059
2967
  CODE_STOPWORDS,
3060
- deriveConfidence as deriveConfidence6,
2968
+ deriveConfidence as deriveConfidence5,
3061
2969
  diffHasDistinctiveOverlap,
3062
- getUsage as getUsage8,
2970
+ getUsage as getUsage7,
3063
2971
  isRetiredMemory as isRetiredMemory2,
3064
- loadMemoriesFromDir as loadMemoriesFromDir20,
3065
- loadUsageIndex as loadUsageIndex10,
2972
+ loadMemoriesFromDir as loadMemoriesFromDir19,
2973
+ loadUsageIndex as loadUsageIndex9,
3066
2974
  literalMatchesAnyToken as literalMatchesAnyToken3,
3067
- memoryMatchesAnchorPaths as memoryMatchesAnchorPaths4,
2975
+ memoryMatchesAnchorPaths as memoryMatchesAnchorPaths3,
3068
2976
  recordPreventionHits,
3069
2977
  runSensors,
3070
2978
  sensorTargetsFromDiff,
3071
2979
  tokenizeQuery as tokenizeQuery3
3072
2980
  } from "@hivelore/core";
3073
- import { z as z27 } from "zod";
2981
+ import { z as z25 } from "zod";
3074
2982
  var AntiPatternsCheckInputSchema = {
3075
- diff: z27.string().optional().describe(
2983
+ diff: z25.string().optional().describe(
3076
2984
  "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."
3077
2985
  ),
3078
- paths: z27.array(z27.string()).default([]).describe(
2986
+ paths: z25.array(z25.string()).default([]).describe(
3079
2987
  "File paths affected by the change. Memories anchored to any of these paths are surfaced regardless of the diff content."
3080
2988
  ),
3081
- limit: z27.number().int().positive().max(20).default(8).describe("Cap on returned warnings."),
3082
- semantic: z27.boolean().default(true).describe(
2989
+ limit: z25.number().int().positive().max(20).default(8).describe("Cap on returned warnings."),
2990
+ semantic: z25.boolean().default(true).describe(
3083
2991
  "When true, also use semantic search (requires @hivelore/embeddings + memory index) to find related anti-patterns."
3084
2992
  ),
3085
- min_semantic_score: z27.number().min(0).max(1).default(0.45).describe(
2993
+ min_semantic_score: z25.number().min(0).max(1).default(0.45).describe(
3086
2994
  "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."
3087
2995
  )
3088
2996
  };
@@ -3166,10 +3074,10 @@ async function antiPatternsCheck(input, ctx) {
3166
3074
  notice: "Nothing to check \u2014 provide either `diff` text or `paths`."
3167
3075
  };
3168
3076
  }
3169
- if (!existsSync26(ctx.paths.memoriesDir)) {
3077
+ if (!existsSync24(ctx.paths.memoriesDir)) {
3170
3078
  return { scanned: 0, warnings: [], notice: "No .ai/memories directory \u2014 nothing to check against." };
3171
3079
  }
3172
- const all = await loadMemoriesFromDir20(ctx.paths.memoriesDir);
3080
+ const all = await loadMemoriesFromDir19(ctx.paths.memoriesDir);
3173
3081
  const minSemanticScore = input.min_semantic_score ?? 0.45;
3174
3082
  const negative = all.filter(({ memory }) => {
3175
3083
  const t = memory.frontmatter.type;
@@ -3180,7 +3088,7 @@ async function antiPatternsCheck(input, ctx) {
3180
3088
  if (negative.length === 0) {
3181
3089
  return { scanned: 0, warnings: [], notice: "No attempt/gotcha memories found yet." };
3182
3090
  }
3183
- const usage = await loadUsageIndex10(ctx.paths);
3091
+ const usage = await loadUsageIndex9(ctx.paths);
3184
3092
  const docFreq = buildDocFrequency(negative.map(({ memory }) => memory.body));
3185
3093
  const seen = /* @__PURE__ */ new Map();
3186
3094
  const upsert = (fm, body, reason, score) => {
@@ -3192,12 +3100,12 @@ async function antiPatternsCheck(input, ctx) {
3192
3100
  }
3193
3101
  return;
3194
3102
  }
3195
- const u = getUsage8(usage, fm.id);
3103
+ const u = getUsage7(usage, fm.id);
3196
3104
  seen.set(fm.id, {
3197
3105
  id: fm.id,
3198
3106
  type: fm.type,
3199
3107
  scope: fm.scope,
3200
- confidence: deriveConfidence6(fm, u),
3108
+ confidence: deriveConfidence5(fm, u),
3201
3109
  body_preview: body.split("\n").slice(0, 5).join("\n").slice(0, 400),
3202
3110
  reasons: [reason],
3203
3111
  tags: fm.tags ?? [],
@@ -3208,7 +3116,7 @@ async function antiPatternsCheck(input, ctx) {
3208
3116
  };
3209
3117
  if (input.paths.length > 0) {
3210
3118
  for (const { memory } of negative) {
3211
- if (memoryMatchesAnchorPaths4(memory, input.paths)) {
3119
+ if (memoryMatchesAnchorPaths3(memory, input.paths)) {
3212
3120
  upsert(memory.frontmatter, memory.body, "anchor");
3213
3121
  }
3214
3122
  }
@@ -3285,19 +3193,19 @@ async function antiPatternsCheck(input, ctx) {
3285
3193
  }
3286
3194
 
3287
3195
  // src/tools/mem-distill.ts
3288
- import { existsSync as existsSync27 } from "fs";
3196
+ import { existsSync as existsSync25 } from "fs";
3289
3197
  import {
3290
- loadMemoriesFromDir as loadMemoriesFromDir21,
3198
+ loadMemoriesFromDir as loadMemoriesFromDir20,
3291
3199
  tokenizeQuery as tokenizeQuery4
3292
3200
  } from "@hivelore/core";
3293
- import { z as z28 } from "zod";
3201
+ import { z as z26 } from "zod";
3294
3202
  var MemDistillInputSchema = {
3295
- since_days: z28.number().int().positive().default(30).describe("Only consider memories created in the last N days."),
3296
- min_cluster: z28.number().int().min(2).default(3).describe("Minimum cluster size to surface."),
3297
- type_filter: z28.enum(["gotcha", "attempt", "all"]).default("gotcha").describe(
3203
+ since_days: z26.number().int().positive().default(30).describe("Only consider memories created in the last N days."),
3204
+ min_cluster: z26.number().int().min(2).default(3).describe("Minimum cluster size to surface."),
3205
+ type_filter: z26.enum(["gotcha", "attempt", "all"]).default("gotcha").describe(
3298
3206
  "Memory type to scan. 'gotcha' targets observe-style discoveries that recur, 'attempt' surfaces failed approaches that repeat, 'all' considers both."
3299
3207
  ),
3300
- scope: z28.enum(["personal", "team", "module", "any"]).default("any").describe("Restrict to a specific scope.")
3208
+ scope: z26.enum(["personal", "team", "module", "any"]).default("any").describe("Restrict to a specific scope.")
3301
3209
  };
3302
3210
  var MS_PER_DAY = 24 * 60 * 60 * 1e3;
3303
3211
  var STOP_WORDS = /* @__PURE__ */ new Set([
@@ -3337,11 +3245,11 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
3337
3245
  "error"
3338
3246
  ]);
3339
3247
  async function memDistill(input, ctx) {
3340
- if (!existsSync27(ctx.paths.memoriesDir)) {
3248
+ if (!existsSync25(ctx.paths.memoriesDir)) {
3341
3249
  return { scanned: 0, singletons: 0, clusters: [], notice: "No .ai/memories directory." };
3342
3250
  }
3343
3251
  const cutoff = Date.now() - input.since_days * MS_PER_DAY;
3344
- const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
3252
+ const all = await loadMemoriesFromDir20(ctx.paths.memoriesDir);
3345
3253
  const candidates = all.filter(({ memory }) => {
3346
3254
  const fm = memory.frontmatter;
3347
3255
  if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
@@ -3444,299 +3352,19 @@ function firstHeading(body) {
3444
3352
  return void 0;
3445
3353
  }
3446
3354
 
3447
- // src/tools/why-this-decision.ts
3448
- import { existsSync as existsSync28 } from "fs";
3449
- import { spawn as spawn2 } from "child_process";
3450
- import {
3451
- deriveConfidence as deriveConfidence7,
3452
- getUsage as getUsage9,
3453
- loadMemoriesFromDir as loadMemoriesFromDir22,
3454
- loadUsageIndex as loadUsageIndex11,
3455
- pathsOverlap as singlePathsOverlap
3456
- } from "@hivelore/core";
3457
- import { z as z29 } from "zod";
3458
- var WhyThisDecisionInputSchema = {
3459
- id: z29.string().min(1).describe("Memory id to inspect (e.g. '2026-04-25-decision-esm-only')."),
3460
- git_log_limit: z29.number().int().positive().max(20).default(5).describe("How many recent commits per anchor path to surface.")
3461
- };
3462
- async function whyThisDecision(input, ctx) {
3463
- if (!existsSync28(ctx.paths.memoriesDir)) {
3464
- return {
3465
- found: false,
3466
- related: [],
3467
- path_neighbors: [],
3468
- recent_commits: [],
3469
- notice: "No .ai/memories directory."
3470
- };
3471
- }
3472
- const all = await loadMemoriesFromDir22(ctx.paths.memoriesDir);
3473
- const usage = await loadUsageIndex11(ctx.paths);
3474
- const target = all.find(({ memory }) => memory.frontmatter.id === input.id);
3475
- if (!target) {
3476
- return {
3477
- found: false,
3478
- related: [],
3479
- path_neighbors: [],
3480
- recent_commits: [],
3481
- notice: `Memory '${input.id}' not found.`
3482
- };
3483
- }
3484
- const fm = target.memory.frontmatter;
3485
- const targetUsage = getUsage9(usage, fm.id);
3486
- const decision = {
3487
- id: fm.id,
3488
- type: fm.type,
3489
- scope: fm.scope,
3490
- status: fm.status,
3491
- confidence: deriveConfidence7(fm, targetUsage),
3492
- body: target.memory.body,
3493
- created_at: fm.created_at
3494
- };
3495
- const relatedSet = new Set(fm.related_ids ?? []);
3496
- const related = [];
3497
- for (const { memory } of all) {
3498
- if (memory.frontmatter.id === fm.id) continue;
3499
- const isExplicit = relatedSet.has(memory.frontmatter.id);
3500
- const isBackLink = (memory.frontmatter.related_ids ?? []).includes(fm.id);
3501
- if (!isExplicit && !isBackLink) continue;
3502
- const u = getUsage9(usage, memory.frontmatter.id);
3503
- related.push({
3504
- id: memory.frontmatter.id,
3505
- type: memory.frontmatter.type,
3506
- scope: memory.frontmatter.scope,
3507
- confidence: deriveConfidence7(memory.frontmatter, u),
3508
- body_preview: memory.body.split("\n").slice(0, 4).join("\n").slice(0, 300),
3509
- relation: isExplicit ? "explicit" : "back-link"
3510
- });
3511
- }
3512
- const targetPaths = fm.anchor.paths;
3513
- const path_neighbors = [];
3514
- if (targetPaths.length > 0) {
3515
- for (const { memory } of all) {
3516
- if (memory.frontmatter.id === fm.id) continue;
3517
- if (relatedSet.has(memory.frontmatter.id)) continue;
3518
- const overlappingPaths = memory.frontmatter.anchor.paths.filter(
3519
- (p) => targetPaths.some((tp) => singlePathsOverlap(p, tp))
3520
- );
3521
- if (overlappingPaths.length === 0) continue;
3522
- const u = getUsage9(usage, memory.frontmatter.id);
3523
- path_neighbors.push({
3524
- id: memory.frontmatter.id,
3525
- type: memory.frontmatter.type,
3526
- scope: memory.frontmatter.scope,
3527
- confidence: deriveConfidence7(memory.frontmatter, u),
3528
- overlap: overlappingPaths,
3529
- body_preview: memory.body.split("\n").slice(0, 3).join("\n").slice(0, 200)
3530
- });
3531
- if (path_neighbors.length >= 10) break;
3532
- }
3533
- }
3534
- const recent_commits = [];
3535
- for (const p of targetPaths.slice(0, 5)) {
3536
- try {
3537
- const commits = await runGitLog2(ctx.paths.root, p, input.git_log_limit);
3538
- for (const c of commits) recent_commits.push({ path: p, ...c });
3539
- } catch {
3540
- }
3541
- }
3542
- const hints = [];
3543
- if (decision.confidence === "low" || decision.confidence === "stale") {
3544
- hints.push(`\u26A0\uFE0F Confidence is ${decision.confidence}. Verify this decision still applies before quoting it.`);
3545
- }
3546
- if (related.length === 0 && path_neighbors.length === 0 && targetPaths.length === 0) {
3547
- hints.push("No related memories and no anchored paths \u2014 this decision is isolated; consider adding related_ids or paths.");
3548
- }
3549
- if (fm.type !== "decision" && fm.type !== "architecture") {
3550
- hints.push(`Memory type is '${fm.type}', not 'decision'/'architecture' \u2014 output may be less informative.`);
3551
- }
3552
- return {
3553
- found: true,
3554
- decision,
3555
- related,
3556
- path_neighbors,
3557
- recent_commits,
3558
- ...hints.length > 0 ? { hints } : {}
3559
- };
3560
- }
3561
- async function runGitLog2(cwd, filePath, limit) {
3562
- const sep = "<<HV>>";
3563
- const fmt = `%h${sep}%an${sep}%ar${sep}%s`;
3564
- const output = await runCommand2(
3565
- "git",
3566
- ["log", "-n", String(limit), `--pretty=format:${fmt}`, "--", filePath],
3567
- cwd
3568
- );
3569
- if (!output.trim()) return [];
3570
- return output.split("\n").map((line) => {
3571
- const [sha = "", author = "", relative_date = "", subject = ""] = line.split(sep);
3572
- return { sha, author, relative_date, subject };
3573
- }).filter((c) => c.sha);
3574
- }
3575
- function runCommand2(cmd, args, cwd) {
3576
- return new Promise((resolve, reject) => {
3577
- const proc = spawn2(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
3578
- let stdout = "";
3579
- let stderr = "";
3580
- proc.stdout.on("data", (chunk) => {
3581
- stdout += chunk.toString();
3582
- });
3583
- proc.stderr.on("data", (chunk) => {
3584
- stderr += chunk.toString();
3585
- });
3586
- proc.on("error", reject);
3587
- proc.on("close", (code) => {
3588
- if (code === 0) resolve(stdout);
3589
- else reject(new Error(stderr || `${cmd} exited with code ${code}`));
3590
- });
3591
- });
3592
- }
3593
-
3594
- // src/tools/mem-conflicts.ts
3595
- import { existsSync as existsSync29 } from "fs";
3596
- import {
3597
- deriveConfidence as deriveConfidence8,
3598
- getUsage as getUsage10,
3599
- loadMemoriesFromDir as loadMemoriesFromDir23,
3600
- loadUsageIndex as loadUsageIndex12,
3601
- pathsOverlap as pathsOverlap2,
3602
- tokenizeQuery as tokenizeQuery5
3603
- } from "@hivelore/core";
3604
- import { z as z30 } from "zod";
3605
- var MemConflictsInputSchema = {
3606
- id: z30.string().min(1).describe("Memory id to check for conflicts."),
3607
- min_score: z30.number().min(0).max(1).default(0.5).describe("Minimum cosine similarity to consider a memory as a potential conflict (semantic mode)."),
3608
- semantic: z30.boolean().default(true).describe("Use embeddings for similarity. Falls back to keyword overlap when embeddings are not installed.")
3609
- };
3610
- var POSITIVE_PATTERNS = /\b(use|prefer|always|should use|do this|recommended|ok to)\b/i;
3611
- var NEGATIVE_PATTERNS = /\b(do not use|don'?t use|never|avoid|forbidden|deprecated|stop using|do NOT|❌)\b/i;
3612
- async function memConflicts(input, ctx) {
3613
- if (!existsSync29(ctx.paths.memoriesDir)) {
3614
- return { found: false, scanned: 0, conflicts: [], notice: "No .ai/memories directory." };
3615
- }
3616
- const all = await loadMemoriesFromDir23(ctx.paths.memoriesDir);
3617
- const target = all.find(({ memory }) => memory.frontmatter.id === input.id);
3618
- if (!target) {
3619
- return { found: false, scanned: 0, conflicts: [], notice: `Memory '${input.id}' not found.` };
3620
- }
3621
- const usage = await loadUsageIndex12(ctx.paths);
3622
- const others = all.filter(
3623
- ({ memory }) => memory.frontmatter.id !== input.id && memory.frontmatter.type !== "session_recap"
3624
- );
3625
- const simScores = input.semantic ? await trySemanticSimilarities(ctx, target, others) : null;
3626
- const targetText = (target.memory.body + " " + target.memory.frontmatter.tags.join(" ")).toLowerCase();
3627
- const targetTokens = new Set(tokenizeQuery5(targetText));
3628
- const targetPolarity = polarity(targetText);
3629
- const targetPaths = target.memory.frontmatter.anchor.paths;
3630
- const explicitContradicts = extractContradictsTags(target.memory.body);
3631
- const conflicts = [];
3632
- for (const other of others) {
3633
- const fm = other.memory.frontmatter;
3634
- const otherText = (other.memory.body + " " + fm.tags.join(" ")).toLowerCase();
3635
- const reasons = [];
3636
- const sim = simScores?.get(fm.id) ?? null;
3637
- const hasPathOverlap = fm.anchor.paths.some((p) => targetPaths.some((tp) => pathsOverlap2(p, tp)));
3638
- const otherTokens = new Set(tokenizeQuery5(otherText));
3639
- const tokenOverlap = countIntersection(targetTokens, otherTokens);
3640
- const isSemanticNeighbor = sim !== null && sim >= input.min_score;
3641
- if (!hasPathOverlap && tokenOverlap < 4 && !isSemanticNeighbor) continue;
3642
- const otherContradicts = extractContradictsTags(other.memory.body);
3643
- if (explicitContradicts.has(fm.id) || otherContradicts.has(input.id)) {
3644
- reasons.push("explicit-contradiction-tag");
3645
- }
3646
- if (target.memory.frontmatter.status === "validated" && fm.status === "rejected" || target.memory.frontmatter.status === "rejected" && fm.status === "validated") {
3647
- if (tokenOverlap >= 4 || isSemanticNeighbor) reasons.push("opposite-status");
3648
- }
3649
- if (hasPathOverlap) {
3650
- const tType = target.memory.frontmatter.type;
3651
- const oType = fm.type;
3652
- const isAttemptVsRule = tType === "attempt" && (oType === "convention" || oType === "decision") || oType === "attempt" && (tType === "convention" || tType === "decision");
3653
- if (isAttemptVsRule) reasons.push("attempt-vs-convention-same-paths");
3654
- }
3655
- if (isSemanticNeighbor) {
3656
- const otherPolarity = polarity(otherText);
3657
- if (targetPolarity === "positive" && otherPolarity === "negative" || targetPolarity === "negative" && otherPolarity === "positive") {
3658
- reasons.push("polarity-keywords");
3659
- }
3660
- }
3661
- if (reasons.length === 0) continue;
3662
- const u = getUsage10(usage, fm.id);
3663
- conflicts.push({
3664
- id: fm.id,
3665
- type: fm.type,
3666
- scope: fm.scope,
3667
- status: fm.status,
3668
- confidence: deriveConfidence8(fm, u),
3669
- body_preview: other.memory.body.split("\n").slice(0, 4).join("\n").slice(0, 300),
3670
- similarity: sim,
3671
- reasons,
3672
- shared_paths: fm.anchor.paths.filter((p) => targetPaths.some((tp) => pathsOverlap2(p, tp)))
3673
- });
3674
- }
3675
- conflicts.sort((a, b) => {
3676
- const score = (c) => (c.reasons.includes("explicit-contradiction-tag") ? 100 : 0) + (c.reasons.includes("opposite-status") ? 50 : 0) + (c.reasons.includes("attempt-vs-convention-same-paths") ? 25 : 0) + (c.reasons.includes("polarity-keywords") ? 10 : 0) + (c.similarity ?? 0) * 5;
3677
- return score(b) - score(a);
3678
- });
3679
- return {
3680
- found: true,
3681
- target: {
3682
- id: target.memory.frontmatter.id,
3683
- type: target.memory.frontmatter.type,
3684
- status: target.memory.frontmatter.status
3685
- },
3686
- scanned: others.length,
3687
- conflicts: conflicts.slice(0, 10)
3688
- };
3689
- }
3690
- function polarity(text) {
3691
- const neg = NEGATIVE_PATTERNS.test(text);
3692
- const pos = POSITIVE_PATTERNS.test(text);
3693
- if (neg && !pos) return "negative";
3694
- if (pos && !neg) return "positive";
3695
- return "neutral";
3696
- }
3697
- function extractContradictsTags(body) {
3698
- const out = /* @__PURE__ */ new Set();
3699
- for (const m of body.matchAll(/#contradicts:([\w-]+)/g)) {
3700
- if (m[1]) out.add(m[1]);
3701
- }
3702
- return out;
3703
- }
3704
- function countIntersection(a, b) {
3705
- let n = 0;
3706
- for (const x of a) if (b.has(x)) n++;
3707
- return n;
3708
- }
3709
- async function trySemanticSimilarities(ctx, target, others) {
3710
- let mod;
3711
- try {
3712
- mod = await import("@hivelore/embeddings");
3713
- } catch {
3714
- return null;
3715
- }
3716
- const result = await mod.semanticSearch(
3717
- ctx.paths,
3718
- target.memory.body,
3719
- { limit: others.length }
3720
- );
3721
- if (!result) return null;
3722
- const map = /* @__PURE__ */ new Map();
3723
- for (const hit of result.hits) map.set(hit.id, hit.score);
3724
- return map;
3725
- }
3726
-
3727
3355
  // src/tools/precommit-check.ts
3728
- import { pathsOverlap as pathsOverlap3 } from "@hivelore/core";
3729
- import { z as z31 } from "zod";
3356
+ import { pathsOverlap as pathsOverlap2 } from "@hivelore/core";
3357
+ import { z as z27 } from "zod";
3730
3358
  var PreCommitCheckInputSchema = {
3731
- diff: z31.string().optional().describe(
3359
+ diff: z27.string().optional().describe(
3732
3360
  "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`."
3733
3361
  ),
3734
- paths: z31.array(z31.string()).default([]).describe("Project-relative paths affected by the change. At least one of `diff` or `paths` should be provided."),
3735
- block_on: z31.enum(["any", "high-confidence", "never"]).default("high-confidence").describe(
3362
+ paths: z27.array(z27.string()).default([]).describe("Project-relative paths affected by the change. At least one of `diff` or `paths` should be provided."),
3363
+ block_on: z27.enum(["any", "high-confidence", "never"]).default("high-confidence").describe(
3736
3364
  "When to set should_block=true: 'any' = any warning blocks; 'high-confidence' = only warnings from authoritative/trusted memories block; 'never' = report only, never block."
3737
3365
  ),
3738
- semantic: z31.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index)."),
3739
- anchored_blocks: z31.boolean().default(false).describe(
3366
+ semantic: z27.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index)."),
3367
+ anchored_blocks: z27.boolean().default(false).describe(
3740
3368
  "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."
3741
3369
  )
3742
3370
  };
@@ -3805,7 +3433,7 @@ async function preCommitCheck(input, ctx) {
3805
3433
  }
3806
3434
  function classifyWarning(warning, paths, anchoredBlocks = false) {
3807
3435
  const codeFiles = paths.filter((p) => !p.startsWith(".ai/") && !isHaiveOwnedPath(p));
3808
- const anchorHits = (warning.anchor_paths ?? []).length > 0 ? codeFiles.filter((p) => (warning.anchor_paths ?? []).some((ap) => pathsOverlap3(ap, p))) : [];
3436
+ const anchorHits = (warning.anchor_paths ?? []).length > 0 ? codeFiles.filter((p) => (warning.anchor_paths ?? []).some((ap) => pathsOverlap2(ap, p))) : [];
3809
3437
  const affectedFiles = anchorHits.length > 0 ? anchorHits : codeFiles;
3810
3438
  const repairCommand = repairCommandForWarning(warning, affectedFiles);
3811
3439
  const fileDowngrade = fileTypeDowngradeReason(warning, affectedFiles);
@@ -4041,228 +3669,15 @@ function repairTargetPathForWarning(warning, paths) {
4041
3669
  return usablePaths[0];
4042
3670
  }
4043
3671
 
4044
- // src/tools/pattern-detect.ts
4045
- import { mkdir as mkdir8, writeFile as writeFile15 } from "fs/promises";
4046
- import { existsSync as existsSync30 } from "fs";
4047
- import path14 from "path";
4048
- import { execSync as execSync3 } from "child_process";
4049
- import {
4050
- buildFrontmatter as buildFrontmatter5,
4051
- memoryFilePath as memoryFilePath6,
4052
- readUsageEvents,
4053
- serializeMemory as serializeMemory13
4054
- } from "@hivelore/core";
4055
- import { z as z32 } from "zod";
4056
- var CONFIG_PATTERNS = [
4057
- ".eslintrc",
4058
- "eslint.config",
4059
- "prettier.config",
4060
- ".prettierrc",
4061
- "tsconfig",
4062
- "jsconfig",
4063
- "vitest.config",
4064
- "jest.config",
4065
- ".env.example",
4066
- ".env.defaults",
4067
- "tailwind.config",
4068
- "vite.config",
4069
- "next.config",
4070
- "babel.config",
4071
- "postcss.config",
4072
- "renovate.json",
4073
- "dependabot.yml"
4074
- ];
4075
- var MAX_DIFF_BYTES = 4096;
4076
- var HOT_FILE_MIN = 3;
4077
- var PatternDetectInputSchema = {
4078
- since_days: z32.number().int().min(1).default(7).describe("Look-back window in days for both git history and usage log."),
4079
- dry_run: z32.boolean().default(false).describe("When true, report matches without writing any memory files."),
4080
- scope: z32.enum(["personal", "team"]).default("team").describe("Scope for proposed memories.")
4081
- };
4082
- async function patternDetect(input, ctx) {
4083
- if (!existsSync30(ctx.paths.haiveDir)) {
4084
- return {
4085
- scanned_events: 0,
4086
- matches: [],
4087
- saved: 0,
4088
- saved_ids: [],
4089
- notice: "No .ai/ directory found. Run 'hivelore init' first."
4090
- };
4091
- }
4092
- const matches = [];
4093
- try {
4094
- const changedFiles = gitChangedFiles(ctx.paths.root, input.since_days);
4095
- const configFiles = changedFiles.filter(
4096
- (f) => CONFIG_PATTERNS.some((p) => path14.basename(f.toLowerCase()).includes(p))
4097
- );
4098
- for (const file of configFiles.slice(0, 5)) {
4099
- const diff = gitFileDiff(ctx.paths.root, file, input.since_days);
4100
- if (!diff) continue;
4101
- const parentDir = path14.basename(path14.dirname(file));
4102
- const baseName = path14.basename(file).replace(/\.[^.]+$/, "");
4103
- const slug = `${parentDir}-${baseName}`.replace(/[^a-z0-9]/gi, "-").toLowerCase().slice(0, 40);
4104
- matches.push({
4105
- kind: "config_change",
4106
- signal: `Config file modified: ${file}`,
4107
- proposed_type: "convention",
4108
- proposed_slug: `config-change-${slug}`,
4109
- proposed_body: [
4110
- `# Config change: \`${file}\``,
4111
- "",
4112
- "This configuration file was recently modified. The diff below captures the intent.",
4113
- "Review and update this memory with the **reason** for the change if known.",
4114
- "",
4115
- "```diff",
4116
- diff.slice(0, MAX_DIFF_BYTES),
4117
- "```"
4118
- ].join("\n"),
4119
- anchor_paths: [file]
4120
- });
4121
- }
4122
- } catch {
4123
- }
4124
- const events = await readUsageEvents(ctx.paths);
4125
- const cutoff = Date.now() - input.since_days * 24 * 60 * 60 * 1e3;
4126
- const recent = events.filter((e) => Date.parse(e.at) >= cutoff);
4127
- const pathCounts = /* @__PURE__ */ new Map();
4128
- for (const e of recent) {
4129
- if (!["mem_tried", "mem_observe", "mem_save"].includes(e.tool)) continue;
4130
- if (!e.summary) continue;
4131
- const tokens = e.summary.match(/[^\s"'`,;()[\]{}]+\.[a-zA-Z]{1,6}/g) ?? [];
4132
- for (const t of tokens) {
4133
- const key = t.toLowerCase();
4134
- const existing = pathCounts.get(key);
4135
- if (existing) {
4136
- existing.count++;
4137
- existing.tools.add(e.tool);
4138
- } else {
4139
- pathCounts.set(key, { count: 1, tools: /* @__PURE__ */ new Set([e.tool]) });
4140
- }
4141
- }
4142
- }
4143
- for (const [p, { count, tools }] of pathCounts) {
4144
- if (count < HOT_FILE_MIN) continue;
4145
- const isGotchaSignal = tools.has("mem_tried") || tools.has("mem_observe");
4146
- if (!isGotchaSignal) continue;
4147
- const slug = p.replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").slice(0, 40);
4148
- matches.push({
4149
- kind: "repeated_path",
4150
- signal: `Path '${p}' appears ${count}\xD7 in mem_tried/mem_observe events`,
4151
- proposed_type: "gotcha",
4152
- proposed_slug: `repeated-issue-${slug}`,
4153
- proposed_body: [
4154
- `# Recurring issue near \`${p}\``,
4155
- "",
4156
- `This file appeared ${count} times in failed-approach or observation events over the last ${input.since_days} days. Review the related attempt/gotcha memories and consolidate them into a single authoritative gotcha.`,
4157
- "",
4158
- `**Source signals:** ${[...tools].join(", ")} (${count} events)`
4159
- ].join("\n"),
4160
- anchor_paths: [p]
4161
- });
4162
- }
4163
- for (const [p, { count, tools }] of pathCounts) {
4164
- if (count < HOT_FILE_MIN) continue;
4165
- if (tools.has("mem_tried") || tools.has("mem_observe")) continue;
4166
- if (CONFIG_PATTERNS.some((cp) => path14.basename(p).includes(cp))) continue;
4167
- const slug = p.replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").slice(0, 40);
4168
- matches.push({
4169
- kind: "hot_file",
4170
- signal: `Path '${p}' referenced ${count}\xD7 across mem_save events`,
4171
- proposed_type: "convention",
4172
- proposed_slug: `hot-file-${slug}`,
4173
- proposed_body: [
4174
- `# Frequent edits to \`${p}\``,
4175
- "",
4176
- `This file was referenced ${count} times in memory-saving events over the last ${input.since_days} days \u2014 a signal that a recurring pattern or convention applies here.`,
4177
- "",
4178
- "**Suggested action:** review recent memories anchored to this path and extract the common pattern as a named convention."
4179
- ].join("\n"),
4180
- anchor_paths: [p]
4181
- });
4182
- }
4183
- if (matches.length === 0) {
4184
- return {
4185
- scanned_events: recent.length,
4186
- matches: [],
4187
- saved: 0,
4188
- saved_ids: [],
4189
- notice: `No patterns detected in the last ${input.since_days} days (${recent.length} events scanned).`
4190
- };
4191
- }
4192
- if (input.dry_run) {
4193
- return { scanned_events: recent.length, matches, saved: 0, saved_ids: [] };
4194
- }
4195
- const savedIds = [];
4196
- for (const match of matches) {
4197
- try {
4198
- const fm = buildFrontmatter5({
4199
- type: match.proposed_type,
4200
- slug: match.proposed_slug,
4201
- scope: input.scope,
4202
- tags: ["pattern-detect", match.kind],
4203
- paths: match.anchor_paths,
4204
- status: "proposed"
4205
- });
4206
- const file = memoryFilePath6(
4207
- ctx.paths,
4208
- fm.scope === "shared" ? "team" : fm.scope,
4209
- fm.id,
4210
- void 0
4211
- );
4212
- if (existsSync30(file)) continue;
4213
- await mkdir8(path14.dirname(file), { recursive: true });
4214
- await writeFile15(
4215
- file,
4216
- serializeMemory13({ frontmatter: fm, body: match.proposed_body }),
4217
- "utf8"
4218
- );
4219
- savedIds.push(fm.id);
4220
- } catch {
4221
- }
4222
- }
4223
- return {
4224
- scanned_events: recent.length,
4225
- matches,
4226
- saved: savedIds.length,
4227
- saved_ids: savedIds
4228
- };
4229
- }
4230
- function gitChangedFiles(root, sinceDays) {
4231
- try {
4232
- const out = execSync3(
4233
- `git log --name-only --pretty="" --diff-filter=AM --since="${sinceDays} days ago"`,
4234
- { cwd: root, encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }
4235
- );
4236
- return [...new Set(out.split("\n").map((l) => l.trim()).filter(Boolean))];
4237
- } catch {
4238
- return [];
4239
- }
4240
- }
4241
- function gitFileDiff(root, file, sinceDays) {
4242
- try {
4243
- const out = execSync3(
4244
- `git log -p --follow --since="${sinceDays} days ago" -- "${file}"`,
4245
- { cwd: root, encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }
4246
- );
4247
- if (!out.trim()) return null;
4248
- const diffLines = out.split("\n").filter(
4249
- (l) => l.startsWith("+") || l.startsWith("-") || l.startsWith("@@") || l.startsWith("diff")
4250
- );
4251
- return diffLines.join("\n").slice(0, MAX_DIFF_BYTES) || null;
4252
- } catch {
4253
- return null;
4254
- }
4255
- }
4256
-
4257
3672
  // src/tools/mem-conflict-candidates.ts
4258
- import { existsSync as existsSync31 } from "fs";
3673
+ import { existsSync as existsSync26 } from "fs";
4259
3674
  import {
4260
3675
  findLexicalConflictPairs,
4261
3676
  findTopicStatusConflictPairs,
4262
- loadMemoriesFromDir as loadMemoriesFromDir24,
3677
+ loadMemoriesFromDir as loadMemoriesFromDir21,
4263
3678
  planConflictResolution
4264
3679
  } from "@hivelore/core";
4265
- import { z as z33 } from "zod";
3680
+ import { z as z28 } from "zod";
4266
3681
  function suggestResolution(byId, idA, idB) {
4267
3682
  const a = byId.get(idA);
4268
3683
  const b = byId.get(idB);
@@ -4276,17 +3691,17 @@ function suggestResolution(byId, idA, idB) {
4276
3691
  };
4277
3692
  }
4278
3693
  var MemConflictCandidatesInputSchema = {
4279
- since_days: z33.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
4280
- types: z33.array(z33.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
4281
- min_jaccard: z33.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
4282
- max_pairs: z33.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
4283
- max_scan: z33.number().int().positive().max(2e3).default(500).describe("Maximum memories sampled for O(n\xB2) scan \u2014 excess dropped after chronological sort."),
4284
- max_topic_pairs: z33.number().int().positive().max(100).default(20).describe(
3694
+ since_days: z28.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
3695
+ types: z28.array(z28.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
3696
+ min_jaccard: z28.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
3697
+ max_pairs: z28.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
3698
+ 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."),
3699
+ max_topic_pairs: z28.number().int().positive().max(100).default(20).describe(
4285
3700
  "Cap for extra signal: memories sharing the same topic with validated vs rejected status."
4286
3701
  )
4287
3702
  };
4288
3703
  async function memConflictCandidates(input, ctx) {
4289
- if (!existsSync31(ctx.paths.memoriesDir)) {
3704
+ if (!existsSync26(ctx.paths.memoriesDir)) {
4290
3705
  return {
4291
3706
  pairs: [],
4292
3707
  topic_status_pairs: [],
@@ -4295,7 +3710,7 @@ async function memConflictCandidates(input, ctx) {
4295
3710
  notice: "No .ai/memories directory."
4296
3711
  };
4297
3712
  }
4298
- const all = await loadMemoriesFromDir24(ctx.paths.memoriesDir);
3713
+ const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
4299
3714
  const byId = new Map(all.map((m) => [m.memory.frontmatter.id, m]));
4300
3715
  const { pairs, scanned, truncated } = findLexicalConflictPairs(all, {
4301
3716
  sinceDays: input.since_days,
@@ -4325,9 +3740,9 @@ async function memConflictCandidates(input, ctx) {
4325
3740
 
4326
3741
  // src/tools/mem-resolve-project.ts
4327
3742
  import { resolveProjectInfo } from "@hivelore/core";
4328
- import { z as z34 } from "zod";
3743
+ import { z as z29 } from "zod";
4329
3744
  var MemResolveProjectInputSchema = {
4330
- cwd: z34.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
3745
+ cwd: z29.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
4331
3746
  };
4332
3747
  async function memResolveProject(input, _ctx) {
4333
3748
  void _ctx;
@@ -4341,10 +3756,10 @@ async function memResolveProject(input, _ctx) {
4341
3756
 
4342
3757
  // src/tools/mem-suggest-topic.ts
4343
3758
  import { MemoryTypeSchema, suggestTopicKey } from "@hivelore/core";
4344
- import { z as z35 } from "zod";
3759
+ import { z as z30 } from "zod";
4345
3760
  var MemSuggestTopicInputSchema = {
4346
3761
  type: MemoryTypeSchema.describe("Memory kind \u2014 drives the suggested topic family."),
4347
- title: z35.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
3762
+ title: z30.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
4348
3763
  };
4349
3764
  async function memSuggestTopic(input, _ctx) {
4350
3765
  void _ctx;
@@ -4353,19 +3768,19 @@ async function memSuggestTopic(input, _ctx) {
4353
3768
  }
4354
3769
 
4355
3770
  // src/tools/mem-timeline.ts
4356
- import { existsSync as existsSync32 } from "fs";
4357
- import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir25 } from "@hivelore/core";
4358
- import { z as z36 } from "zod";
3771
+ import { existsSync as existsSync27 } from "fs";
3772
+ import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir22 } from "@hivelore/core";
3773
+ import { z as z31 } from "zod";
4359
3774
  var MemTimelineInputSchema = {
4360
- memory_id: z36.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
4361
- topic: z36.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
4362
- limit: z36.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
3775
+ memory_id: z31.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
3776
+ topic: z31.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
3777
+ limit: z31.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
4363
3778
  };
4364
3779
  async function memTimeline(input, ctx) {
4365
- if (!existsSync32(ctx.paths.memoriesDir)) {
3780
+ if (!existsSync27(ctx.paths.memoriesDir)) {
4366
3781
  return { entries: [], total: 0, notice: "No .ai/memories directory." };
4367
3782
  }
4368
- const all = await loadMemoriesFromDir25(ctx.paths.memoriesDir);
3783
+ const all = await loadMemoriesFromDir22(ctx.paths.memoriesDir);
4369
3784
  const { entries, notice } = collectTimelineEntries(all, {
4370
3785
  memoryId: input.memory_id,
4371
3786
  topic: input.topic,
@@ -4374,47 +3789,13 @@ async function memTimeline(input, ctx) {
4374
3789
  return { entries, total: entries.length, notice };
4375
3790
  }
4376
3791
 
4377
- // src/tools/runtime-journal-append.ts
4378
- import { appendRuntimeJournalEntry as appendRuntimeJournalEntry2 } from "@hivelore/core";
4379
- import { z as z37 } from "zod";
4380
- var RuntimeJournalAppendInputSchema = {
4381
- message: z37.string().min(1).describe("Short line to append to the runtime session journal"),
4382
- kind: z37.enum(["note", "session_end", "mcp"]).default("note"),
4383
- tool: z37.string().optional().describe("When kind=mcp, which tool name (optional)")
4384
- };
4385
- async function runtimeJournalAppend(input, ctx) {
4386
- await appendRuntimeJournalEntry2(ctx.paths, {
4387
- kind: input.kind,
4388
- message: input.message,
4389
- ...input.tool ? { tool: input.tool } : {}
4390
- });
4391
- return {
4392
- ok: true,
4393
- path_hint: `${ctx.paths.runtimeDir}/session-journal.ndjson`
4394
- };
4395
- }
4396
-
4397
- // src/tools/runtime-journal-tail.ts
4398
- import { readRuntimeJournalTail } from "@hivelore/core";
4399
- import { z as z38 } from "zod";
4400
- var RuntimeJournalTailInputSchema = {
4401
- limit: z38.number().int().positive().max(500).default(30).describe("Last N journal entries to return")
4402
- };
4403
- async function runtimeJournalTail(input, ctx) {
4404
- const entries = await readRuntimeJournalTail(ctx.paths, input.limit);
4405
- if (entries.length === 0) {
4406
- return { entries: [], empty: true };
4407
- }
4408
- return { entries };
4409
- }
4410
-
4411
3792
  // src/prompts/bootstrap-project.ts
4412
- import { z as z39 } from "zod";
3793
+ import { z as z32 } from "zod";
4413
3794
  var BootstrapProjectArgsSchema = {
4414
- module: z39.string().optional().describe(
3795
+ module: z32.string().optional().describe(
4415
3796
  "Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
4416
3797
  ),
4417
- focus: z39.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
3798
+ focus: z32.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
4418
3799
  };
4419
3800
  var ROOT_TEMPLATE = `# Project context
4420
3801
 
@@ -4497,16 +3878,16 @@ ${template}\`\`\`
4497
3878
 
4498
3879
  // src/prompts/bootstrap-repo.ts
4499
3880
  import { readFile as readFile7, readdir as readdir5 } from "fs/promises";
4500
- import { existsSync as existsSync33 } from "fs";
3881
+ import { existsSync as existsSync28 } from "fs";
4501
3882
  import {
4502
3883
  assessBootstrapState as assessBootstrapState2,
4503
- loadCodeMap as loadCodeMap5,
4504
- loadMemoriesFromDir as loadMemoriesFromDir26,
3884
+ loadCodeMap as loadCodeMap4,
3885
+ loadMemoriesFromDir as loadMemoriesFromDir23,
4505
3886
  renderBootstrapChecklist as renderBootstrapChecklist2
4506
3887
  } from "@hivelore/core";
4507
- import { z as z40 } from "zod";
3888
+ import { z as z33 } from "zod";
4508
3889
  var BootstrapRepoArgsSchema = {
4509
- focus: z40.string().optional().describe("Optional area to emphasize first (e.g. 'payments', 'auth').")
3890
+ focus: z33.string().optional().describe("Optional area to emphasize first (e.g. 'payments', 'auth').")
4510
3891
  };
4511
3892
  async function currentAssessment(ctx) {
4512
3893
  let projectContextRaw = "";
@@ -4514,8 +3895,8 @@ async function currentAssessment(ctx) {
4514
3895
  projectContextRaw = await readFile7(ctx.paths.projectContext, "utf8");
4515
3896
  } catch {
4516
3897
  }
4517
- const memories = existsSync33(ctx.paths.memoriesDir) ? await loadMemoriesFromDir26(ctx.paths.memoriesDir) : [];
4518
- const codeMap = await loadCodeMap5(ctx.paths);
3898
+ const memories = existsSync28(ctx.paths.memoriesDir) ? await loadMemoriesFromDir23(ctx.paths.memoriesDir) : [];
3899
+ const codeMap = await loadCodeMap4(ctx.paths);
4519
3900
  let existingModules = [];
4520
3901
  try {
4521
3902
  const entries = await readdir5(ctx.paths.modulesContextDir, { withFileTypes: true });
@@ -4582,10 +3963,10 @@ Main code areas detected: ${areas}
4582
3963
  }
4583
3964
 
4584
3965
  // src/prompts/post-task.ts
4585
- import { z as z41 } from "zod";
3966
+ import { z as z34 } from "zod";
4586
3967
  var PostTaskArgsSchema = {
4587
- task_summary: z41.string().optional().describe("One sentence describing what you just did"),
4588
- files_touched: z41.array(z41.string()).optional().describe("Files you created or modified during the task")
3968
+ task_summary: z34.string().optional().describe("One sentence describing what you just did"),
3969
+ files_touched: z34.array(z34.string()).optional().describe("Files you created or modified during the task")
4589
3970
  };
4590
3971
  function postTaskPrompt(args, ctx) {
4591
3972
  const taskLine = args.task_summary ? `
@@ -4682,12 +4063,12 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
4682
4063
  }
4683
4064
 
4684
4065
  // src/prompts/import-docs.ts
4685
- import { z as z42 } from "zod";
4066
+ import { z as z35 } from "zod";
4686
4067
  var ImportDocsArgsSchema = {
4687
- content: z42.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
4688
- source: z42.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
4689
- scope: z42.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
4690
- dry_run: z42.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
4068
+ content: z35.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
4069
+ source: z35.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
4070
+ scope: z35.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
4071
+ dry_run: z35.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
4691
4072
  };
4692
4073
  function importDocsPrompt(args, ctx) {
4693
4074
  const sourceLine = args.source ? `
@@ -4753,7 +4134,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
4753
4134
  // src/server.ts
4754
4135
  import { hasRecentBriefingMarker, loadConfigSync } from "@hivelore/core";
4755
4136
  var SERVER_NAME = "hivelore";
4756
- var SERVER_VERSION = "0.31.0";
4137
+ var SERVER_VERSION = "0.34.1";
4757
4138
  function jsonResult(data) {
4758
4139
  return {
4759
4140
  content: [
@@ -4801,14 +4182,7 @@ var MAINTENANCE_PROFILE_TOOLS = [
4801
4182
  "ingest_findings"
4802
4183
  ];
4803
4184
  var EXPERIMENTAL_PROFILE_TOOLS = [
4804
- ...MAINTENANCE_PROFILE_TOOLS,
4805
- "mem_observe",
4806
- "why_this_file",
4807
- "why_this_decision",
4808
- "mem_conflicts_with",
4809
- "pattern_detect",
4810
- "runtime_journal_append",
4811
- "runtime_journal_tail"
4185
+ ...MAINTENANCE_PROFILE_TOOLS
4812
4186
  ];
4813
4187
  var TOOL_PROFILES = {
4814
4188
  enforcement: new Set(ENFORCEMENT_PROFILE_TOOLS),
@@ -4823,7 +4197,6 @@ var BRIEFING_TOOLS = /* @__PURE__ */ new Set(["get_briefing", "mem_relevant_to"]
4823
4197
  var MUTATING_TOOLS = /* @__PURE__ */ new Set([
4824
4198
  "mem_save",
4825
4199
  "mem_tried",
4826
- "mem_observe",
4827
4200
  "mem_session_end",
4828
4201
  "bootstrap_project_save",
4829
4202
  "mem_update",
@@ -4831,8 +4204,6 @@ var MUTATING_TOOLS = /* @__PURE__ */ new Set([
4831
4204
  "mem_reject",
4832
4205
  "mem_delete",
4833
4206
  "mem_feedback",
4834
- "runtime_journal_append",
4835
- "pattern_detect",
4836
4207
  "ingest_findings",
4837
4208
  "propose_sensor"
4838
4209
  ]);
@@ -4895,7 +4266,7 @@ function createHaiveServer(options = {}) {
4895
4266
  " - A domain term and what it means in this codebase",
4896
4267
  "",
4897
4268
  "DO NOT USE for failed approaches \u2192 use mem_tried instead (better structure).",
4898
- "DO NOT USE for code discoveries during exploration \u2192 use mem_observe instead.",
4269
+ "For reactive code discoveries during exploration, prefer a compact gotcha via mem_save.",
4899
4270
  "",
4900
4271
  "PARAMETERS:",
4901
4272
  " type \u2014 convention | decision | gotcha | architecture | glossary | attempt",
@@ -5022,38 +4393,6 @@ function createHaiveServer(options = {}) {
5022
4393
  return jsonResult(await ingestFindings(input, context));
5023
4394
  }
5024
4395
  );
5025
- registerTool(
5026
- "mem_observe",
5027
- [
5028
- "Capture a code-level discovery made WHILE READING existing code.",
5029
- "",
5030
- "USE THIS when you read a file and spot something the team may not know about:",
5031
- " - A bug or race condition hiding in the code",
5032
- " - A security gap or missing validation",
5033
- " - An inconsistency between two files",
5034
- " - A missing configuration or environment variable",
5035
- " - Anything that could silently break in production",
5036
- "",
5037
- "DIFFERENCE from mem_save: mem_observe is for REACTIVE discoveries during code",
5038
- "reading. mem_save is for deliberate knowledge capture (conventions, decisions).",
5039
- "",
5040
- "Auto-validated, anchored to file paths for staleness detection.",
5041
- "",
5042
- "PARAMETERS:",
5043
- " what \u2014 one-line title (e.g. 'MobilePaymentController: duplicate @RequestBody')",
5044
- " where \u2014 file path(s) where the issue lives",
5045
- " impact \u2014 what breaks or could break because of this",
5046
- " fix \u2014 suggested fix (optional)",
5047
- " scope \u2014 team (default, since discoveries benefit everyone)",
5048
- "",
5049
- "RETURNS: { id, file_path }"
5050
- ].join("\n"),
5051
- MemObserveInputSchema,
5052
- async (input) => {
5053
- tracker.record("mem_observe", input.where);
5054
- return jsonResult(await memObserve(input, context));
5055
- }
5056
- );
5057
4396
  registerTool(
5058
4397
  "mem_session_end",
5059
4398
  [
@@ -5513,26 +4852,6 @@ function createHaiveServer(options = {}) {
5513
4852
  return jsonResult(await codeSearch(input, context));
5514
4853
  }
5515
4854
  );
5516
- registerTool(
5517
- "why_this_file",
5518
- [
5519
- "One-shot file-context lookup: combines recent git history, memories anchored",
5520
- "to the path, and the code-map entry. Answers 'why is this file the way it is?'",
5521
- "in a single call instead of 3-4 manual ones.",
5522
- "",
5523
- "PARAMETERS:",
5524
- " path \u2014 project-relative path (required)",
5525
- " git_log_limit \u2014 recent commits to include (default 5)",
5526
- " memory_limit \u2014 anchored memories cap (default 5)",
5527
- "",
5528
- "RETURNS: { file, exists, recent_commits: [...], memories: [...], code_map_entry, hints? }"
5529
- ].join("\n"),
5530
- WhyThisFileInputSchema,
5531
- async (input) => {
5532
- tracker.record("why_this_file", input.path);
5533
- return jsonResult(await whyThisFile(input, context));
5534
- }
5535
- );
5536
4855
  registerTool(
5537
4856
  "anti_patterns_check",
5538
4857
  [
@@ -5582,54 +4901,6 @@ function createHaiveServer(options = {}) {
5582
4901
  return jsonResult(await memDistill(input, context));
5583
4902
  }
5584
4903
  );
5585
- registerTool(
5586
- "why_this_decision",
5587
- [
5588
- "Trace the genealogy of a memory (especially decision/architecture):",
5589
- "the memory itself + memories explicitly linked via related_ids + memories",
5590
- "anchored to overlapping paths + recent commits touching those paths.",
5591
- "",
5592
- "USE WHEN you find a memory and need to understand WHY it was made and",
5593
- "what surrounds it. One call instead of 4-5 manual lookups.",
5594
- "",
5595
- "PARAMETERS:",
5596
- " id \u2014 memory id (required)",
5597
- " git_log_limit \u2014 how many recent commits per anchor path (default 5)",
5598
- "",
5599
- "RETURNS: { decision, related: [...], path_neighbors: [...], recent_commits: [...] }"
5600
- ].join("\n"),
5601
- WhyThisDecisionInputSchema,
5602
- async (input) => {
5603
- tracker.record("why_this_decision", input.id);
5604
- return jsonResult(await whyThisDecision(input, context));
5605
- }
5606
- );
5607
- registerTool(
5608
- "mem_conflicts_with",
5609
- [
5610
- "Detect memories that potentially CONTRADICT a given memory.",
5611
- "",
5612
- "USE BEFORE relying on a memory's advice \u2014 surfaces 'another memory says",
5613
- "the opposite'. Detection uses several heuristics layered together:",
5614
- "",
5615
- " 1. Opposite status \u2014 validated vs rejected on overlapping topic",
5616
- " 2. attempt-vs-convention on overlapping anchor paths",
5617
- " 3. Polarity keywords \u2014 'use X' vs 'do not use X' among semantic neighbors",
5618
- " 4. Explicit #contradicts:<id> tags in either body",
5619
- "",
5620
- "PARAMETERS:",
5621
- " id \u2014 memory id to check (required)",
5622
- " min_score \u2014 minimum cosine similarity for semantic neighbors (default 0.5)",
5623
- " semantic \u2014 use embeddings (default true)",
5624
- "",
5625
- "RETURNS: { found, target, scanned, conflicts: [{ id, reasons, similarity, ... }] }"
5626
- ].join("\n"),
5627
- MemConflictsInputSchema,
5628
- async (input) => {
5629
- tracker.record("mem_conflicts_with", input.id);
5630
- return jsonResult(await memConflicts(input, context));
5631
- }
5632
- );
5633
4904
  registerTool(
5634
4905
  "mem_conflict_candidates",
5635
4906
  [
@@ -5638,7 +4909,7 @@ function createHaiveServer(options = {}) {
5638
4909
  " 1. Lexical similarity (Jaccard) on decision/architecture-like pairs",
5639
4910
  " 2. Same frontmatter.topic with validated vs rejected \u2014 quick human-review signal",
5640
4911
  "",
5641
- "Advisory only \u2014 follow with mem_conflicts_with on specific ids.",
4912
+ "Advisory only \u2014 review the listed candidate ids, then resolve with mem_update/mem_delete.",
5642
4913
  "",
5643
4914
  "PARAMETERS:",
5644
4915
  " since_days, types, min_jaccard, max_pairs, max_scan, max_topic_pairs",
@@ -5651,32 +4922,6 @@ function createHaiveServer(options = {}) {
5651
4922
  return jsonResult(await memConflictCandidates(input, context));
5652
4923
  }
5653
4924
  );
5654
- registerTool(
5655
- "runtime_journal_append",
5656
- [
5657
- "Append one line to `.ai/.runtime/session-journal.ndjson` \u2014 machine-local session continuity.",
5658
- "",
5659
- "Does NOT replace team memories; complements mem_session_end recaps for local traces.",
5660
- "",
5661
- "PARAMETERS: message, kind (note|session_end|mcp), optional tool",
5662
- "",
5663
- "RETURNS: { ok, path_hint }"
5664
- ].join("\n"),
5665
- RuntimeJournalAppendInputSchema,
5666
- async (input) => jsonResult(await runtimeJournalAppend(input, context))
5667
- );
5668
- registerTool(
5669
- "runtime_journal_tail",
5670
- [
5671
- "Read the last N entries from the runtime session journal (parsed JSON lines).",
5672
- "",
5673
- "PARAMETERS: limit (default 30, max 500)",
5674
- "",
5675
- "RETURNS: { entries: [...], empty?: true }"
5676
- ].join("\n"),
5677
- RuntimeJournalTailInputSchema,
5678
- async (input) => jsonResult(await runtimeJournalTail(input, context))
5679
- );
5680
4925
  registerTool(
5681
4926
  "pre_commit_check",
5682
4927
  [
@@ -5703,37 +4948,6 @@ function createHaiveServer(options = {}) {
5703
4948
  return jsonResult(await preCommitCheck(input, context));
5704
4949
  }
5705
4950
  );
5706
- registerTool(
5707
- "pattern_detect",
5708
- [
5709
- "Heuristic memory detector \u2014 finds knowledge worth saving WITHOUT calling an LLM.",
5710
- "",
5711
- "Runs three signals over local git history and the tool-usage log:",
5712
- " 1. CONFIG_CHANGE \u2014 config files modified recently (tsconfig, eslint, prettier, \u2026)",
5713
- " \u2192 proposes a convention memory with the git diff as body.",
5714
- " 2. REPEATED_PATH \u2014 same file appears \u22653\xD7 in mem_tried/mem_observe events",
5715
- " \u2192 proposes a gotcha memory anchored to that path.",
5716
- " 3. HOT_FILE \u2014 source file referenced \u22653\xD7 in writing-tool events",
5717
- " \u2192 proposes a convention memory (frequent edits = pattern emerging).",
5718
- "",
5719
- "Saves memories with status='proposed'. They feed into auto-promote (Phase 4)",
5720
- "or are surfaced in the next post_task distillation for LLM review.",
5721
- "",
5722
- "USE periodically (e.g. end of sprint) or trigger from post-commit hook.",
5723
- "",
5724
- "PARAMETERS:",
5725
- " since_days \u2014 look-back window in days (default 7)",
5726
- " dry_run \u2014 report matches without saving (default false)",
5727
- " scope \u2014 'team' (default) | 'personal'",
5728
- "",
5729
- "RETURNS: { scanned_events, matches: [{kind, signal, proposed_type, \u2026}], saved, saved_ids }"
5730
- ].join("\n"),
5731
- PatternDetectInputSchema,
5732
- async (input) => {
5733
- tracker.record("pattern_detect", `since=${input.since_days}d/dry_run=${input.dry_run}`);
5734
- return jsonResult(await patternDetect(input, context));
5735
- }
5736
- );
5737
4951
  registerTool(
5738
4952
  "mem_diff",
5739
4953
  [
@@ -5784,7 +4998,7 @@ function createHaiveServer(options = {}) {
5784
4998
  [
5785
4999
  "\u2B50 Post-task reflection \u2014 run at the end of every session to capture what you learned:",
5786
5000
  "failed approaches (mem_tried), new conventions/decisions/gotchas (mem_save),",
5787
- "code discoveries (mem_observe), and an end-of-session recap (mem_session_end).",
5001
+ "failed approaches (mem_tried), and an end-of-session recap (mem_session_end).",
5788
5002
  "In autopilot mode a minimal recap saves automatically; calling this produces a richer one."
5789
5003
  ].join(" "),
5790
5004
  PostTaskArgsSchema,