@hivelore/mcp 0.31.0 → 0.33.0

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