@hivelore/mcp 0.30.1 → 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/index.js CHANGED
@@ -1122,94 +1122,45 @@ async function memApprove(input, ctx) {
1122
1122
  }
1123
1123
 
1124
1124
  // src/tools/mem-tried.ts
1125
- import { mkdir as mkdir3, writeFile as writeFile8 } from "fs/promises";
1126
- import { existsSync as existsSync15 } from "fs";
1127
- import path5 from "path";
1125
+ import { mkdir as mkdir3, writeFile as writeFile9 } from "fs/promises";
1126
+ import { existsSync as existsSync16 } from "fs";
1127
+ import path6 from "path";
1128
1128
  import {
1129
1129
  buildFrontmatter as buildFrontmatter2,
1130
1130
  memoryFilePath as memoryFilePath2,
1131
- serializeMemory as serializeMemory7,
1131
+ serializeMemory as serializeMemory8,
1132
1132
  suggestSensorSeed as suggestSensorSeed2
1133
1133
  } from "@hivelore/core";
1134
- import { z as z15 } from "zod";
1135
- var MemTriedInputSchema = {
1136
- what: z15.string().min(1).describe("Brief description of the approach that was tried"),
1137
- why_failed: z15.string().min(1).describe("Why it failed or why it should NOT be used"),
1138
- instead: z15.string().optional().describe("What to use or do instead (recommended alternative)"),
1139
- scope: z15.enum(["personal", "team", "module"]).default("personal").describe("Visibility scope"),
1140
- module: z15.string().optional().describe("Module name (required when scope=module)"),
1141
- tags: z15.array(z15.string()).default([]).describe("Tags for filtering"),
1142
- paths: z15.array(z15.string()).default([]).describe("Anchor file paths this applies to"),
1143
- author: z15.string().optional().describe("Author handle or email")
1144
- };
1145
- async function memTried(input, ctx) {
1146
- if (!existsSync15(ctx.paths.haiveDir)) {
1147
- throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
1148
- }
1149
- const slug = input.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 5).join("-");
1150
- const baseFm = buildFrontmatter2({
1151
- type: "attempt",
1152
- slug,
1153
- scope: input.scope,
1154
- module: input.module,
1155
- tags: input.tags,
1156
- paths: input.paths,
1157
- author: input.author
1158
- });
1159
- const frontmatter = { ...baseFm, status: "validated" };
1160
- const lines = [`# ${input.what}`, ""];
1161
- lines.push(`**Why it failed / do NOT use:** ${input.why_failed}`);
1162
- if (input.instead) {
1163
- lines.push("", `**Instead, use:** ${input.instead}`);
1164
- }
1165
- const body = lines.join("\n") + "\n";
1166
- const file = memoryFilePath2(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
1167
- await mkdir3(path5.dirname(file), { recursive: true });
1168
- if (existsSync15(file)) {
1169
- throw new Error(`Memory already exists at ${file}`);
1170
- }
1171
- await writeFile8(file, serializeMemory7({ frontmatter, body }), "utf8");
1172
- const seed = input.paths.length > 0 ? suggestSensorSeed2(body, input.paths) : null;
1173
- const hint = input.paths.length === 0 ? "No `paths` given, so this attempt is feedforward-only \u2014 it will be briefed but the gate cannot block the repeat. Re-run with `paths` set to the file(s) where the mistake lives, then call propose_sensor to close the loop." : seed ? "This attempt is NOT yet enforced. Call propose_sensor to turn it into a reliable block \u2014 a candidate is pre-filled in proposed_sensor_seed (refine it: pattern = the faulty usage, absent = the correct-usage marker). Hivelore validates the proposal (silent on current code, fires on the bad example) before trusting it to block." : "This attempt is NOT yet enforced and no candidate pattern could be derived from the wording. Call propose_sensor with a discriminating pattern (pattern = faulty usage, absent = correct-usage marker) to close the loop.";
1174
- return {
1175
- id: frontmatter.id,
1176
- scope: frontmatter.scope,
1177
- file_path: file,
1178
- loop_open: true,
1179
- ...seed ? {
1180
- proposed_sensor_seed: {
1181
- pattern: seed.pattern,
1182
- ...seed.absent ? { absent: seed.absent } : {},
1183
- message: seed.message
1184
- }
1185
- } : {},
1186
- hint
1187
- };
1188
- }
1134
+ import { z as z16 } from "zod";
1189
1135
 
1190
1136
  // src/tools/propose-sensor.ts
1191
1137
  import { execSync } from "child_process";
1192
- import { readFile as readFile3, writeFile as writeFile9 } from "fs/promises";
1193
- import { existsSync as existsSync16 } from "fs";
1194
- import path6 from "path";
1138
+ import { readFile as readFile3, writeFile as writeFile8 } from "fs/promises";
1139
+ import { existsSync as existsSync15 } from "fs";
1140
+ import path5 from "path";
1195
1141
  import {
1196
1142
  extractSensorExamples,
1197
1143
  judgeProposedSensor,
1198
1144
  loadMemoriesFromDir as loadMemoriesFromDir13,
1199
- serializeMemory as serializeMemory8
1145
+ serializeMemory as serializeMemory7
1200
1146
  } from "@hivelore/core";
