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