1201
- import { z as z16 } from "zod";
1147
+ import { z as z15 } from "zod";
1202
1148
  var ProposeSensorInputSchema = {
1203
- memory_id: z16.string().min(1).describe("Id of the gotcha/attempt memory this sensor protects."),
1204
- pattern: z16.string().min(1).describe("Regex matching the FAULTY usage (the risky call/token), e.g. 'stripe\\.paymentIntents\\.create'."),
1205
- absent: z16.string().optional().describe(
1149
+ memory_id: z15.string().min(1).describe("Id of the gotcha/attempt memory this sensor protects."),
1150
+ kind: z15.enum(["regex", "shell", "test"]).default("regex").describe(
1151
+ "regex = pattern matched on added diff lines (default). shell|test = a COMMAND the gate runs when the diff touches the sensor's paths \u2014 routes the team's own oracle (an existing test, an invariant script) to this lesson. Command sensors only execute where enforcement.runCommandSensors=true."
1152
+ ),
1153
+ pattern: z15.string().optional().describe("kind=regex: regex matching the FAULTY usage (the risky call/token), e.g. 'stripe\\.paymentIntents\\.create'."),
1154
+ command: z15.string().optional().describe("kind=shell|test: command to execute (e.g. 'npx vitest run tests/payments/refund.spec.ts'). Non-zero exit = the lesson fires."),
1155
+ timeout_ms: z15.number().int().positive().optional().describe("kind=shell|test: max runtime before the executor kills the command (default 120000)."),
1156
+ absent: z15.string().optional().describe(
1206
1157
  "Regex for the CORRECT-usage marker (e.g. 'idempotencyKey'). When it appears in the window around a match, the catch is suppressed \u2014 this is what makes the sensor discriminate the faulty call from the correct one. STRONGLY recommended for 'X without Y' lessons."
1207
1158
  ),
1208
- bad_example: z16.string().optional().describe("A code snippet that SHOULD match \u2014 proves the sensor catches the mistake. If omitted, examples are read from the lesson body."),
1209
- severity: z16.enum(["warn", "block"]).default("block").describe("block = hard-fail the gate (accepted ONLY if it passes self-validation). warn = advisory."),
1210
- message: z16.string().optional().describe("LLM-facing fix message shown when it fires. Defaults to one derived from the lesson."),
1211
- flags: z16.string().optional().describe("Optional regex flags (e.g. 'i' for case-insensitive)."),
1212
- paths: z16.array(z16.string()).default([]).describe("Override scope paths. Defaults to the memory's anchor paths.")
1159
+ bad_example: z15.string().optional().describe("A code snippet that SHOULD match \u2014 proves the sensor catches the mistake. If omitted, examples are read from the lesson body."),
1160
+ severity: z15.enum(["warn", "block"]).default("block").describe("block = hard-fail the gate (accepted ONLY if it passes self-validation). warn = advisory."),
1161
+ message: z15.string().optional().describe("LLM-facing fix message shown when it fires. Defaults to one derived from the lesson."),
1162
+ flags: z15.string().optional().describe("Optional regex flags (e.g. 'i' for case-insensitive)."),
1163
+ paths: z15.array(z15.string()).default([]).describe("Override scope paths. Defaults to the memory's anchor paths.")
1213
1164
  };
1214
1165
  function deriveMessage(body, pattern, absent) {
1215
1166
  const instead = body.match(/\*\*Instead,\s*use:\*\*\s*([^\n]+)/i)?.[1]?.trim();
@@ -1234,8 +1185,8 @@ async function readPresumedCorrectTargets(root, relPaths) {
1234
1185
  continue;
1235
1186
  } catch {
1236
1187
  }
1237
- const abs = path6.resolve(root, rel);
1238
- if (!existsSync16(abs)) continue;
1188
+ const abs = path5.resolve(root, rel);
1189
+ if (!existsSync15(abs)) continue;
1239
1190
  try {
1240
1191
  targets.push({ path: rel, content: await readFile3(abs, "utf8") });
1241
1192
  } catch {
@@ -1243,20 +1194,62 @@ async function readPresumedCorrectTargets(root, relPaths) {
1243
1194
  }
1244
1195
  return targets;
1245
1196
  }
1197
+ function runCommandForValidation(command, root, timeoutMs = 12e4) {
1198
+ try {
1199
+ execSync(`bash -c ${JSON.stringify(command)}`, {
1200
+ cwd: root,
1201
+ timeout: timeoutMs,
1202
+ maxBuffer: 8 * 1024 * 1024,
1203
+ stdio: ["ignore", "pipe", "pipe"]
1204
+ });
1205
+ return { status: "passed", detail: "exit 0" };
1206
+ } catch (err) {
1207
+ const e = err;
1208
+ const out = `${e.stdout?.toString() ?? ""}
1209
+ ${e.stderr?.toString() ?? ""}`.split("\n").filter(Boolean).slice(-8).join("\n");
1210
+ if (e.killed) return { status: "unrunnable", detail: `timed out after ${timeoutMs}ms` };
1211
+ if (e.status === 127 || e.status === 126 || e.code === "ENOENT") {
1212
+ return { status: "unrunnable", detail: e.status === 126 ? "command found but not executable" : "command not found" };
1213
+ }
1214
+ return { status: "failed", detail: out || `exit ${e.status ?? "?"}` };
1215
+ }
1216
+ }
1246
1217
  async function proposeSensor(input, ctx) {
1247
- if (!existsSync16(ctx.paths.memoriesDir)) {
1218
+ if (!existsSync15(ctx.paths.memoriesDir)) {
1248
1219
  throw new Error(`No .ai/memories at ${ctx.paths.root}. Run 'hivelore init' first.`);
1249
1220
  }
1250
- try {
1251
- new RegExp(input.pattern, input.flags ?? "");
1252
- if (input.absent) new RegExp(input.absent, input.flags ?? "");
1253
- } catch (err) {
1221
+ const kind = input.kind ?? "regex";
1222
+ if (kind === "regex") {
1223
+ if (!input.pattern) {
1224
+ return {
1225
+ accepted: false,
1226
+ memory_id: input.memory_id,
1227
+ severity: input.severity,
1228
+ reason: "invalid-regex",
1229
+ guidance: "kind=regex requires a `pattern`.",
1230
+ self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1231
+ };
1232
+ }
1233
+ try {
1234
+ new RegExp(input.pattern, input.flags ?? "");
1235
+ if (input.absent) new RegExp(input.absent, input.flags ?? "");
1236
+ } catch (err) {
1237
+ return {
1238
+ accepted: false,
1239
+ memory_id: input.memory_id,
1240
+ severity: input.severity,
1241
+ reason: "invalid-regex",
1242
+ guidance: `The pattern or absent regex does not compile: ${String(err)}`,
1243
+ self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1244
+ };
1245
+ }
1246
+ } else if (!input.command?.trim()) {
1254
1247
  return {
1255
1248
  accepted: false,
1256
1249
  memory_id: input.memory_id,
1257
1250
  severity: input.severity,
1258
- reason: "invalid-regex",
1259
- guidance: `The pattern or absent regex does not compile: ${String(err)}`,
1251
+ reason: "invalid-command",
1252
+ guidance: "kind=shell|test requires a `command` (the check the gate will execute).",
1260
1253
  self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1261
1254
  };
1262
1255
  }
@@ -1265,6 +1258,43 @@ async function proposeSensor(input, ctx) {
1265
1258
  if (!found) {
1266
1259
  throw new Error(`No memory found with id ${input.memory_id}`);
1267
1260
  }
1261
+ if (kind !== "regex") {
1262
+ const verdictCmd = runCommandForValidation(input.command.trim(), ctx.paths.root, input.timeout_ms);
1263
+ const anchorPathsCmd = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
1264
+ if (verdictCmd.status !== "passed" && input.severity === "block") {
1265
+ return {
1266
+ accepted: false,
1267
+ memory_id: input.memory_id,
1268
+ severity: input.severity,
1269
+ reason: verdictCmd.status === "unrunnable" ? "command-unrunnable" : "fails-on-current",
1270
+ guidance: verdictCmd.status === "unrunnable" ? `The command could not run (${verdictCmd.detail}). Fix the command (or timeout_ms), then re-propose.` : `The command FAILS on the current tree \u2014 the presumed-correct state must pass, or the gate would block every commit. Revert the faulty diff (or fix the check), then re-propose. Output tail:
1271
+ ${verdictCmd.detail}`,
1272
+ self_check: { silent_on_current: false, fires_on_bad: null, fired_on: anchorPathsCmd }
1273
+ };
1274
+ }
1275
+ const sensorCmd = {
1276
+ kind,
1277
+ command: input.command.trim(),
1278
+ ...input.timeout_ms ? { timeout_ms: input.timeout_ms } : {},
1279
+ paths: anchorPathsCmd,
1280
+ message: input.message?.trim() || deriveMessage(found.memory.body, input.command.trim(), void 0),
1281
+ severity: input.severity,
1282
+ autogen: false,
1283
+ last_fired: null
1284
+ };
1285
+ const nextCmd = {
1286
+ frontmatter: { ...found.memory.frontmatter, sensor: sensorCmd },
1287
+ body: found.memory.body
1288
+ };
1289
+ await writeFile8(found.filePath, serializeMemory7(nextCmd), "utf8");
1290
+ return {
1291
+ accepted: true,
1292
+ memory_id: input.memory_id,
1293
+ severity: input.severity,
1294
+ guidance: verdictCmd.status === "passed" ? "Command oracle passes on the current tree; the gate now runs it when the diff touches the sensor's paths (requires enforcement.runCommandSensors=true)." : `Accepted at warn severity, but note: ${verdictCmd.status} on the current tree (${verdictCmd.detail}).`,
1295
+ self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: null, fired_on: [] }
1296
+ };
1297
+ }
1268
1298
  const anchorPaths = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
1269
1299
  const currentTargets = await readPresumedCorrectTargets(ctx.paths.root, anchorPaths);
1270
1300
  const badExamples = [
@@ -1304,7 +1334,7 @@ async function proposeSensor(input, ctx) {
1304
1334
  frontmatter: { ...found.memory.frontmatter, sensor },
1305
1335
  body: found.memory.body
1306
1336
  };
1307
- await writeFile9(found.filePath, serializeMemory8(next), "utf8");
1337
+ await writeFile8(found.filePath, serializeMemory7(next), "utf8");
1308
1338
  return {
1309
1339
  accepted: true,
1310
1340
  memory_id: input.memory_id,
@@ -1314,6 +1344,105 @@ async function proposeSensor(input, ctx) {
1314
1344
  };
1315
1345
  }
1316
1346
 
1347
+ // src/tools/mem-tried.ts
1348
+ var MemTriedInputSchema = {
1349
+ what: z16.string().min(1).describe("Brief description of the approach that was tried"),
1350
+ why_failed: z16.string().min(1).describe("Why it failed or why it should NOT be used"),
1351
+ instead: z16.string().optional().describe("What to use or do instead (recommended alternative)"),
1352
+ scope: z16.enum(["personal", "team", "module"]).default("personal").describe("Visibility scope"),
1353
+ module: z16.string().optional().describe("Module name (required when scope=module)"),
1354
+ tags: z16.array(z16.string()).default([]).describe("Tags for filtering"),
1355
+ paths: z16.array(z16.string()).default([]).describe("Anchor file paths this applies to"),
1356
+ author: z16.string().optional().describe("Author handle or email"),
1357
+ sensor: z16.object({
1358
+ kind: z16.enum(["regex", "shell", "test"]).default("regex").describe("regex pattern, or a shell/test COMMAND the gate executes (behaviour bridge)"),
1359
+ pattern: z16.string().optional().describe("kind=regex: regex matching the FAULTY usage (added diff lines)"),
1360
+ command: z16.string().optional().describe("kind=shell|test: command the gate runs when the diff touches the sensor's paths (non-zero exit = lesson fires)"),
1361
+ timeout_ms: z16.number().int().positive().optional().describe("kind=shell|test: max runtime (default 120000)"),
1362
+ absent: z16.string().optional().describe("kind=regex: regex marking CORRECT usage nearby \u2014 excludes it from firing"),
1363
+ severity: z16.enum(["warn", "block"]).default("block").describe("block = deterministic gate refusal"),
1364
+ message: z16.string().optional().describe("Self-correction message shown when the sensor fires"),
1365
+ bad_example: z16.string().optional().describe("kind=regex: code snippet the sensor MUST fire on (validation)")
1366
+ }).optional().describe(
1367
+ "ONE-SHOT loop close: validate and attach a sensor in the same call (equivalent to a follow-up propose_sensor). Validated against HEAD \u2014 silent on current code, fires on the bad example. If rejected, the attempt is still saved and the verdict tells you how to revise."
1368
+ )
1369
+ };
1370
+ async function memTried(input, ctx) {
1371
+ if (!existsSync16(ctx.paths.haiveDir)) {
1372
+ throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
1373
+ }
1374
+ const slug = input.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 5).join("-");
1375
+ const baseFm = buildFrontmatter2({
1376
+ type: "attempt",
1377
+ slug,
1378
+ scope: input.scope,
1379
+ module: input.module,
1380
+ tags: input.tags,
1381
+ paths: input.paths,
1382
+ author: input.author
1383
+ });
1384
+ const frontmatter = { ...baseFm, status: "validated" };
1385
+ const lines = [`# ${input.what}`, ""];
1386
+ lines.push(`**Why it failed / do NOT use:** ${input.why_failed}`);
1387
+ if (input.instead) {
1388
+ lines.push("", `**Instead, use:** ${input.instead}`);
1389
+ }
1390
+ const body = lines.join("\n") + "\n";
1391
+ const file = memoryFilePath2(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
1392
+ await mkdir3(path6.dirname(file), { recursive: true });
1393
+ if (existsSync16(file)) {
1394
+ throw new Error(`Memory already exists at ${file}`);
1395
+ }
1396
+ await writeFile9(file, serializeMemory8({ frontmatter, body }), "utf8");
1397
+ if (input.sensor) {
1398
+ const verdict = await proposeSensor(
1399
+ {
1400
+ memory_id: frontmatter.id,
1401
+ kind: input.sensor.kind ?? "regex",
1402
+ pattern: input.sensor.pattern,
1403
+ command: input.sensor.command,
1404
+ timeout_ms: input.sensor.timeout_ms,
1405
+ absent: input.sensor.absent,
1406
+ severity: input.sensor.severity ?? "block",
1407
+ message: input.sensor.message,
1408
+ bad_example: input.sensor.bad_example,
1409
+ flags: void 0,
1410
+ paths: []
1411
+ },
1412
+ ctx
1413
+ );
1414
+ return {
1415
+ id: frontmatter.id,
1416
+ scope: frontmatter.scope,
1417
+ file_path: file,
1418
+ loop_open: !verdict.accepted,
1419
+ sensor_result: {
1420
+ accepted: verdict.accepted,
1421
+ severity: input.sensor.severity ?? "block",
1422
+ ...verdict.reason ? { reason: verdict.reason } : {},
1423
+ ...verdict.guidance ? { guidance: verdict.guidance } : {}
1424
+ },
1425
+ hint: verdict.accepted ? "Loop closed: the attempt is saved AND enforced \u2014 the gate now refuses a repeat deterministically." : `Attempt saved, but the sensor was rejected (${verdict.reason}). Revise per the guidance and re-propose with propose_sensor.`
1426
+ };
1427
+ }
1428
+ const seed = input.paths.length > 0 ? suggestSensorSeed2(body, input.paths) : null;
1429
+ const hint = input.paths.length === 0 ? "No `paths` given, so this attempt is feedforward-only \u2014 it will be briefed but the gate cannot block the repeat. Re-run with `paths` set to the file(s) where the mistake lives, then call propose_sensor to close the loop." : seed ? "This attempt is NOT yet enforced. Call propose_sensor (or re-run mem_tried with the one-shot `sensor` parameter) to turn it into a reliable block \u2014 a candidate is pre-filled in proposed_sensor_seed (refine it: pattern = the faulty usage, absent = the correct-usage marker). Hivelore validates the proposal (silent on current code, fires on the bad example) before trusting it to block." : "This attempt is NOT yet enforced and no candidate pattern could be derived from the wording. Call propose_sensor with a discriminating pattern (pattern = faulty usage, absent = correct-usage marker) to close the loop.";
1430
+ return {
1431
+ id: frontmatter.id,
1432
+ scope: frontmatter.scope,
1433
+ file_path: file,
1434
+ loop_open: true,
1435
+ ...seed ? {
1436
+ proposed_sensor_seed: {
1437
+ pattern: seed.pattern,
1438
+ ...seed.absent ? { absent: seed.absent } : {},
1439
+ message: seed.message
1440
+ }
1441
+ } : {},
1442
+ hint
1443
+ };
1444
+ }
1445
+
1317
1446
  // src/tools/ingest-findings.ts
1318
1447
  import { existsSync as existsSync17 } from "fs";
1319
1448
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile10 } from "fs/promises";
@@ -1407,83 +1536,17 @@ async function writeDraft(ctx, draft) {
1407
1536
  return file;
1408
1537
  }
1409
1538
 
1410
- // src/tools/mem-observe.ts
1411
- import { mkdir as mkdir5, writeFile as writeFile11 } from "fs/promises";
1412
- import { existsSync as existsSync18 } from "fs";
1413
- import path8 from "path";
1539
+ // src/tools/mem-session-end.ts
1540
+ import { writeFile as writeFile12, mkdir as mkdir6 } from "fs/promises";
1541
+ import { existsSync as existsSync19 } from "fs";
1542
+ import path9 from "path";
1414
1543
  import {
1415
1544
  buildFrontmatter as buildFrontmatter3,
1416
- isLikelyGuessable,
1545
+ loadMemoriesFromDir as loadMemoriesFromDir15,
1417
1546
  memoryFilePath as memoryFilePath4,
1418
1547
  serializeMemory as serializeMemory10
1419
1548
  } from "@hivelore/core";
1420
1549
  import { z as z18 } from "zod";
1421
- var MemObserveInputSchema = {
1422
- what: z18.string().min(1).describe("Short title: what did you observe? (e.g. 'MobilePaymentController has two @RequestBody on handleWebhook')"),
1423
- where: z18.string().min(1).describe("File path(s) where the issue lives \u2014 be specific"),
1424
- impact: z18.string().min(1).describe("What breaks or could break because of this (e.g. 'Spring MVC rejects the handler at startup')"),
1425
- fix: z18.string().optional().describe("Suggested fix or workaround (optional \u2014 leave empty if unknown)"),
1426
- scope: z18.enum(["personal", "team", "module"]).default("team").describe("Visibility scope \u2014 defaults to team since discoveries benefit everyone"),
1427
- module: z18.string().optional().describe("Module name (required when scope=module)"),
1428
- tags: z18.array(z18.string()).default([]).describe("Tags for filtering"),
1429
- author: z18.string().optional().describe("Author handle or email"),
1430
- force: z18.boolean().default(false).describe(
1431
- "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."
1432
- )
1433
- };
1434
- async function memObserve(input, ctx) {
1435
- if (!existsSync18(ctx.paths.haiveDir)) {
1436
- throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
1437
- }
1438
- const signalText = [input.what, input.impact, input.fix ?? ""].join(" ");
1439
- if (!input.force && isLikelyGuessable(signalText)) {
1440
- return {
1441
- id: "",
1442
- scope: input.scope,
1443
- file_path: "",
1444
- skipped: true,
1445
- 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."
1446
- };
1447
- }
1448
- const slug = input.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 6).join("-");
1449
- const anchorPaths = input.where.split(/[,\n]/).map((s) => s.trim()).filter(Boolean);
1450
- const baseFm = buildFrontmatter3({
1451
- type: "gotcha",
1452
- slug,
1453
- scope: input.scope,
1454
- module: input.module,
1455
- tags: input.tags,
1456
- paths: anchorPaths,
1457
- author: input.author
1458
- });
1459
- const frontmatter = { ...baseFm, status: "validated" };
1460
- const lines = [`# ${input.what}`, ""];
1461
- lines.push(`**Where:** \`${input.where}\``);
1462
- lines.push("", `**Impact:** ${input.impact}`);
1463
- if (input.fix) {
1464
- lines.push("", `**Fix/workaround:** ${input.fix}`);
1465
- }
1466
- const body = lines.join("\n") + "\n";
1467
- const file = memoryFilePath4(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
1468
- await mkdir5(path8.dirname(file), { recursive: true });
1469
- if (existsSync18(file)) {
1470
- throw new Error(`Memory already exists at ${file}`);
1471
- }
1472
- await writeFile11(file, serializeMemory10({ frontmatter, body }), "utf8");
1473
- return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
1474
- }
1475
-
1476
- // src/tools/mem-session-end.ts
1477
- import { writeFile as writeFile13, mkdir as mkdir7 } from "fs/promises";
1478
- import { existsSync as existsSync20 } from "fs";
1479
- import path10 from "path";
1480
- import {
1481
- buildFrontmatter as buildFrontmatter4,
1482
- loadMemoriesFromDir as loadMemoriesFromDir15,
1483
- memoryFilePath as memoryFilePath5,
1484
- serializeMemory as serializeMemory11
1485
- } from "@hivelore/core";
1486
- import { z as z19 } from "zod";
1487
1550
 
1488
1551
  // src/session-tracker.ts
1489
1552
  import {
@@ -1492,12 +1555,12 @@ import {
1492
1555
  loadConfig as loadConfig2,
1493
1556
  writeSessionHandoff
1494
1557
  } from "@hivelore/core";
1495
- import { mkdir as mkdir6, writeFile as writeFile12, rm } from "fs/promises";
1496
- import { existsSync as existsSync19 } from "fs";
1497
- import path9 from "path";
1558
+ import { mkdir as mkdir5, writeFile as writeFile11, rm } from "fs/promises";
1559
+ import { existsSync as existsSync18 } from "fs";
1560
+ import path8 from "path";
1498
1561
  import { execSync as execSync2 } from "child_process";
1499
1562
  function pendingDistillPath(ctx) {
1500
- return path9.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
1563
+ return path8.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
1501
1564
  }
1502
1565
  var SessionTracker = class {
1503
1566
  events = [];
@@ -1601,7 +1664,7 @@ var SessionTracker = class {
1601
1664
  (e) => e.tool === "mem_session_end" && !e.summary?.startsWith("Auto-captured")
1602
1665
  );
1603
1666
  const isSubstantialSession = totalCalls >= 3 || writingTools.length > 0;
1604
- if (!ranPostTask && isSubstantialSession && existsSync19(this.ctx.paths.haiveDir)) {
1667
+ if (!ranPostTask && isSubstantialSession && existsSync18(this.ctx.paths.haiveDir)) {
1605
1668
  try {
1606
1669
  const memoriesSaved = writingTools.map((e) => e.summary ?? "").filter(Boolean).slice(0, 20);
1607
1670
  const payload = {
@@ -1614,9 +1677,9 @@ var SessionTracker = class {
1614
1677
  ...gitDiff ? { git_diff: gitDiff } : {},
1615
1678
  ...recapId ? { recap_id: recapId } : {}
1616
1679
  };
1617
- const cacheDir = path9.join(this.ctx.paths.haiveDir, ".cache");
1618
- await mkdir6(cacheDir, { recursive: true });
1619
- await writeFile12(
1680
+ const cacheDir = path8.join(this.ctx.paths.haiveDir, ".cache");
1681
+ await mkdir5(cacheDir, { recursive: true });
1682
+ await writeFile11(
1620
1683
  pendingDistillPath(this.ctx),
1621
1684
  JSON.stringify(payload, null, 2) + "\n",
1622
1685
  "utf8"
@@ -1635,7 +1698,7 @@ var SessionTracker = class {
1635
1698
  };
1636
1699
  async function clearPendingDistill(ctx) {
1637
1700
  const p = pendingDistillPath(ctx);
1638
- if (existsSync19(p)) {
1701
+ if (existsSync18(p)) {
1639
1702
  try {
1640
1703
  await rm(p);
1641
1704
  } catch {
@@ -1652,15 +1715,15 @@ function summarizeTools(events) {
1652
1715
 
1653
1716
  // src/tools/mem-session-end.ts
1654
1717
  var MemSessionEndInputSchema = {
1655
- goal: z19.string().min(1).describe("What you were trying to accomplish this session (1\u20132 sentences)"),
1656
- accomplished: z19.string().describe("What was actually done \u2014 bullet list recommended"),
1657
- discoveries: z19.string().default("").describe(
1718
+ goal: z18.string().min(1).describe("What you were trying to accomplish this session (1\u20132 sentences)"),
1719
+ accomplished: z18.string().describe("What was actually done \u2014 bullet list recommended"),
1720
+ discoveries: z18.string().default("").describe(
1658
1721
  "Any bugs, inconsistencies, surprises, or missing knowledge found during this session. Empty if nothing surprising was found."
1659
1722
  ),
1660
- files_touched: z19.array(z19.string()).default([]).describe("Key files that were read or modified \u2014 used as anchor paths"),
1661
- next_steps: z19.string().default("").describe("What should happen next (for the next session or a teammate)"),
1662
- scope: z19.enum(["personal", "team", "module"]).default("personal").describe("Visibility: personal = private to you, team = shared with the team"),
1663
- module: z19.string().optional().describe("Module name (required when scope=module)")
1723
+ files_touched: z18.array(z18.string()).default([]).describe("Key files that were read or modified \u2014 used as anchor paths"),
1724
+ next_steps: z18.string().default("").describe("What should happen next (for the next session or a teammate)"),
1725
+ scope: z18.enum(["personal", "team", "module"]).default("personal").describe("Visibility: personal = private to you, team = shared with the team"),
1726
+ module: z18.string().optional().describe("Module name (required when scope=module)")
1664
1727
  };
1665
1728
  function recapTopic(scope, module) {
1666
1729
  return module ? `session-recap-${scope}-${module}` : `session-recap-${scope}`;
@@ -1690,23 +1753,23 @@ ${input.next_steps}`);
1690
1753
  return lines.join("\n");
1691
1754
  }
1692
1755
  async function memSessionEnd(input, ctx) {
1693
- if (!existsSync20(ctx.paths.haiveDir)) {
1756
+ if (!existsSync19(ctx.paths.haiveDir)) {
1694
1757
  throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
1695
1758
  }
1696
1759
  const body = buildBody(input);
1697
1760
  const topic = recapTopic(input.scope, input.module);
1698
1761
  const normalizedFiles = input.files_touched.map((p) => {
1699
- if (!p || !path10.isAbsolute(p)) return p;
1700
- const rel = path10.relative(ctx.paths.root, p);
1762
+ if (!p || !path9.isAbsolute(p)) return p;
1763
+ const rel = path9.relative(ctx.paths.root, p);
1701
1764
  return rel.startsWith("..") ? p : rel;
1702
1765
  });
1703
1766
  const invalidPaths = normalizedFiles.filter(
1704
- (p) => !existsSync20(path10.resolve(ctx.paths.root, p))
1767
+ (p) => !existsSync19(path9.resolve(ctx.paths.root, p))
1705
1768
  );
1706
1769
  if (invalidPaths.length > 0) {
1707
1770
  console.warn(`[haive] session end: anchor path(s) not found: ${invalidPaths.join(", ")}`);
1708
1771
  }
1709
- const existing = existsSync20(ctx.paths.memoriesDir) ? await loadMemoriesFromDir15(ctx.paths.memoriesDir) : [];
1772
+ const existing = existsSync19(ctx.paths.memoriesDir) ? await loadMemoriesFromDir15(ctx.paths.memoriesDir) : [];
1710
1773
  const topicMatch = existing.find(
1711
1774
  ({ memory }) => memory.frontmatter.topic === topic && memory.frontmatter.scope === input.scope && (!input.module || memory.frontmatter.module === input.module)
1712
1775
  );
@@ -1722,9 +1785,9 @@ async function memSessionEnd(input, ctx) {
1722
1785
  paths: normalizedFiles.length ? normalizedFiles : fm.anchor.paths
1723
1786
  }
1724
1787
  };
1725
- await writeFile13(
1788
+ await writeFile12(
1726
1789
  topicMatch.filePath,
1727
- serializeMemory11({ frontmatter: newFrontmatter, body }),
1790
+ serializeMemory10({ frontmatter: newFrontmatter, body }),
1728
1791
  "utf8"
1729
1792
  );
1730
1793
  await clearPendingDistill(ctx);
@@ -1736,7 +1799,7 @@ async function memSessionEnd(input, ctx) {
1736
1799
  revision_count: revisionCount
1737
1800
  };
1738
1801
  }
1739
- const frontmatter = buildFrontmatter4({
1802
+ const frontmatter = buildFrontmatter3({
1740
1803
  type: "session_recap",
1741
1804
  slug: "recap",
1742
1805
  scope: input.scope,
@@ -1746,14 +1809,14 @@ async function memSessionEnd(input, ctx) {
1746
1809
  topic,
1747
1810
  status: "validated"
1748
1811
  });
1749
- const file = memoryFilePath5(
1812
+ const file = memoryFilePath4(
1750
1813
  ctx.paths,
1751
1814
  frontmatter.scope,
1752
1815
  frontmatter.id,
1753
1816
  frontmatter.module
1754
1817
  );
1755
- await mkdir7(path10.dirname(file), { recursive: true });
1756
- await writeFile13(file, serializeMemory11({ frontmatter, body }), "utf8");
1818
+ await mkdir6(path9.dirname(file), { recursive: true });
1819
+ await writeFile12(file, serializeMemory10({ frontmatter, body }), "utf8");
1757
1820
  await clearPendingDistill(ctx);
1758
1821
  return {
1759
1822
  id: frontmatter.id,
@@ -1765,9 +1828,9 @@ async function memSessionEnd(input, ctx) {
1765
1828
  }
1766
1829
 
1767
1830
  // src/tools/get-briefing.ts
1768
- import { readFile as readFile6, writeFile as writeFile14, readdir as readdir4 } from "fs/promises";
1769
- import { existsSync as existsSync22 } from "fs";
1770
- import path12 from "path";
1831
+ import { readFile as readFile6, writeFile as writeFile13, readdir as readdir4 } from "fs/promises";
1832
+ import { existsSync as existsSync21 } from "fs";
1833
+ import path11 from "path";
1771
1834
  import {
1772
1835
  allocateBudget,
1773
1836
  assessBootstrapState,
@@ -1801,7 +1864,7 @@ import {
1801
1864
  queryCodeMap,
1802
1865
  readSessionHandoff,
1803
1866
  resolveBriefingBudget,
1804
- serializeMemory as serializeMemory12,
1867
+ serializeMemory as serializeMemory11,
1805
1868
  specificityScore,
1806
1869
  GUESSABLE_THRESHOLD,
1807
1870
  tokenizeQuery as tokenizeQuery2,
@@ -1809,12 +1872,12 @@ import {
1809
1872
  truncateToTokens,
1810
1873
  writeBriefingMarker
1811
1874
  } from "@hivelore/core";
1812
- import { z as z20 } from "zod";
1875
+ import { z as z19 } from "zod";
1813
1876
 
1814
1877
  // src/tools/briefing-helpers.ts
1815
1878
  import { readdir as readdir3, readFile as readFile5 } from "fs/promises";
1816
- import { existsSync as existsSync21 } from "fs";
1817
- import path11 from "path";
1879
+ import { existsSync as existsSync20 } from "fs";
1880
+ import path10 from "path";
1818
1881
  import {
1819
1882
  classifyMemoryPriority as coreClassifyPriority,
1820
1883
  isGlobPath,
@@ -1947,15 +2010,15 @@ async function trySemanticHits(ctx, task, limit) {
1947
2010
  }
1948
2011
  async function loadModuleContexts2(ctx, modules) {
1949
2012
  if (modules.length === 0) return [];
1950
- if (!existsSync21(ctx.paths.modulesContextDir)) return [];
2013
+ if (!existsSync20(ctx.paths.modulesContextDir)) return [];
1951
2014
  const available = new Set(
1952
2015
  (await readdir3(ctx.paths.modulesContextDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name)
1953
2016
  );
1954
2017
  const out = [];
1955
2018
  for (const m of modules) {
1956
2019
  if (!available.has(m)) continue;
1957
- const file = path11.join(ctx.paths.modulesContextDir, m, "context.md");
1958
- if (existsSync21(file)) {
2020
+ const file = path10.join(ctx.paths.modulesContextDir, m, "context.md");
2021
+ if (existsSync20(file)) {
1959
2022
  out.push({ name: m, content: await readFile5(file, "utf8") });
1960
2023
  }
1961
2024
  }
@@ -1964,38 +2027,38 @@ async function loadModuleContexts2(ctx, modules) {
1964
2027
 
1965
2028
  // src/tools/get-briefing.ts
1966
2029
  var GetBriefingInputSchema = {
1967
- task: z20.string().optional().describe(
2030
+ task: z19.string().optional().describe(
1968
2031
  "What you are about to do, in 1\u20132 sentences. Used to rank relevant memories semantically."
1969
2032
  ),
1970
- files: z20.array(z20.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
1971
- max_tokens: z20.number().int().positive().default(8e3).describe(
2033
+ files: z19.array(z19.string()).default([]).describe("Project-relative file paths the agent is currently looking at or about to edit"),
2034
+ max_tokens: z19.number().int().positive().default(8e3).describe(
1972
2035
  "Approximate token budget for the entire briefing. Each section is allocated a share and truncated to fit."
1973
2036
  ),
1974
- max_memories: z20.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
1975
- include_project_context: z20.boolean().default(true),
1976
- dedupe_project_context: z20.boolean().optional().describe(
2037
+ max_memories: z19.number().int().positive().default(8).describe("Cap on memories surfaced regardless of token budget"),
2038
+ include_project_context: z19.boolean().default(true),
2039
+ dedupe_project_context: z19.boolean().optional().describe(
1977
2040
  "Token saver (default ON): skip re-emitting the project-context body if an identical copy was already sent within the last few minutes this session (the agent still has it). Set false to always include it."
1978
2041
  ),
1979
- include_module_contexts: z20.boolean().default(true),
1980
- semantic: z20.boolean().default(true).describe(
2042
+ include_module_contexts: z19.boolean().default(true),
2043
+ semantic: z19.boolean().default(true).describe(
1981
2044
  "Use semantic ranking when a task is provided (requires `hivelore embeddings index`)."
1982
2045
  ),
1983
- include_stale: z20.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
1984
- track: z20.boolean().default(true).describe("Increment read_count on returned memories"),
1985
- format: z20.enum(["full", "compact", "actions"]).default("full").describe(
2046
+ include_stale: z19.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
2047
+ track: z19.boolean().default(true).describe("Increment read_count on returned memories"),
2048
+ format: z19.enum(["full", "compact", "actions"]).default("full").describe(
1986
2049
  "Output format: 'full' returns memory bodies (honors token budget via truncation); 'compact' returns a 1-line summary per memory (call mem_get for detail); 'actions' squeezes bodies to actionable bullet lines \u2014 fewer tokens vs full."
1987
2050
  ),
1988
- symbols: z20.array(z20.string()).default([]).describe(
2051
+ symbols: z19.array(z19.string()).default([]).describe(
1989
2052
  "Symbol names to look up in the code-map (e.g. ['PaymentService', 'TenantFilter']). Returns the file(s) exporting each symbol so agents don't need to grep. Requires `hivelore index code` to have been run."
1990
2053
  ),
1991
- min_semantic_score: z20.number().min(0).max(1).default(0).describe(
2054
+ min_semantic_score: z19.number().min(0).max(1).default(0).describe(
1992
2055
  "Drop semantic-only memory hits whose cosine score is below this threshold. Useful to avoid weakly-related noise when the task is short or the corpus is broad. Has no effect on memories matched via anchor/module/literal \u2014 those are always kept. Try 0.25\u20130.4 for stricter matching."
1993
2056
  ),
1994
- budget_preset: z20.enum(["quick", "balanced", "deep"]).optional().describe(
2057
+ budget_preset: z19.enum(["quick", "balanced", "deep"]).optional().describe(
1995
2058
  "Shortcut token budget: 'quick' minimizes tokens/skip module CONTEXT slices; 'balanced' mirrors historical defaults; 'deep' uses a larger briefing. When set, overrides max_tokens, max_memories, and include_module_contexts."
1996
2059
  )
1997
2060
  };
1998
- var GetBriefingZod = z20.object(GetBriefingInputSchema);
2061
+ var GetBriefingZod = z19.object(GetBriefingInputSchema);
1999
2062
  async function getBriefing(input, ctx) {
2000
2063
  const resolvedBudget = resolveBriefingBudget(input.budget_preset, {
2001
2064
  max_tokens: input.max_tokens,
@@ -2011,7 +2074,7 @@ async function getBriefing(input, ctx) {
2011
2074
  let usage = { version: 1, updated_at: "", by_id: {} };
2012
2075
  let byId = /* @__PURE__ */ new Map();
2013
2076
  let lastSession;
2014
- if (existsSync22(ctx.paths.memoriesDir)) {
2077
+ if (existsSync21(ctx.paths.memoriesDir)) {
2015
2078
  const allLoaded = await loadMemoriesFromDir16(ctx.paths.memoriesDir);
2016
2079
  const recaps = allLoaded.filter(({ memory }) => memory.frontmatter.type === "session_recap").sort(
2017
2080
  (a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
@@ -2186,7 +2249,7 @@ async function getBriefing(input, ctx) {
2186
2249
  if (!isAutoPromoteEligible(loaded.memory.frontmatter, u, rule)) continue;
2187
2250
  const newFm = { ...loaded.memory.frontmatter, status: "validated", validated_by: "auto" };
2188
2251
  try {
2189
- await writeFile14(loaded.filePath, serializeMemory12({ frontmatter: newFm, body: loaded.memory.body }), "utf8");
2252
+ await writeFile13(loaded.filePath, serializeMemory11({ frontmatter: newFm, body: loaded.memory.body }), "utf8");
2190
2253
  m.status = "validated";
2191
2254
  m.confidence = "trusted";
2192
2255
  } catch {
@@ -2194,7 +2257,7 @@ async function getBriefing(input, ctx) {
2194
2257
  }
2195
2258
  }
2196
2259
  }
2197
- let projectContextRaw = input.include_project_context && existsSync22(ctx.paths.projectContext) ? await readFile6(ctx.paths.projectContext, "utf8") : "";
2260
+ let projectContextRaw = input.include_project_context && existsSync21(ctx.paths.projectContext) ? await readFile6(ctx.paths.projectContext, "utf8") : "";
2198
2261
  let contextOmittedRecent = false;
2199
2262
  if (projectContextRaw && input.dedupe_project_context !== false) {
2200
2263
  const ctxHash = hashProjectContext(projectContextRaw);
@@ -2209,7 +2272,7 @@ async function getBriefing(input, ctx) {
2209
2272
  const setupWarnings = [];
2210
2273
  let autoContextGenerated = false;
2211
2274
  let projectContext = isTemplateContext ? "" : projectContextRaw;
2212
- if ((isTemplateContext || !existsSync22(ctx.paths.projectContext)) && input.include_project_context) {
2275
+ if ((isTemplateContext || !existsSync21(ctx.paths.projectContext)) && input.include_project_context) {
2213
2276
  const haiveConfig = await loadConfig3(ctx.paths);
2214
2277
  if (haiveConfig.autoContext) {
2215
2278
  const codeMap = await loadCodeMap(ctx.paths);
@@ -2315,11 +2378,20 @@ ${m.content}`).join("\n\n---\n\n"),
2315
2378
  if (isDecaying(u, createdAt)) decayWarnings.push(m.id);
2316
2379
  }
2317
2380
  const formattedMemories = input.format === "compact" ? trimmedMemories.map((m) => ({ ...m, body: compactSummary(m.body) })) : input.format === "actions" ? trimmedMemories.map((m) => ({ ...m, body: extractActionsBriefBody(m.body) })) : trimmedMemories;
2318
- const outputMemories = formattedMemories.map((m) => ({
2381
+ let outputMemories = formattedMemories.map((m) => ({
2319
2382
  ...m,
2320
2383
  priority: classifyMemoryPriority(m, byId.get(m.id), input.files, input.symbols),
2321
2384
  why: explainWhySurfaced(m, byId.get(m.id), input.files, inferred)
2322
2385
  }));
2386
+ if (input.format === "full") {
2387
+ const hasDirectHits = outputMemories.some((m) => m.priority === "must_read" || m.priority === "useful");
2388
+ if (hasDirectHits) {
2389
+ outputMemories = outputMemories.map(
2390
+ (m) => m.priority === "background" ? { ...m, body: `${compactSummary(m.body)}
2391
+ (background \u2014 full body: mem_get("${m.id}"))` } : m
2392
+ );
2393
+ }
2394
+ }
2323
2395
  const briefingQuality = classifyBriefingQuality(outputMemories, {
2324
2396
  isTemplateContext,
2325
2397
  autoContextGenerated,
@@ -2374,7 +2446,7 @@ ${m.content}`).join("\n\n---\n\n"),
2374
2446
  actionRequired.push(extractActionItem(m.id, loaded.memory.body));
2375
2447
  }
2376
2448
  }
2377
- if (existsSync22(ctx.paths.memoriesDir)) {
2449
+ if (existsSync21(ctx.paths.memoriesDir)) {
2378
2450
  const allMems = await loadMemoriesFromDir16(ctx.paths.memoriesDir);
2379
2451
  for (const { memory } of allMems) {
2380
2452
  const fm = memory.frontmatter;
@@ -2385,7 +2457,7 @@ ${m.content}`).join("\n\n---\n\n"),
2385
2457
  }
2386
2458
  }
2387
2459
  const pendingDistillFile = pendingDistillPath(ctx);
2388
- if (existsSync22(pendingDistillFile)) {
2460
+ if (existsSync21(pendingDistillFile)) {
2389
2461
  try {
2390
2462
  const raw = await readFile6(pendingDistillFile, "utf8");
2391
2463
  const pd = JSON.parse(raw);
@@ -2414,7 +2486,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
2414
2486
  }
2415
2487
  }
2416
2488
  const memoriesEmpty = outputMemories.length === 0;
2417
- const hasMemoriesDir = existsSync22(ctx.paths.memoriesDir);
2489
+ const hasMemoriesDir = existsSync21(ctx.paths.memoriesDir);
2418
2490
  const isColdStart = isTemplateContext && memoriesEmpty && !lastSession && !autoContextGenerated;
2419
2491
  const hasUnguessableSignal = outputMemories.some(
2420
2492
  (m) => (m.priority === "must_read" || m.priority === "useful") && specificityScore(m.body) >= GUESSABLE_THRESHOLD
@@ -2432,7 +2504,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
2432
2504
  pcRaw = await readFile6(ctx.paths.projectContext, "utf8");
2433
2505
  } catch {
2434
2506
  }
2435
- const allForBootstrap = existsSync22(ctx.paths.memoriesDir) ? await loadMemoriesFromDir16(ctx.paths.memoriesDir) : [];
2507
+ const allForBootstrap = existsSync21(ctx.paths.memoriesDir) ? await loadMemoriesFromDir16(ctx.paths.memoriesDir) : [];
2436
2508
  const cmForBootstrap = await loadCodeMap(ctx.paths);
2437
2509
  let existingModules = [];
2438
2510
  try {
@@ -2496,7 +2568,7 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
2496
2568
  "No team-specific policy matched these files/task \u2014 nothing here a capable model can't infer. The auto-generated project context was trimmed to keep this briefing near-zero-cost; proceed with normal Read/Grep."
2497
2569
  );
2498
2570
  }
2499
- if (outputMemories.length > 0 && existsSync22(ctx.paths.haiveDir)) {
2571
+ if (outputMemories.length > 0 && existsSync21(ctx.paths.haiveDir)) {
2500
2572
  const proof = briefingProofLine(await loadPreventionEvents(ctx.paths));
2501
2573
  if (proof) hints.push(proof);
2502
2574
  }
@@ -2510,7 +2582,7 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
2510
2582
  adaptiveTrim
2511
2583
  });
2512
2584
  const breadcrumbTokens = breadcrumbs ? estimateTokens([...breadcrumbs.start_here, ...breadcrumbs.drill_down, breadcrumbs.note ?? ""].join("\n")) : 0;
2513
- if (existsSync22(ctx.paths.haiveDir)) {
2585
+ if (existsSync21(ctx.paths.haiveDir)) {
2514
2586
  await writeBriefingMarker(ctx.paths, {
2515
2587
  sessionId: process.env.HAIVE_SESSION_ID,
2516
2588
  ...input.task ? { task: input.task } : {},
@@ -2566,8 +2638,8 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
2566
2638
  };
2567
2639
  }
2568
2640
  async function detectRunCommands(root) {
2569
- const pkgPath = path12.join(root, "package.json");
2570
- if (!existsSync22(pkgPath)) return null;
2641
+ const pkgPath = path11.join(root, "package.json");
2642
+ if (!existsSync21(pkgPath)) return null;
2571
2643
  try {
2572
2644
  const pkg = JSON.parse(await readFile6(pkgPath, "utf8"));
2573
2645
  const scripts = pkg.scripts ?? {};
@@ -2634,24 +2706,24 @@ function oneLine(value) {
2634
2706
  return value.replace(/\s+/g, " ").replace(/"/g, '\\"').trim().slice(0, 120);
2635
2707
  }
2636
2708
  function serverVersion() {
2637
- return true ? "0.30.1" : "dev";
2709
+ return true ? "0.33.0" : "dev";
2638
2710
  }
2639
2711
 
2640
2712
  // src/tools/code-map.ts
2641
2713
  import { estimateTokens as estimateTokens2, loadCodeMap as loadCodeMap2, queryCodeMap as queryCodeMap2 } from "@hivelore/core";
2642
- import { z as z21 } from "zod";
2714
+ import { z as z20 } from "zod";
2643
2715
  var CodeMapInputSchema = {
2644
- file: z21.string().optional().describe("Filter to files whose path contains this substring"),
2645
- symbol: z21.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
2646
- paths: z21.array(z21.string()).default([]).describe(
2716
+ file: z20.string().optional().describe("Filter to files whose path contains this substring"),
2717
+ symbol: z20.string().optional().describe("Filter to files exporting a symbol whose name contains this substring"),
2718
+ paths: z20.array(z20.string()).default([]).describe(
2647
2719
  "Filter to files under any of these path prefixes (e.g. ['packages/mcp/src/tools/', 'src/auth/']). OR-joined with `file` substring; useful to get a focused view of one module."
2648
2720
  ),
2649
- max_files: z21.number().int().positive().default(40).describe("Cap on returned files (hard limit, applied after token budget)"),
2650
- max_tokens: z21.number().int().positive().optional().describe(
2721
+ max_files: z20.number().int().positive().default(40).describe("Cap on returned files (hard limit, applied after token budget)"),
2722
+ max_tokens: z20.number().int().positive().optional().describe(
2651
2723
  "Approximate token budget for the response. When the matching set exceeds it, files are ranked by export density (exports per LOC) and the highest-signal ones are kept first. Omit to disable budgeting (legacy behavior)."
2652
2724
  )
2653
2725
  };
2654
- var CodeMapInputZod = z21.object(CodeMapInputSchema);
2726
+ var CodeMapInputZod = z20.object(CodeMapInputSchema);
2655
2727
  async function codeMapTool(input, ctx) {
2656
2728
  const map = await loadCodeMap2(ctx.paths);
2657
2729
  if (!map) {
@@ -2720,15 +2792,15 @@ function estimateFileEntryTokens(f) {
2720
2792
  }
2721
2793
 
2722
2794
  // src/tools/mem-diff.ts
2723
- import { existsSync as existsSync23 } from "fs";
2795
+ import { existsSync as existsSync22 } from "fs";
2724
2796
  import { loadMemoriesFromDir as loadMemoriesFromDir17 } from "@hivelore/core";
2725
- import { z as z22 } from "zod";
2797
+ import { z as z21 } from "zod";
2726
2798
  var MemDiffInputSchema = {
2727
- id_a: z22.string().min(1).describe("First memory id"),
2728
- id_b: z22.string().min(1).describe("Second memory id")
2799
+ id_a: z21.string().min(1).describe("First memory id"),
2800
+ id_b: z21.string().min(1).describe("Second memory id")
2729
2801
  };
2730
2802
  async function memDiff(input, ctx) {
2731
- if (!existsSync23(ctx.paths.memoriesDir)) {
2803
+ if (!existsSync22(ctx.paths.memoriesDir)) {
2732
2804
  throw new Error(`No .ai/memories at ${ctx.paths.root}.`);
2733
2805
  }
2734
2806
  const all = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
@@ -2765,16 +2837,16 @@ async function memDiff(input, ctx) {
2765
2837
  }
2766
2838
 
2767
2839
  // src/tools/get-recap.ts
2768
- import { existsSync as existsSync24 } from "fs";
2840
+ import { existsSync as existsSync23 } from "fs";
2769
2841
  import { loadMemoriesFromDir as loadMemoriesFromDir18 } from "@hivelore/core";
2770
- import { z as z23 } from "zod";
2842
+ import { z as z22 } from "zod";
2771
2843
  var GetRecapInputSchema = {
2772
- scope: z23.enum(["personal", "team", "any"]).default("any").describe(
2844
+ scope: z22.enum(["personal", "team", "any"]).default("any").describe(
2773
2845
  "Limit to a specific scope's recap. Default 'any' returns the most recent recap across both personal and team scopes."
2774
2846
  )
2775
2847
  };
2776
2848
  async function getRecap(input, ctx) {
2777
- if (!existsSync24(ctx.paths.memoriesDir)) {
2849
+ if (!existsSync23(ctx.paths.memoriesDir)) {
2778
2850
  return { recap: null, notice: "No .ai/memories directory \u2014 haive not initialized here." };
2779
2851
  }
2780
2852
  const all = await loadMemoriesFromDir18(ctx.paths.memoriesDir);
@@ -2801,13 +2873,13 @@ async function getRecap(input, ctx) {
2801
2873
  }
2802
2874
 
2803
2875
  // src/tools/mem-relevant-to.ts
2804
- import { z as z24 } from "zod";
2876
+ import { z as z23 } from "zod";
2805
2877
  var MemRelevantToInputSchema = {
2806
- task: z24.string().min(1).describe("What you are about to do, in 1\u20132 sentences. Used to rank relevant memories."),
2807
- files: z24.array(z24.string()).default([]).describe("Optional: files you are about to edit \u2014 surfaces anchored memories."),
2808
- limit: z24.number().int().positive().max(30).default(8).describe("Cap on returned memories."),
2809
- min_semantic_score: z24.number().min(0).max(1).default(0.25).describe("Drop weakly-related semantic hits below this cosine threshold."),
2810
- format: z24.enum(["full", "compact", "actions"]).default("full").describe("'compact' = id + 1-line summary; 'full' = complete bodies; 'actions' = bullet-first excerpts.")
2878
+ task: z23.string().min(1).describe("What you are about to do, in 1\u20132 sentences. Used to rank relevant memories."),
2879
+ files: z23.array(z23.string()).default([]).describe("Optional: files you are about to edit \u2014 surfaces anchored memories."),
2880
+ limit: z23.number().int().positive().max(30).default(8).describe("Cap on returned memories."),
2881
+ min_semantic_score: z23.number().min(0).max(1).default(0.25).describe("Drop weakly-related semantic hits below this cosine threshold."),
2882
+ format: z23.enum(["full", "compact", "actions"]).default("full").describe("'compact' = id + 1-line summary; 'full' = complete bodies; 'actions' = bullet-first excerpts.")
2811
2883
  };
2812
2884
  async function memRelevantTo(input, ctx) {
2813
2885
  const briefingInput = {
@@ -2837,14 +2909,14 @@ async function memRelevantTo(input, ctx) {
2837
2909
  }
2838
2910
 
2839
2911
  // src/tools/code-search.ts
2840
- import { z as z25 } from "zod";
2912
+ import { z as z24 } from "zod";
2841
2913
  import { loadCodeMap as loadCodeMap3 } from "@hivelore/core";
2842
2914
  var CodeSearchInputSchema = {
2843
- query: z25.string().min(1).describe(
2915
+ query: z24.string().min(1).describe(
2844
2916
  "Natural-language description of what you are looking for in the codebase (e.g. 'function that hashes passwords', 'JWT signing logic', 'route registration')."
2845
2917
  ),
2846
- k: z25.number().int().positive().max(50).default(5).describe("Number of top hits to return."),
2847
- min_score: z25.number().min(0).max(1).default(0.2).describe(
2918
+ k: z24.number().int().positive().max(50).default(5).describe("Number of top hits to return."),
2919
+ min_score: z24.number().min(0).max(1).default(0.2).describe(
2848
2920
  "Minimum cosine similarity. Hits below this threshold are dropped to avoid noise. Try 0.3+ for stricter matching."
2849
2921
  )
2850
2922
  };
@@ -2886,155 +2958,39 @@ async function codeSearch(input, ctx) {
2886
2958
  };
2887
2959
  }
2888
2960
 
2889
- // src/tools/why-this-file.ts
2890
- import { existsSync as existsSync25 } from "fs";
2891
- import { spawn } from "child_process";
2892
- import path13 from "path";
2893
- import {
2894
- deriveConfidence as deriveConfidence5,
2895
- getUsage as getUsage7,
2896
- loadCodeMap as loadCodeMap4,
2897
- loadMemoriesFromDir as loadMemoriesFromDir19,
2898
- loadUsageIndex as loadUsageIndex9,
2899
- memoryMatchesAnchorPaths as memoryMatchesAnchorPaths3
2900
- } from "@hivelore/core";
2901
- import { z as z26 } from "zod";
2902
- var WhyThisFileInputSchema = {
2903
- path: z26.string().min(1).describe(
2904
- "Project-relative path to the file you want context on (e.g. 'packages/mcp/src/tools/mem-save.ts')."
2905
- ),
2906
- git_log_limit: z26.number().int().positive().max(20).default(5).describe("How many recent commits touching this file to include."),
2907
- memory_limit: z26.number().int().positive().max(20).default(5).describe("Cap on memories anchored to this path.")
2908
- };
2909
- async function whyThisFile(input, ctx) {
2910
- const fileExists = existsSync25(path13.join(ctx.paths.root, input.path));
2911
- const [commits, memories, codeMap] = await Promise.all([
2912
- runGitLog(ctx.paths.root, input.path, input.git_log_limit).catch(() => []),
2913
- collectAnchoredMemories(ctx, input.path, input.memory_limit),
2914
- loadCodeMap4(ctx.paths)
2915
- ]);
2916
- const codeMapEntry = codeMap?.files[input.path];
2917
- const hints = [];
2918
- if (!fileExists) {
2919
- hints.push(`File '${input.path}' does not exist on disk \u2014 path may be wrong or file removed.`);
2920
- }
2921
- if (commits.length === 0 && fileExists) {
2922
- hints.push("No git history found \u2014 file may be untracked or git not initialized.");
2923
- }
2924
- if (memories.length === 0 && fileExists) {
2925
- hints.push(
2926
- "No memories anchored here. If you discover something non-obvious while editing, use mem_observe (with where=" + input.path + ") to capture it."
2927
- );
2928
- }
2929
- if (memories.some((m) => m.type === "attempt" || m.type === "gotcha")) {
2930
- hints.push("\u26A0\uFE0F attempt/gotcha memories anchored to this file \u2014 read them BEFORE editing.");
2931
- }
2932
- return {
2933
- file: input.path,
2934
- exists: fileExists,
2935
- recent_commits: commits,
2936
- memories,
2937
- code_map_entry: codeMapEntry ? {
2938
- ...codeMapEntry.summary ? { summary: codeMapEntry.summary } : {},
2939
- loc: codeMapEntry.loc,
2940
- exports: codeMapEntry.exports.map((e) => ({
2941
- name: e.name,
2942
- kind: e.kind,
2943
- line: e.line,
2944
- ...e.description ? { description: e.description } : {}
2945
- }))
2946
- } : null,
2947
- ...hints.length > 0 ? { hints } : {}
2948
- };
2949
- }
2950
- async function collectAnchoredMemories(ctx, filePath, limit) {
2951
- if (!existsSync25(ctx.paths.memoriesDir)) return [];
2952
- const all = await loadMemoriesFromDir19(ctx.paths.memoriesDir);
2953
- const usage = await loadUsageIndex9(ctx.paths);
2954
- const out = [];
2955
- for (const { memory } of all) {
2956
- const fm = memory.frontmatter;
2957
- if (fm.status === "rejected" || fm.status === "deprecated") continue;
2958
- if (fm.type === "session_recap") continue;
2959
- if (!memoryMatchesAnchorPaths3(memory, [filePath])) continue;
2960
- const u = getUsage7(usage, fm.id);
2961
- out.push({
2962
- id: fm.id,
2963
- type: fm.type,
2964
- scope: fm.scope,
2965
- confidence: deriveConfidence5(fm, u),
2966
- body_preview: memory.body.split("\n").slice(0, 6).join("\n")
2967
- });
2968
- if (out.length >= limit) break;
2969
- }
2970
- return out;
2971
- }
2972
- async function runGitLog(cwd, filePath, limit) {
2973
- const sep = "<<HV>>";
2974
- const fmt = `%h${sep}%an${sep}%ar${sep}%s`;
2975
- const output = await runCommand(
2976
- "git",
2977
- ["log", `-n`, String(limit), `--pretty=format:${fmt}`, "--", filePath],
2978
- cwd
2979
- );
2980
- if (!output.trim()) return [];
2981
- return output.split("\n").map((line) => {
2982
- const [sha = "", author = "", relative_date = "", subject = ""] = line.split(sep);
2983
- return { sha, author, relative_date, subject };
2984
- }).filter((c) => c.sha);
2985
- }
2986
- function runCommand(cmd, args, cwd) {
2987
- return new Promise((resolve, reject) => {
2988
- const proc = spawn(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
2989
- let stdout = "";
2990
- let stderr = "";
2991
- proc.stdout.on("data", (chunk) => {
2992
- stdout += chunk.toString();
2993
- });
2994
- proc.stderr.on("data", (chunk) => {
2995
- stderr += chunk.toString();
2996
- });
2997
- proc.on("error", reject);
2998
- proc.on("close", (code) => {
2999
- if (code === 0) resolve(stdout);
3000
- else reject(new Error(stderr || `${cmd} exited with code ${code}`));
3001
- });
3002
- });
3003
- }
3004
-
3005
2961
  // src/tools/anti-patterns-check.ts
3006
- import { existsSync as existsSync26 } from "fs";
2962
+ import { existsSync as existsSync24 } from "fs";
3007
2963
  import {
3008
2964
  addedLinesFromDiff,
3009
2965
  BRIDGE_TARGET_PATH,
3010
2966
  buildDocFrequency,
3011
2967
  CODE_STOPWORDS,
3012
- deriveConfidence as deriveConfidence6,
2968
+ deriveConfidence as deriveConfidence5,
3013
2969
  diffHasDistinctiveOverlap,
3014
- getUsage as getUsage8,
2970
+ getUsage as getUsage7,
3015
2971
  isRetiredMemory as isRetiredMemory2,
3016
- loadMemoriesFromDir as loadMemoriesFromDir20,
3017
- loadUsageIndex as loadUsageIndex10,
2972
+ loadMemoriesFromDir as loadMemoriesFromDir19,
2973
+ loadUsageIndex as loadUsageIndex9,
3018
2974
  literalMatchesAnyToken as literalMatchesAnyToken3,
3019
- memoryMatchesAnchorPaths as memoryMatchesAnchorPaths4,
2975
+ memoryMatchesAnchorPaths as memoryMatchesAnchorPaths3,
3020
2976
  recordPreventionHits,
3021
2977
  runSensors,
3022
2978
  sensorTargetsFromDiff,
3023
2979
  tokenizeQuery as tokenizeQuery3
3024
2980
  } from "@hivelore/core";
3025
- import { z as z27 } from "zod";
2981
+ import { z as z25 } from "zod";
3026
2982
  var AntiPatternsCheckInputSchema = {
3027
- diff: z27.string().optional().describe(
2983
+ diff: z25.string().optional().describe(
3028
2984
  "Raw unified diff text (or any code/text snippet) to scan for previously documented anti-patterns. Tokens from the diff are used to match memory bodies and the embeddings index."
3029
2985
  ),
3030
- paths: z27.array(z27.string()).default([]).describe(
2986
+ paths: z25.array(z25.string()).default([]).describe(
3031
2987
  "File paths affected by the change. Memories anchored to any of these paths are surfaced regardless of the diff content."
3032
2988
  ),
3033
- limit: z27.number().int().positive().max(20).default(8).describe("Cap on returned warnings."),
3034
- semantic: z27.boolean().default(true).describe(
2989
+ limit: z25.number().int().positive().max(20).default(8).describe("Cap on returned warnings."),
2990
+ semantic: z25.boolean().default(true).describe(
3035
2991
  "When true, also use semantic search (requires @hivelore/embeddings + memory index) to find related anti-patterns."
3036
2992
  ),
3037
- min_semantic_score: z27.number().min(0).max(1).default(0.45).describe(
2993
+ min_semantic_score: z25.number().min(0).max(1).default(0.45).describe(
3038
2994
  "Minimum cosine score for semantic-only anti-pattern hits. Anchor/literal matches still surface. Default 0.45 keeps broad, weakly-related memories out of review noise."
3039
2995
  )
3040
2996
  };
@@ -3118,10 +3074,10 @@ async function antiPatternsCheck(input, ctx) {
3118
3074
  notice: "Nothing to check \u2014 provide either `diff` text or `paths`."
3119
3075
  };
3120
3076
  }
3121
- if (!existsSync26(ctx.paths.memoriesDir)) {
3077
+ if (!existsSync24(ctx.paths.memoriesDir)) {
3122
3078
  return { scanned: 0, warnings: [], notice: "No .ai/memories directory \u2014 nothing to check against." };
3123
3079
  }
3124
- const all = await loadMemoriesFromDir20(ctx.paths.memoriesDir);
3080
+ const all = await loadMemoriesFromDir19(ctx.paths.memoriesDir);
3125
3081
  const minSemanticScore = input.min_semantic_score ?? 0.45;
3126
3082
  const negative = all.filter(({ memory }) => {
3127
3083
  const t = memory.frontmatter.type;
@@ -3132,7 +3088,7 @@ async function antiPatternsCheck(input, ctx) {
3132
3088
  if (negative.length === 0) {
3133
3089
  return { scanned: 0, warnings: [], notice: "No attempt/gotcha memories found yet." };
3134
3090
  }
3135
- const usage = await loadUsageIndex10(ctx.paths);
3091
+ const usage = await loadUsageIndex9(ctx.paths);
3136
3092
  const docFreq = buildDocFrequency(negative.map(({ memory }) => memory.body));
3137
3093
  const seen = /* @__PURE__ */ new Map();
3138
3094
  const upsert = (fm, body, reason, score) => {
@@ -3144,12 +3100,12 @@ async function antiPatternsCheck(input, ctx) {
3144
3100
  }
3145
3101
  return;
3146
3102
  }
3147
- const u = getUsage8(usage, fm.id);
3103
+ const u = getUsage7(usage, fm.id);
3148
3104
  seen.set(fm.id, {
3149
3105
  id: fm.id,
3150
3106
  type: fm.type,
3151
3107
  scope: fm.scope,
3152
- confidence: deriveConfidence6(fm, u),
3108
+ confidence: deriveConfidence5(fm, u),
3153
3109
  body_preview: body.split("\n").slice(0, 5).join("\n").slice(0, 400),
3154
3110
  reasons: [reason],
3155
3111
  tags: fm.tags ?? [],
@@ -3160,7 +3116,7 @@ async function antiPatternsCheck(input, ctx) {
3160
3116
  };
3161
3117
  if (input.paths.length > 0) {
3162
3118
  for (const { memory } of negative) {
3163
- if (memoryMatchesAnchorPaths4(memory, input.paths)) {
3119
+ if (memoryMatchesAnchorPaths3(memory, input.paths)) {
3164
3120
  upsert(memory.frontmatter, memory.body, "anchor");
3165
3121
  }
3166
3122
  }
@@ -3237,19 +3193,19 @@ async function antiPatternsCheck(input, ctx) {
3237
3193
  }
3238
3194
 
3239
3195
  // src/tools/mem-distill.ts
3240
- import { existsSync as existsSync27 } from "fs";
3196
+ import { existsSync as existsSync25 } from "fs";
3241
3197
  import {
3242
- loadMemoriesFromDir as loadMemoriesFromDir21,
3198
+ loadMemoriesFromDir as loadMemoriesFromDir20,
3243
3199
  tokenizeQuery as tokenizeQuery4
3244
3200
  } from "@hivelore/core";
3245
- import { z as z28 } from "zod";
3201
+ import { z as z26 } from "zod";
3246
3202
  var MemDistillInputSchema = {
3247
- since_days: z28.number().int().positive().default(30).describe("Only consider memories created in the last N days."),
3248
- min_cluster: z28.number().int().min(2).default(3).describe("Minimum cluster size to surface."),
3249
- type_filter: z28.enum(["gotcha", "attempt", "all"]).default("gotcha").describe(
3203
+ since_days: z26.number().int().positive().default(30).describe("Only consider memories created in the last N days."),
3204
+ min_cluster: z26.number().int().min(2).default(3).describe("Minimum cluster size to surface."),
3205
+ type_filter: z26.enum(["gotcha", "attempt", "all"]).default("gotcha").describe(
3250
3206
  "Memory type to scan. 'gotcha' targets observe-style discoveries that recur, 'attempt' surfaces failed approaches that repeat, 'all' considers both."
3251
3207
  ),
3252
- scope: z28.enum(["personal", "team", "module", "any"]).default("any").describe("Restrict to a specific scope.")
3208
+ scope: z26.enum(["personal", "team", "module", "any"]).default("any").describe("Restrict to a specific scope.")
3253
3209
  };
3254
3210
  var MS_PER_DAY = 24 * 60 * 60 * 1e3;
3255
3211
  var STOP_WORDS = /* @__PURE__ */ new Set([
@@ -3289,11 +3245,11 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
3289
3245
  "error"
3290
3246
  ]);
3291
3247
  async function memDistill(input, ctx) {
3292
- if (!existsSync27(ctx.paths.memoriesDir)) {
3248
+ if (!existsSync25(ctx.paths.memoriesDir)) {
3293
3249
  return { scanned: 0, singletons: 0, clusters: [], notice: "No .ai/memories directory." };
3294
3250
  }
3295
3251
  const cutoff = Date.now() - input.since_days * MS_PER_DAY;
3296
- const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
3252
+ const all = await loadMemoriesFromDir20(ctx.paths.memoriesDir);
3297
3253
  const candidates = all.filter(({ memory }) => {
3298
3254
  const fm = memory.frontmatter;
3299
3255
  if (fm.status === "rejected" || fm.status === "deprecated" || fm.status === "stale") return false;
@@ -3396,299 +3352,19 @@ function firstHeading(body) {
3396
3352
  return void 0;
3397
3353
  }
3398
3354
 
3399
- // src/tools/why-this-decision.ts
3400
- import { existsSync as existsSync28 } from "fs";
3401
- import { spawn as spawn2 } from "child_process";
3402
- import {
3403
- deriveConfidence as deriveConfidence7,
3404
- getUsage as getUsage9,
3405
- loadMemoriesFromDir as loadMemoriesFromDir22,
3406
- loadUsageIndex as loadUsageIndex11,
3407
- pathsOverlap as singlePathsOverlap
3408
- } from "@hivelore/core";
3409
- import { z as z29 } from "zod";
3410
- var WhyThisDecisionInputSchema = {
3411
- id: z29.string().min(1).describe("Memory id to inspect (e.g. '2026-04-25-decision-esm-only')."),
3412
- git_log_limit: z29.number().int().positive().max(20).default(5).describe("How many recent commits per anchor path to surface.")
3413
- };
3414
- async function whyThisDecision(input, ctx) {
3415
- if (!existsSync28(ctx.paths.memoriesDir)) {
3416
- return {
3417
- found: false,
3418
- related: [],
3419
- path_neighbors: [],
3420
- recent_commits: [],
3421
- notice: "No .ai/memories directory."
3422
- };
3423
- }
3424
- const all = await loadMemoriesFromDir22(ctx.paths.memoriesDir);
3425
- const usage = await loadUsageIndex11(ctx.paths);
3426
- const target = all.find(({ memory }) => memory.frontmatter.id === input.id);
3427
- if (!target) {
3428
- return {
3429
- found: false,
3430
- related: [],
3431
- path_neighbors: [],
3432
- recent_commits: [],
3433
- notice: `Memory '${input.id}' not found.`
3434
- };
3435
- }
3436
- const fm = target.memory.frontmatter;
3437
- const targetUsage = getUsage9(usage, fm.id);
3438
- const decision = {
3439
- id: fm.id,
3440
- type: fm.type,
3441
- scope: fm.scope,
3442
- status: fm.status,
3443
- confidence: deriveConfidence7(fm, targetUsage),
3444
- body: target.memory.body,
3445
- created_at: fm.created_at
3446
- };
3447
- const relatedSet = new Set(fm.related_ids ?? []);
3448
- const related = [];
3449
- for (const { memory } of all) {
3450
- if (memory.frontmatter.id === fm.id) continue;
3451
- const isExplicit = relatedSet.has(memory.frontmatter.id);
3452
- const isBackLink = (memory.frontmatter.related_ids ?? []).includes(fm.id);
3453
- if (!isExplicit && !isBackLink) continue;
3454
- const u = getUsage9(usage, memory.frontmatter.id);
3455
- related.push({
3456
- id: memory.frontmatter.id,
3457
- type: memory.frontmatter.type,
3458
- scope: memory.frontmatter.scope,
3459
- confidence: deriveConfidence7(memory.frontmatter, u),
3460
- body_preview: memory.body.split("\n").slice(0, 4).join("\n").slice(0, 300),
3461
- relation: isExplicit ? "explicit" : "back-link"
3462
- });
3463
- }
3464
- const targetPaths = fm.anchor.paths;
3465
- const path_neighbors = [];
3466
- if (targetPaths.length > 0) {
3467
- for (const { memory } of all) {
3468
- if (memory.frontmatter.id === fm.id) continue;
3469
- if (relatedSet.has(memory.frontmatter.id)) continue;
3470
- const overlappingPaths = memory.frontmatter.anchor.paths.filter(
3471
- (p) => targetPaths.some((tp) => singlePathsOverlap(p, tp))
3472
- );
3473
- if (overlappingPaths.length === 0) continue;
3474
- const u = getUsage9(usage, memory.frontmatter.id);
3475
- path_neighbors.push({
3476
- id: memory.frontmatter.id,
3477
- type: memory.frontmatter.type,
3478
- scope: memory.frontmatter.scope,
3479
- confidence: deriveConfidence7(memory.frontmatter, u),
3480
- overlap: overlappingPaths,
3481
- body_preview: memory.body.split("\n").slice(0, 3).join("\n").slice(0, 200)
3482
- });
3483
- if (path_neighbors.length >= 10) break;
3484
- }
3485
- }
3486
- const recent_commits = [];
3487
- for (const p of targetPaths.slice(0, 5)) {
3488
- try {
3489
- const commits = await runGitLog2(ctx.paths.root, p, input.git_log_limit);
3490
- for (const c of commits) recent_commits.push({ path: p, ...c });
3491
- } catch {
3492
- }
3493
- }
3494
- const hints = [];
3495
- if (decision.confidence === "low" || decision.confidence === "stale") {
3496
- hints.push(`\u26A0\uFE0F Confidence is ${decision.confidence}. Verify this decision still applies before quoting it.`);
3497
- }
3498
- if (related.length === 0 && path_neighbors.length === 0 && targetPaths.length === 0) {
3499
- hints.push("No related memories and no anchored paths \u2014 this decision is isolated; consider adding related_ids or paths.");
3500
- }
3501
- if (fm.type !== "decision" && fm.type !== "architecture") {
3502
- hints.push(`Memory type is '${fm.type}', not 'decision'/'architecture' \u2014 output may be less informative.`);
3503
- }
3504
- return {
3505
- found: true,
3506
- decision,
3507
- related,
3508
- path_neighbors,
3509
- recent_commits,
3510
- ...hints.length > 0 ? { hints } : {}
3511
- };
3512
- }
3513
- async function runGitLog2(cwd, filePath, limit) {
3514
- const sep = "<<HV>>";
3515
- const fmt = `%h${sep}%an${sep}%ar${sep}%s`;
3516
- const output = await runCommand2(
3517
- "git",
3518
- ["log", "-n", String(limit), `--pretty=format:${fmt}`, "--", filePath],
3519
- cwd
3520
- );
3521
- if (!output.trim()) return [];
3522
- return output.split("\n").map((line) => {
3523
- const [sha = "", author = "", relative_date = "", subject = ""] = line.split(sep);
3524
- return { sha, author, relative_date, subject };
3525
- }).filter((c) => c.sha);
3526
- }
3527
- function runCommand2(cmd, args, cwd) {
3528
- return new Promise((resolve, reject) => {
3529
- const proc = spawn2(cmd, args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
3530
- let stdout = "";
3531
- let stderr = "";
3532
- proc.stdout.on("data", (chunk) => {
3533
- stdout += chunk.toString();
3534
- });
3535
- proc.stderr.on("data", (chunk) => {
3536
- stderr += chunk.toString();
3537
- });
3538
- proc.on("error", reject);
3539
- proc.on("close", (code) => {
3540
- if (code === 0) resolve(stdout);
3541
- else reject(new Error(stderr || `${cmd} exited with code ${code}`));
3542
- });
3543
- });
3544
- }
3545
-
3546
- // src/tools/mem-conflicts.ts
3547
- import { existsSync as existsSync29 } from "fs";
3548
- import {
3549
- deriveConfidence as deriveConfidence8,
3550
- getUsage as getUsage10,
3551
- loadMemoriesFromDir as loadMemoriesFromDir23,
3552
- loadUsageIndex as loadUsageIndex12,
3553
- pathsOverlap as pathsOverlap2,
3554
- tokenizeQuery as tokenizeQuery5
3555
- } from "@hivelore/core";
3556
- import { z as z30 } from "zod";
3557
- var MemConflictsInputSchema = {
3558
- id: z30.string().min(1).describe("Memory id to check for conflicts."),
3559
- 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)."),
3560
- semantic: z30.boolean().default(true).describe("Use embeddings for similarity. Falls back to keyword overlap when embeddings are not installed.")
3561
- };
3562
- var POSITIVE_PATTERNS = /\b(use|prefer|always|should use|do this|recommended|ok to)\b/i;
3563
- var NEGATIVE_PATTERNS = /\b(do not use|don'?t use|never|avoid|forbidden|deprecated|stop using|do NOT|❌)\b/i;
3564
- async function memConflicts(input, ctx) {
3565
- if (!existsSync29(ctx.paths.memoriesDir)) {
3566
- return { found: false, scanned: 0, conflicts: [], notice: "No .ai/memories directory." };
3567
- }
3568
- const all = await loadMemoriesFromDir23(ctx.paths.memoriesDir);
3569
- const target = all.find(({ memory }) => memory.frontmatter.id === input.id);
3570
- if (!target) {
3571
- return { found: false, scanned: 0, conflicts: [], notice: `Memory '${input.id}' not found.` };
3572
- }
3573
- const usage = await loadUsageIndex12(ctx.paths);
3574
- const others = all.filter(
3575
- ({ memory }) => memory.frontmatter.id !== input.id && memory.frontmatter.type !== "session_recap"
3576
- );
3577
- const simScores = input.semantic ? await trySemanticSimilarities(ctx, target, others) : null;
3578
- const targetText = (target.memory.body + " " + target.memory.frontmatter.tags.join(" ")).toLowerCase();
3579
- const targetTokens = new Set(tokenizeQuery5(targetText));
3580
- const targetPolarity = polarity(targetText);
3581
- const targetPaths = target.memory.frontmatter.anchor.paths;
3582
- const explicitContradicts = extractContradictsTags(target.memory.body);
3583
- const conflicts = [];
3584
- for (const other of others) {
3585
- const fm = other.memory.frontmatter;
3586
- const otherText = (other.memory.body + " " + fm.tags.join(" ")).toLowerCase();
3587
- const reasons = [];
3588
- const sim = simScores?.get(fm.id) ?? null;
3589
- const hasPathOverlap = fm.anchor.paths.some((p) => targetPaths.some((tp) => pathsOverlap2(p, tp)));
3590
- const otherTokens = new Set(tokenizeQuery5(otherText));
3591
- const tokenOverlap = countIntersection(targetTokens, otherTokens);
3592
- const isSemanticNeighbor = sim !== null && sim >= input.min_score;
3593
- if (!hasPathOverlap && tokenOverlap < 4 && !isSemanticNeighbor) continue;
3594
- const otherContradicts = extractContradictsTags(other.memory.body);
3595
- if (explicitContradicts.has(fm.id) || otherContradicts.has(input.id)) {
3596
- reasons.push("explicit-contradiction-tag");
3597
- }
3598
- if (target.memory.frontmatter.status === "validated" && fm.status === "rejected" || target.memory.frontmatter.status === "rejected" && fm.status === "validated") {
3599
- if (tokenOverlap >= 4 || isSemanticNeighbor) reasons.push("opposite-status");
3600
- }
3601
- if (hasPathOverlap) {
3602
- const tType = target.memory.frontmatter.type;
3603
- const oType = fm.type;
3604
- const isAttemptVsRule = tType === "attempt" && (oType === "convention" || oType === "decision") || oType === "attempt" && (tType === "convention" || tType === "decision");
3605
- if (isAttemptVsRule) reasons.push("attempt-vs-convention-same-paths");
3606
- }
3607
- if (isSemanticNeighbor) {
3608
- const otherPolarity = polarity(otherText);
3609
- if (targetPolarity === "positive" && otherPolarity === "negative" || targetPolarity === "negative" && otherPolarity === "positive") {
3610
- reasons.push("polarity-keywords");
3611
- }
3612
- }
3613
- if (reasons.length === 0) continue;
3614
- const u = getUsage10(usage, fm.id);
3615
- conflicts.push({
3616
- id: fm.id,
3617
- type: fm.type,
3618
- scope: fm.scope,
3619
- status: fm.status,
3620
- confidence: deriveConfidence8(fm, u),
3621
- body_preview: other.memory.body.split("\n").slice(0, 4).join("\n").slice(0, 300),
3622
- similarity: sim,
3623
- reasons,
3624
- shared_paths: fm.anchor.paths.filter((p) => targetPaths.some((tp) => pathsOverlap2(p, tp)))
3625
- });
3626
- }
3627
- conflicts.sort((a, b) => {
3628
- 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;
3629
- return score(b) - score(a);
3630
- });
3631
- return {
3632
- found: true,
3633
- target: {
3634
- id: target.memory.frontmatter.id,
3635
- type: target.memory.frontmatter.type,
3636
- status: target.memory.frontmatter.status
3637
- },
3638
- scanned: others.length,
3639
- conflicts: conflicts.slice(0, 10)
3640
- };
3641
- }
3642
- function polarity(text) {
3643
- const neg = NEGATIVE_PATTERNS.test(text);
3644
- const pos = POSITIVE_PATTERNS.test(text);
3645
- if (neg && !pos) return "negative";
3646
- if (pos && !neg) return "positive";
3647
- return "neutral";
3648
- }
3649
- function extractContradictsTags(body) {
3650
- const out = /* @__PURE__ */ new Set();
3651
- for (const m of body.matchAll(/#contradicts:([\w-]+)/g)) {
3652
- if (m[1]) out.add(m[1]);
3653
- }
3654
- return out;
3655
- }
3656
- function countIntersection(a, b) {
3657
- let n = 0;
3658
- for (const x of a) if (b.has(x)) n++;
3659
- return n;
3660
- }
3661
- async function trySemanticSimilarities(ctx, target, others) {
3662
- let mod;
3663
- try {
3664
- mod = await import("@hivelore/embeddings");
3665
- } catch {
3666
- return null;
3667
- }
3668
- const result = await mod.semanticSearch(
3669
- ctx.paths,
3670
- target.memory.body,
3671
- { limit: others.length }
3672
- );
3673
- if (!result) return null;
3674
- const map = /* @__PURE__ */ new Map();
3675
- for (const hit of result.hits) map.set(hit.id, hit.score);
3676
- return map;
3677
- }
3678
-
3679
3355
  // src/tools/precommit-check.ts
3680
- import { pathsOverlap as pathsOverlap3 } from "@hivelore/core";
3681
- import { z as z31 } from "zod";
3356
+ import { pathsOverlap as pathsOverlap2 } from "@hivelore/core";
3357
+ import { z as z27 } from "zod";
3682
3358
  var PreCommitCheckInputSchema = {
3683
- diff: z31.string().optional().describe(
3359
+ diff: z27.string().optional().describe(
3684
3360
  "Raw unified diff text to scan. If omitted, only `paths` is used. When called from a pre-commit hook, pipe the output of `git diff --cached`."
3685
3361
  ),
3686
- paths: z31.array(z31.string()).default([]).describe("Project-relative paths affected by the change. At least one of `diff` or `paths` should be provided."),
3687
- block_on: z31.enum(["any", "high-confidence", "never"]).default("high-confidence").describe(
3362
+ paths: z27.array(z27.string()).default([]).describe("Project-relative paths affected by the change. At least one of `diff` or `paths` should be provided."),
3363
+ block_on: z27.enum(["any", "high-confidence", "never"]).default("high-confidence").describe(
3688
3364
  "When to set should_block=true: 'any' = any warning blocks; 'high-confidence' = only warnings from authoritative/trusted memories block; 'never' = report only, never block."
3689
3365
  ),
3690
- semantic: z31.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index)."),
3691
- anchored_blocks: z31.boolean().default(false).describe(
3366
+ semantic: z27.boolean().default(true).describe("Enable semantic search in anti_patterns_check (requires embeddings index)."),
3367
+ anchored_blocks: z27.boolean().default(false).describe(
3692
3368
  "When true, ALSO block a high-confidence anti-pattern (attempt/gotcha) that is anchored to a touched file AND corroborated by the diff (literal token overlap, or semantic >= 0.45) \u2014 not just very strong semantic matches. Powers the 'anchored' enforcement gate. Config/docs-only commits are still downgraded. Default false preserves the soft, semantic-only blocking behavior."
3693
3369
  )
3694
3370
  };
@@ -3757,7 +3433,7 @@ async function preCommitCheck(input, ctx) {
3757
3433
  }
3758
3434
  function classifyWarning(warning, paths, anchoredBlocks = false) {
3759
3435
  const codeFiles = paths.filter((p) => !p.startsWith(".ai/") && !isHaiveOwnedPath(p));
3760
- const anchorHits = (warning.anchor_paths ?? []).length > 0 ? codeFiles.filter((p) => (warning.anchor_paths ?? []).some((ap) => pathsOverlap3(ap, p))) : [];
3436
+ const anchorHits = (warning.anchor_paths ?? []).length > 0 ? codeFiles.filter((p) => (warning.anchor_paths ?? []).some((ap) => pathsOverlap2(ap, p))) : [];
3761
3437
  const affectedFiles = anchorHits.length > 0 ? anchorHits : codeFiles;
3762
3438
  const repairCommand = repairCommandForWarning(warning, affectedFiles);
3763
3439
  const fileDowngrade = fileTypeDowngradeReason(warning, affectedFiles);
@@ -3993,228 +3669,15 @@ function repairTargetPathForWarning(warning, paths) {
3993
3669
  return usablePaths[0];
3994
3670
  }
3995
3671
 
3996
- // src/tools/pattern-detect.ts
3997
- import { mkdir as mkdir8, writeFile as writeFile15 } from "fs/promises";
3998
- import { existsSync as existsSync30 } from "fs";
3999
- import path14 from "path";
4000
- import { execSync as execSync3 } from "child_process";
4001
- import {
4002
- buildFrontmatter as buildFrontmatter5,
4003
- memoryFilePath as memoryFilePath6,
4004
- readUsageEvents,
4005
- serializeMemory as serializeMemory13
4006
- } from "@hivelore/core";
4007
- import { z as z32 } from "zod";
4008
- var CONFIG_PATTERNS = [
4009
- ".eslintrc",
4010
- "eslint.config",
4011
- "prettier.config",
4012
- ".prettierrc",
4013
- "tsconfig",
4014
- "jsconfig",
4015
- "vitest.config",
4016
- "jest.config",
4017
- ".env.example",
4018
- ".env.defaults",
4019
- "tailwind.config",
4020
- "vite.config",
4021
- "next.config",
4022
- "babel.config",
4023
- "postcss.config",
4024
- "renovate.json",
4025
- "dependabot.yml"
4026
- ];
4027
- var MAX_DIFF_BYTES = 4096;
4028
- var HOT_FILE_MIN = 3;
4029
- var PatternDetectInputSchema = {
4030
- since_days: z32.number().int().min(1).default(7).describe("Look-back window in days for both git history and usage log."),
4031
- dry_run: z32.boolean().default(false).describe("When true, report matches without writing any memory files."),
4032
- scope: z32.enum(["personal", "team"]).default("team").describe("Scope for proposed memories.")
4033
- };
4034
- async function patternDetect(input, ctx) {
4035
- if (!existsSync30(ctx.paths.haiveDir)) {
4036
- return {
4037
- scanned_events: 0,
4038
- matches: [],
4039
- saved: 0,
4040
- saved_ids: [],
4041
- notice: "No .ai/ directory found. Run 'hivelore init' first."
4042
- };
4043
- }
4044
- const matches = [];
4045
- try {
4046
- const changedFiles = gitChangedFiles(ctx.paths.root, input.since_days);
4047
- const configFiles = changedFiles.filter(
4048
- (f) => CONFIG_PATTERNS.some((p) => path14.basename(f.toLowerCase()).includes(p))
4049
- );
4050
- for (const file of configFiles.slice(0, 5)) {
4051
- const diff = gitFileDiff(ctx.paths.root, file, input.since_days);
4052
- if (!diff) continue;
4053
- const parentDir = path14.basename(path14.dirname(file));
4054
- const baseName = path14.basename(file).replace(/\.[^.]+$/, "");
4055
- const slug = `${parentDir}-${baseName}`.replace(/[^a-z0-9]/gi, "-").toLowerCase().slice(0, 40);
4056
- matches.push({
4057
- kind: "config_change",
4058
- signal: `Config file modified: ${file}`,
4059
- proposed_type: "convention",
4060
- proposed_slug: `config-change-${slug}`,
4061
- proposed_body: [
4062
- `# Config change: \`${file}\``,
4063
- "",
4064
- "This configuration file was recently modified. The diff below captures the intent.",
4065
- "Review and update this memory with the **reason** for the change if known.",
4066
- "",
4067
- "```diff",
4068
- diff.slice(0, MAX_DIFF_BYTES),
4069
- "```"
4070
- ].join("\n"),
4071
- anchor_paths: [file]
4072
- });
4073
- }
4074
- } catch {
4075
- }
4076
- const events = await readUsageEvents(ctx.paths);
4077
- const cutoff = Date.now() - input.since_days * 24 * 60 * 60 * 1e3;
4078
- const recent = events.filter((e) => Date.parse(e.at) >= cutoff);
4079
- const pathCounts = /* @__PURE__ */ new Map();
4080
- for (const e of recent) {
4081
- if (!["mem_tried", "mem_observe", "mem_save"].includes(e.tool)) continue;
4082
- if (!e.summary) continue;
4083
- const tokens = e.summary.match(/[^\s"'`,;()[\]{}]+\.[a-zA-Z]{1,6}/g) ?? [];
4084
- for (const t of tokens) {
4085
- const key = t.toLowerCase();
4086
- const existing = pathCounts.get(key);
4087
- if (existing) {
4088
- existing.count++;
4089
- existing.tools.add(e.tool);
4090
- } else {
4091
- pathCounts.set(key, { count: 1, tools: /* @__PURE__ */ new Set([e.tool]) });
4092
- }
4093
- }
4094
- }
4095
- for (const [p, { count, tools }] of pathCounts) {
4096
- if (count < HOT_FILE_MIN) continue;
4097
- const isGotchaSignal = tools.has("mem_tried") || tools.has("mem_observe");
4098
- if (!isGotchaSignal) continue;
4099
- const slug = p.replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").slice(0, 40);
4100
- matches.push({
4101
- kind: "repeated_path",
4102
- signal: `Path '${p}' appears ${count}\xD7 in mem_tried/mem_observe events`,
4103
- proposed_type: "gotcha",
4104
- proposed_slug: `repeated-issue-${slug}`,
4105
- proposed_body: [
4106
- `# Recurring issue near \`${p}\``,
4107
- "",
4108
- `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.`,
4109
- "",
4110
- `**Source signals:** ${[...tools].join(", ")} (${count} events)`
4111
- ].join("\n"),
4112
- anchor_paths: [p]
4113
- });
4114
- }
4115
- for (const [p, { count, tools }] of pathCounts) {
4116
- if (count < HOT_FILE_MIN) continue;
4117
- if (tools.has("mem_tried") || tools.has("mem_observe")) continue;
4118
- if (CONFIG_PATTERNS.some((cp) => path14.basename(p).includes(cp))) continue;
4119
- const slug = p.replace(/[^a-z0-9]/g, "-").replace(/-+/g, "-").slice(0, 40);
4120
- matches.push({
4121
- kind: "hot_file",
4122
- signal: `Path '${p}' referenced ${count}\xD7 across mem_save events`,
4123
- proposed_type: "convention",
4124
- proposed_slug: `hot-file-${slug}`,
4125
- proposed_body: [
4126
- `# Frequent edits to \`${p}\``,
4127
- "",
4128
- `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.`,
4129
- "",
4130
- "**Suggested action:** review recent memories anchored to this path and extract the common pattern as a named convention."
4131
- ].join("\n"),
4132
- anchor_paths: [p]
4133
- });
4134
- }
4135
- if (matches.length === 0) {
4136
- return {
4137
- scanned_events: recent.length,
4138
- matches: [],
4139
- saved: 0,
4140
- saved_ids: [],
4141
- notice: `No patterns detected in the last ${input.since_days} days (${recent.length} events scanned).`
4142
- };
4143
- }
4144
- if (input.dry_run) {
4145
- return { scanned_events: recent.length, matches, saved: 0, saved_ids: [] };
4146
- }
4147
- const savedIds = [];
4148
- for (const match of matches) {
4149
- try {
4150
- const fm = buildFrontmatter5({
4151
- type: match.proposed_type,
4152
- slug: match.proposed_slug,
4153
- scope: input.scope,
4154
- tags: ["pattern-detect", match.kind],
4155
- paths: match.anchor_paths,
4156
- status: "proposed"
4157
- });
4158
- const file = memoryFilePath6(
4159
- ctx.paths,
4160
- fm.scope === "shared" ? "team" : fm.scope,
4161
- fm.id,
4162
- void 0
4163
- );
4164
- if (existsSync30(file)) continue;
4165
- await mkdir8(path14.dirname(file), { recursive: true });
4166
- await writeFile15(
4167
- file,
4168
- serializeMemory13({ frontmatter: fm, body: match.proposed_body }),
4169
- "utf8"
4170
- );
4171
- savedIds.push(fm.id);
4172
- } catch {
4173
- }
4174
- }
4175
- return {
4176
- scanned_events: recent.length,
4177
- matches,
4178
- saved: savedIds.length,
4179
- saved_ids: savedIds
4180
- };
4181
- }
4182
- function gitChangedFiles(root, sinceDays) {
4183
- try {
4184
- const out = execSync3(
4185
- `git log --name-only --pretty="" --diff-filter=AM --since="${sinceDays} days ago"`,
4186
- { cwd: root, encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }
4187
- );
4188
- return [...new Set(out.split("\n").map((l) => l.trim()).filter(Boolean))];
4189
- } catch {
4190
- return [];
4191
- }
4192
- }
4193
- function gitFileDiff(root, file, sinceDays) {
4194
- try {
4195
- const out = execSync3(
4196
- `git log -p --follow --since="${sinceDays} days ago" -- "${file}"`,
4197
- { cwd: root, encoding: "utf8", timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }
4198
- );
4199
- if (!out.trim()) return null;
4200
- const diffLines = out.split("\n").filter(
4201
- (l) => l.startsWith("+") || l.startsWith("-") || l.startsWith("@@") || l.startsWith("diff")
4202
- );
4203
- return diffLines.join("\n").slice(0, MAX_DIFF_BYTES) || null;
4204
- } catch {
4205
- return null;
4206
- }
4207
- }
4208
-
4209
3672
  // src/tools/mem-conflict-candidates.ts
4210
- import { existsSync as existsSync31 } from "fs";
3673
+ import { existsSync as existsSync26 } from "fs";
4211
3674
  import {
4212
3675
  findLexicalConflictPairs,
4213
3676
  findTopicStatusConflictPairs,
4214
- loadMemoriesFromDir as loadMemoriesFromDir24,
3677
+ loadMemoriesFromDir as loadMemoriesFromDir21,
4215
3678
  planConflictResolution
4216
3679
  } from "@hivelore/core";
4217
- import { z as z33 } from "zod";
3680
+ import { z as z28 } from "zod";
4218
3681
  function suggestResolution(byId, idA, idB) {
4219
3682
  const a = byId.get(idA);
4220
3683
  const b = byId.get(idB);
@@ -4228,17 +3691,17 @@ function suggestResolution(byId, idA, idB) {
4228
3691
  };
4229
3692
  }
4230
3693
  var MemConflictCandidatesInputSchema = {
4231
- since_days: z33.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
4232
- types: z33.array(z33.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
4233
- min_jaccard: z33.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
4234
- max_pairs: z33.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
4235
- 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."),
4236
- max_topic_pairs: z33.number().int().positive().max(100).default(20).describe(
3694
+ since_days: z28.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
3695
+ types: z28.array(z28.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
3696
+ min_jaccard: z28.number().min(0).max(1).default(0.45).describe("Minimum Jaccard token similarity to surface as a candidate pair"),
3697
+ max_pairs: z28.number().int().positive().max(100).default(20).describe("Cap pairs returned"),
3698
+ max_scan: z28.number().int().positive().max(2e3).default(500).describe("Maximum memories sampled for O(n\xB2) scan \u2014 excess dropped after chronological sort."),
3699
+ max_topic_pairs: z28.number().int().positive().max(100).default(20).describe(
4237
3700
  "Cap for extra signal: memories sharing the same topic with validated vs rejected status."
4238
3701
  )
4239
3702
  };
4240
3703
  async function memConflictCandidates(input, ctx) {
4241
- if (!existsSync31(ctx.paths.memoriesDir)) {
3704
+ if (!existsSync26(ctx.paths.memoriesDir)) {
4242
3705
  return {
4243
3706
  pairs: [],
4244
3707
  topic_status_pairs: [],
@@ -4247,7 +3710,7 @@ async function memConflictCandidates(input, ctx) {
4247
3710
  notice: "No .ai/memories directory."
4248
3711
  };
4249
3712
  }
4250
- const all = await loadMemoriesFromDir24(ctx.paths.memoriesDir);
3713
+ const all = await loadMemoriesFromDir21(ctx.paths.memoriesDir);
4251
3714
  const byId = new Map(all.map((m) => [m.memory.frontmatter.id, m]));
4252
3715
  const { pairs, scanned, truncated } = findLexicalConflictPairs(all, {
4253
3716
  sinceDays: input.since_days,
@@ -4277,9 +3740,9 @@ async function memConflictCandidates(input, ctx) {
4277
3740
 
4278
3741
  // src/tools/mem-resolve-project.ts
4279
3742
  import { resolveProjectInfo } from "@hivelore/core";
4280
- import { z as z34 } from "zod";
3743
+ import { z as z29 } from "zod";
4281
3744
  var MemResolveProjectInputSchema = {
4282
- cwd: z34.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
3745
+ cwd: z29.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
4283
3746
  };
4284
3747
  async function memResolveProject(input, _ctx) {
4285
3748
  void _ctx;
@@ -4293,10 +3756,10 @@ async function memResolveProject(input, _ctx) {
4293
3756
 
4294
3757
  // src/tools/mem-suggest-topic.ts
4295
3758
  import { MemoryTypeSchema, suggestTopicKey } from "@hivelore/core";
4296
- import { z as z35 } from "zod";
3759
+ import { z as z30 } from "zod";
4297
3760
  var MemSuggestTopicInputSchema = {
4298
3761
  type: MemoryTypeSchema.describe("Memory kind \u2014 drives the suggested topic family."),
4299
- title: z35.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
3762
+ title: z30.string().min(1).describe("Short title or phrase (headers, headings) \u2014 turned into slug")
4300
3763
  };
4301
3764
  async function memSuggestTopic(input, _ctx) {
4302
3765
  void _ctx;
@@ -4305,19 +3768,19 @@ async function memSuggestTopic(input, _ctx) {
4305
3768
  }
4306
3769
 
4307
3770
  // src/tools/mem-timeline.ts
4308
- import { existsSync as existsSync32 } from "fs";
4309
- import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir25 } from "@hivelore/core";
4310
- import { z as z36 } from "zod";
3771
+ import { existsSync as existsSync27 } from "fs";
3772
+ import { collectTimelineEntries, loadMemoriesFromDir as loadMemoriesFromDir22 } from "@hivelore/core";
3773
+ import { z as z31 } from "zod";
4311
3774
  var MemTimelineInputSchema = {
4312
- memory_id: z36.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
4313
- topic: z36.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
4314
- limit: z36.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
3775
+ memory_id: z31.string().optional().describe("Seed id \u2014 expands via related_ids, topic, anchors"),
3776
+ topic: z31.string().optional().describe("Frontmatter.topic value \u2014 chronological list when memory_id omitted"),
3777
+ limit: z31.number().int().positive().max(100).default(30).describe("Max timeline entries returned")
4315
3778
  };
4316
3779
  async function memTimeline(input, ctx) {
4317
- if (!existsSync32(ctx.paths.memoriesDir)) {
3780
+ if (!existsSync27(ctx.paths.memoriesDir)) {
4318
3781
  return { entries: [], total: 0, notice: "No .ai/memories directory." };
4319
3782
  }
4320
- const all = await loadMemoriesFromDir25(ctx.paths.memoriesDir);
3783
+ const all = await loadMemoriesFromDir22(ctx.paths.memoriesDir);
4321
3784
  const { entries, notice } = collectTimelineEntries(all, {
4322
3785
  memoryId: input.memory_id,
4323
3786
  topic: input.topic,
@@ -4326,47 +3789,13 @@ async function memTimeline(input, ctx) {
4326
3789
  return { entries, total: entries.length, notice };
4327
3790
  }
4328
3791
 
4329
- // src/tools/runtime-journal-append.ts
4330
- import { appendRuntimeJournalEntry as appendRuntimeJournalEntry2 } from "@hivelore/core";
4331
- import { z as z37 } from "zod";
4332
- var RuntimeJournalAppendInputSchema = {
4333
- message: z37.string().min(1).describe("Short line to append to the runtime session journal"),
4334
- kind: z37.enum(["note", "session_end", "mcp"]).default("note"),
4335
- tool: z37.string().optional().describe("When kind=mcp, which tool name (optional)")
4336
- };
4337
- async function runtimeJournalAppend(input, ctx) {
4338
- await appendRuntimeJournalEntry2(ctx.paths, {
4339
- kind: input.kind,
4340
- message: input.message,
4341
- ...input.tool ? { tool: input.tool } : {}
4342
- });
4343
- return {
4344
- ok: true,
4345
- path_hint: `${ctx.paths.runtimeDir}/session-journal.ndjson`
4346
- };
4347
- }
4348
-
4349
- // src/tools/runtime-journal-tail.ts
4350
- import { readRuntimeJournalTail } from "@hivelore/core";
4351
- import { z as z38 } from "zod";
4352
- var RuntimeJournalTailInputSchema = {
4353
- limit: z38.number().int().positive().max(500).default(30).describe("Last N journal entries to return")
4354
- };
4355
- async function runtimeJournalTail(input, ctx) {
4356
- const entries = await readRuntimeJournalTail(ctx.paths, input.limit);
4357
- if (entries.length === 0) {
4358
- return { entries: [], empty: true };
4359
- }
4360
- return { entries };
4361
- }
4362
-
4363
3792
  // src/prompts/bootstrap-project.ts
4364
- import { z as z39 } from "zod";
3793
+ import { z as z32 } from "zod";
4365
3794
  var BootstrapProjectArgsSchema = {
4366
- module: z39.string().optional().describe(
3795
+ module: z32.string().optional().describe(
4367
3796
  "Optional module name to scope the analysis to (writes to .ai/modules/<module>/context.md)"
4368
3797
  ),
4369
- focus: z39.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
3798
+ focus: z32.string().optional().describe("Optional area to emphasize (e.g. 'data layer', 'API surface')")
4370
3799
  };
4371
3800
  var ROOT_TEMPLATE = `# Project context
4372
3801
 
@@ -4449,16 +3878,16 @@ ${template}\`\`\`
4449
3878
 
4450
3879
  // src/prompts/bootstrap-repo.ts
4451
3880
  import { readFile as readFile7, readdir as readdir5 } from "fs/promises";
4452
- import { existsSync as existsSync33 } from "fs";
3881
+ import { existsSync as existsSync28 } from "fs";
4453
3882
  import {
4454
3883
  assessBootstrapState as assessBootstrapState2,
4455
- loadCodeMap as loadCodeMap5,
4456
- loadMemoriesFromDir as loadMemoriesFromDir26,
3884
+ loadCodeMap as loadCodeMap4,
3885
+ loadMemoriesFromDir as loadMemoriesFromDir23,
4457
3886
  renderBootstrapChecklist as renderBootstrapChecklist2
4458
3887
  } from "@hivelore/core";
4459
- import { z as z40 } from "zod";
3888
+ import { z as z33 } from "zod";
4460
3889
  var BootstrapRepoArgsSchema = {
4461
- focus: z40.string().optional().describe("Optional area to emphasize first (e.g. 'payments', 'auth').")
3890
+ focus: z33.string().optional().describe("Optional area to emphasize first (e.g. 'payments', 'auth').")
4462
3891
  };
4463
3892
  async function currentAssessment(ctx) {
4464
3893
  let projectContextRaw = "";
@@ -4466,8 +3895,8 @@ async function currentAssessment(ctx) {
4466
3895
  projectContextRaw = await readFile7(ctx.paths.projectContext, "utf8");
4467
3896
  } catch {
4468
3897
  }
4469
- const memories = existsSync33(ctx.paths.memoriesDir) ? await loadMemoriesFromDir26(ctx.paths.memoriesDir) : [];
4470
- const codeMap = await loadCodeMap5(ctx.paths);
3898
+ const memories = existsSync28(ctx.paths.memoriesDir) ? await loadMemoriesFromDir23(ctx.paths.memoriesDir) : [];
3899
+ const codeMap = await loadCodeMap4(ctx.paths);
4471
3900
  let existingModules = [];
4472
3901
  try {
4473
3902
  const entries = await readdir5(ctx.paths.modulesContextDir, { withFileTypes: true });
@@ -4534,10 +3963,10 @@ Main code areas detected: ${areas}
4534
3963
  }
4535
3964
 
4536
3965
  // src/prompts/post-task.ts
4537
- import { z as z41 } from "zod";
3966
+ import { z as z34 } from "zod";
4538
3967
  var PostTaskArgsSchema = {
4539
- task_summary: z41.string().optional().describe("One sentence describing what you just did"),
4540
- files_touched: z41.array(z41.string()).optional().describe("Files you created or modified during the task")
3968
+ task_summary: z34.string().optional().describe("One sentence describing what you just did"),
3969
+ files_touched: z34.array(z34.string()).optional().describe("Files you created or modified during the task")
4541
3970
  };
4542
3971
  function postTaskPrompt(args, ctx) {
4543
3972
  const taskLine = args.task_summary ? `
@@ -4634,12 +4063,12 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
4634
4063
  }
4635
4064
 
4636
4065
  // src/prompts/import-docs.ts
4637
- import { z as z42 } from "zod";
4066
+ import { z as z35 } from "zod";
4638
4067
  var ImportDocsArgsSchema = {
4639
- content: z42.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
4640
- source: z42.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
4641
- scope: z42.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
4642
- dry_run: z42.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
4068
+ content: z35.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
4069
+ source: z35.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
4070
+ scope: z35.enum(["personal", "team"]).default("team").describe("Scope to assign to created memories"),
4071
+ dry_run: z35.boolean().default(false).describe("If true, describe what would be saved without actually calling mem_save")
4643
4072
  };
4644
4073
  function importDocsPrompt(args, ctx) {
4645
4074
  const sourceLine = args.source ? `
@@ -4705,7 +4134,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
4705
4134
  // src/server.ts
4706
4135
  import { hasRecentBriefingMarker, loadConfigSync } from "@hivelore/core";
4707
4136
  var SERVER_NAME = "hivelore";
4708
- var SERVER_VERSION = "0.30.1";
4137
+ var SERVER_VERSION = "0.33.0";
4709
4138
  function jsonResult(data) {
4710
4139
  return {
4711
4140
  content: [
@@ -4753,14 +4182,7 @@ var MAINTENANCE_PROFILE_TOOLS = [
4753
4182
  "ingest_findings"
4754
4183
  ];
4755
4184
  var EXPERIMENTAL_PROFILE_TOOLS = [
4756
- ...MAINTENANCE_PROFILE_TOOLS,
4757
- "mem_observe",
4758
- "why_this_file",
4759
- "why_this_decision",
4760
- "mem_conflicts_with",
4761
- "pattern_detect",
4762
- "runtime_journal_append",
4763
- "runtime_journal_tail"
4185
+ ...MAINTENANCE_PROFILE_TOOLS
4764
4186
  ];
4765
4187
  var TOOL_PROFILES = {
4766
4188
  enforcement: new Set(ENFORCEMENT_PROFILE_TOOLS),
@@ -4775,7 +4197,6 @@ var BRIEFING_TOOLS = /* @__PURE__ */ new Set(["get_briefing", "mem_relevant_to"]
4775
4197
  var MUTATING_TOOLS = /* @__PURE__ */ new Set([
4776
4198
  "mem_save",
4777
4199
  "mem_tried",
4778
- "mem_observe",
4779
4200
  "mem_session_end",
4780
4201
  "bootstrap_project_save",
4781
4202
  "mem_update",
@@ -4783,8 +4204,6 @@ var MUTATING_TOOLS = /* @__PURE__ */ new Set([
4783
4204
  "mem_reject",
4784
4205
  "mem_delete",
4785
4206
  "mem_feedback",
4786
- "runtime_journal_append",
4787
- "pattern_detect",
4788
4207
  "ingest_findings",
4789
4208
  "propose_sensor"
4790
4209
  ]);
@@ -4847,7 +4266,7 @@ function createHaiveServer(options = {}) {
4847
4266
  " - A domain term and what it means in this codebase",
4848
4267
  "",
4849
4268
  "DO NOT USE for failed approaches \u2192 use mem_tried instead (better structure).",
4850
- "DO NOT USE for code discoveries during exploration \u2192 use mem_observe instead.",
4269
+ "For reactive code discoveries during exploration, prefer a compact gotcha via mem_save.",
4851
4270
  "",
4852
4271
  "PARAMETERS:",
4853
4272
  " type \u2014 convention | decision | gotcha | architecture | glossary | attempt",
@@ -4974,38 +4393,6 @@ function createHaiveServer(options = {}) {
4974
4393
  return jsonResult(await ingestFindings(input, context));
4975
4394
  }
4976
4395
  );
4977
- registerTool(
4978
- "mem_observe",
4979
- [
4980
- "Capture a code-level discovery made WHILE READING existing code.",
4981
- "",
4982
- "USE THIS when you read a file and spot something the team may not know about:",
4983
- " - A bug or race condition hiding in the code",
4984
- " - A security gap or missing validation",
4985
- " - An inconsistency between two files",
4986
- " - A missing configuration or environment variable",
4987
- " - Anything that could silently break in production",
4988
- "",
4989
- "DIFFERENCE from mem_save: mem_observe is for REACTIVE discoveries during code",
4990
- "reading. mem_save is for deliberate knowledge capture (conventions, decisions).",
4991
- "",
4992
- "Auto-validated, anchored to file paths for staleness detection.",
4993
- "",
4994
- "PARAMETERS:",
4995
- " what \u2014 one-line title (e.g. 'MobilePaymentController: duplicate @RequestBody')",
4996
- " where \u2014 file path(s) where the issue lives",
4997
- " impact \u2014 what breaks or could break because of this",
4998
- " fix \u2014 suggested fix (optional)",
4999
- " scope \u2014 team (default, since discoveries benefit everyone)",
5000
- "",
5001
- "RETURNS: { id, file_path }"
5002
- ].join("\n"),
5003
- MemObserveInputSchema,
5004
- async (input) => {
5005
- tracker.record("mem_observe", input.where);
5006
- return jsonResult(await memObserve(input, context));
5007
- }
5008
- );
5009
4396
  registerTool(
5010
4397
  "mem_session_end",
5011
4398
  [
@@ -5465,26 +4852,6 @@ function createHaiveServer(options = {}) {
5465
4852
  return jsonResult(await codeSearch(input, context));
5466
4853
  }
5467
4854
  );
5468
- registerTool(
5469
- "why_this_file",
5470
- [
5471
- "One-shot file-context lookup: combines recent git history, memories anchored",
5472
- "to the path, and the code-map entry. Answers 'why is this file the way it is?'",
5473
- "in a single call instead of 3-4 manual ones.",
5474
- "",
5475
- "PARAMETERS:",
5476
- " path \u2014 project-relative path (required)",
5477
- " git_log_limit \u2014 recent commits to include (default 5)",
5478
- " memory_limit \u2014 anchored memories cap (default 5)",
5479
- "",
5480
- "RETURNS: { file, exists, recent_commits: [...], memories: [...], code_map_entry, hints? }"
5481
- ].join("\n"),
5482
- WhyThisFileInputSchema,
5483
- async (input) => {
5484
- tracker.record("why_this_file", input.path);
5485
- return jsonResult(await whyThisFile(input, context));
5486
- }
5487
- );
5488
4855
  registerTool(
5489
4856
  "anti_patterns_check",
5490
4857
  [
@@ -5534,54 +4901,6 @@ function createHaiveServer(options = {}) {
5534
4901
  return jsonResult(await memDistill(input, context));
5535
4902
  }
5536
4903
  );
5537
- registerTool(
5538
- "why_this_decision",
5539
- [
5540
- "Trace the genealogy of a memory (especially decision/architecture):",
5541
- "the memory itself + memories explicitly linked via related_ids + memories",
5542
- "anchored to overlapping paths + recent commits touching those paths.",
5543
- "",
5544
- "USE WHEN you find a memory and need to understand WHY it was made and",
5545
- "what surrounds it. One call instead of 4-5 manual lookups.",
5546
- "",
5547
- "PARAMETERS:",
5548
- " id \u2014 memory id (required)",
5549
- " git_log_limit \u2014 how many recent commits per anchor path (default 5)",
5550
- "",
5551
- "RETURNS: { decision, related: [...], path_neighbors: [...], recent_commits: [...] }"
5552
- ].join("\n"),
5553
- WhyThisDecisionInputSchema,
5554
- async (input) => {
5555
- tracker.record("why_this_decision", input.id);
5556
- return jsonResult(await whyThisDecision(input, context));
5557
- }
5558
- );
5559
- registerTool(
5560
- "mem_conflicts_with",
5561
- [
5562
- "Detect memories that potentially CONTRADICT a given memory.",
5563
- "",
5564
- "USE BEFORE relying on a memory's advice \u2014 surfaces 'another memory says",
5565
- "the opposite'. Detection uses several heuristics layered together:",
5566
- "",
5567
- " 1. Opposite status \u2014 validated vs rejected on overlapping topic",
5568
- " 2. attempt-vs-convention on overlapping anchor paths",
5569
- " 3. Polarity keywords \u2014 'use X' vs 'do not use X' among semantic neighbors",
5570
- " 4. Explicit #contradicts:<id> tags in either body",
5571
- "",
5572
- "PARAMETERS:",
5573
- " id \u2014 memory id to check (required)",
5574
- " min_score \u2014 minimum cosine similarity for semantic neighbors (default 0.5)",
5575
- " semantic \u2014 use embeddings (default true)",
5576
- "",
5577
- "RETURNS: { found, target, scanned, conflicts: [{ id, reasons, similarity, ... }] }"
5578
- ].join("\n"),
5579
- MemConflictsInputSchema,
5580
- async (input) => {
5581
- tracker.record("mem_conflicts_with", input.id);
5582
- return jsonResult(await memConflicts(input, context));
5583
- }
5584
- );
5585
4904
  registerTool(
5586
4905
  "mem_conflict_candidates",
5587
4906
  [
@@ -5590,7 +4909,7 @@ function createHaiveServer(options = {}) {
5590
4909
  " 1. Lexical similarity (Jaccard) on decision/architecture-like pairs",
5591
4910
  " 2. Same frontmatter.topic with validated vs rejected \u2014 quick human-review signal",
5592
4911
  "",
5593
- "Advisory only \u2014 follow with mem_conflicts_with on specific ids.",
4912
+ "Advisory only \u2014 review the listed candidate ids, then resolve with mem_update/mem_delete.",
5594
4913
  "",
5595
4914
  "PARAMETERS:",
5596
4915
  " since_days, types, min_jaccard, max_pairs, max_scan, max_topic_pairs",
@@ -5603,32 +4922,6 @@ function createHaiveServer(options = {}) {
5603
4922
  return jsonResult(await memConflictCandidates(input, context));
5604
4923
  }
5605
4924
  );
5606
- registerTool(
5607
- "runtime_journal_append",
5608
- [
5609
- "Append one line to `.ai/.runtime/session-journal.ndjson` \u2014 machine-local session continuity.",
5610
- "",
5611
- "Does NOT replace team memories; complements mem_session_end recaps for local traces.",
5612
- "",
5613
- "PARAMETERS: message, kind (note|session_end|mcp), optional tool",
5614
- "",
5615
- "RETURNS: { ok, path_hint }"
5616
- ].join("\n"),
5617
- RuntimeJournalAppendInputSchema,
5618
- async (input) => jsonResult(await runtimeJournalAppend(input, context))
5619
- );
5620
- registerTool(
5621
- "runtime_journal_tail",
5622
- [
5623
- "Read the last N entries from the runtime session journal (parsed JSON lines).",
5624
- "",
5625
- "PARAMETERS: limit (default 30, max 500)",
5626
- "",
5627
- "RETURNS: { entries: [...], empty?: true }"
5628
- ].join("\n"),
5629
- RuntimeJournalTailInputSchema,
5630
- async (input) => jsonResult(await runtimeJournalTail(input, context))
5631
- );
5632
4925
  registerTool(
5633
4926
  "pre_commit_check",
5634
4927
  [
@@ -5655,37 +4948,6 @@ function createHaiveServer(options = {}) {
5655
4948
  return jsonResult(await preCommitCheck(input, context));
5656
4949
  }
5657
4950
  );
5658
- registerTool(
5659
- "pattern_detect",
5660
- [
5661
- "Heuristic memory detector \u2014 finds knowledge worth saving WITHOUT calling an LLM.",
5662
- "",
5663
- "Runs three signals over local git history and the tool-usage log:",
5664
- " 1. CONFIG_CHANGE \u2014 config files modified recently (tsconfig, eslint, prettier, \u2026)",
5665
- " \u2192 proposes a convention memory with the git diff as body.",
5666
- " 2. REPEATED_PATH \u2014 same file appears \u22653\xD7 in mem_tried/mem_observe events",
5667
- " \u2192 proposes a gotcha memory anchored to that path.",
5668
- " 3. HOT_FILE \u2014 source file referenced \u22653\xD7 in writing-tool events",
5669
- " \u2192 proposes a convention memory (frequent edits = pattern emerging).",
5670
- "",
5671
- "Saves memories with status='proposed'. They feed into auto-promote (Phase 4)",
5672
- "or are surfaced in the next post_task distillation for LLM review.",
5673
- "",
5674
- "USE periodically (e.g. end of sprint) or trigger from post-commit hook.",
5675
- "",
5676
- "PARAMETERS:",
5677
- " since_days \u2014 look-back window in days (default 7)",
5678
- " dry_run \u2014 report matches without saving (default false)",
5679
- " scope \u2014 'team' (default) | 'personal'",
5680
- "",
5681
- "RETURNS: { scanned_events, matches: [{kind, signal, proposed_type, \u2026}], saved, saved_ids }"
5682
- ].join("\n"),
5683
- PatternDetectInputSchema,
5684
- async (input) => {
5685
- tracker.record("pattern_detect", `since=${input.since_days}d/dry_run=${input.dry_run}`);
5686
- return jsonResult(await patternDetect(input, context));
5687
- }
5688
- );
5689
4951
  registerTool(
5690
4952
  "mem_diff",
5691
4953
  [
@@ -5736,7 +4998,7 @@ function createHaiveServer(options = {}) {
5736
4998
  [
5737
4999
  "\u2B50 Post-task reflection \u2014 run at the end of every session to capture what you learned:",
5738
5000
  "failed approaches (mem_tried), new conventions/decisions/gotchas (mem_save),",
5739
- "code discoveries (mem_observe), and an end-of-session recap (mem_session_end).",
5001
+ "failed approaches (mem_tried), and an end-of-session recap (mem_session_end).",
5740
5002
  "In autopilot mode a minimal recap saves automatically; calling this produces a richer one."
5741
5003
  ].join(" "),
5742
5004
  PostTaskArgsSchema,