@hivelore/mcp 0.42.1 → 0.43.2

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.d.ts CHANGED
@@ -192,6 +192,8 @@ declare const GetBriefingZod: z.ZodObject<{
192
192
  semantic: z.ZodDefault<z.ZodBoolean>;
193
193
  include_stale: z.ZodDefault<z.ZodBoolean>;
194
194
  track: z.ZodDefault<z.ZodBoolean>;
195
+ memory_scopes: z.ZodOptional<z.ZodArray<z.ZodEnum<["personal", "team", "module", "shared"]>, "many">>;
196
+ deterministic: z.ZodOptional<z.ZodBoolean>;
195
197
  format: z.ZodDefault<z.ZodEnum<["full", "compact", "actions"]>>;
196
198
  symbols: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
197
199
  min_semantic_score: z.ZodDefault<z.ZodNumber>;
@@ -210,6 +212,8 @@ declare const GetBriefingZod: z.ZodObject<{
210
212
  min_semantic_score: number;
211
213
  task?: string | undefined;
212
214
  dedupe_project_context?: boolean | undefined;
215
+ memory_scopes?: ("personal" | "team" | "module" | "shared")[] | undefined;
216
+ deterministic?: boolean | undefined;
213
217
  budget_preset?: "quick" | "balanced" | "deep" | undefined;
214
218
  }, {
215
219
  symbols?: string[] | undefined;
@@ -224,6 +228,8 @@ declare const GetBriefingZod: z.ZodObject<{
224
228
  semantic?: boolean | undefined;
225
229
  include_stale?: boolean | undefined;
226
230
  track?: boolean | undefined;
231
+ memory_scopes?: ("personal" | "team" | "module" | "shared")[] | undefined;
232
+ deterministic?: boolean | undefined;
227
233
  min_semantic_score?: number | undefined;
228
234
  budget_preset?: "quick" | "balanced" | "deep" | undefined;
229
235
  }>;
@@ -509,6 +515,8 @@ declare const ProposeSensorInputSchema: {
509
515
  memory_id: z.ZodString;
510
516
  kind: z.ZodDefault<z.ZodEnum<["regex", "ast", "shell", "test"]>>;
511
517
  pattern: z.ZodOptional<z.ZodString>;
518
+ rule: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
519
+ language: z.ZodOptional<z.ZodString>;
512
520
  command: z.ZodOptional<z.ZodString>;
513
521
  timeout_ms: z.ZodOptional<z.ZodNumber>;
514
522
  absent: z.ZodOptional<z.ZodString>;
@@ -561,22 +569,25 @@ interface AstScanResult {
561
569
  matches: AstMatch[];
562
570
  detail?: string;
563
571
  }
572
+ type AstRule = Record<string, unknown>;
564
573
  declare function astEngineAvailable(): Promise<boolean>;
565
574
  /** Map a file extension to a built-in ast-grep language. Unknown → null (unsupported, warn only). */
566
- declare function astLangForPath(filePath: string): "TypeScript" | "Tsx" | "JavaScript" | null;
575
+ declare function astLangForPath(filePath: string, explicitLanguage?: string): string | null;
567
576
  /**
568
577
  * Run one AST pattern (with optional `absent` sub-pattern) over a file's FULL content.
569
578
  * A match is suppressed when `absent` matches INSIDE the matched node — the structural version of
570
579
  * the regex `absent` window: the required companion lives in the call's own arguments.
571
580
  */
572
- declare function runAstPattern(content: string, filePath: string, pattern: string, absent?: string): Promise<AstScanResult>;
581
+ declare function runAstPattern(content: string, filePath: string, pattern?: string, absent?: string, rule?: AstRule, language?: string): Promise<AstScanResult>;
573
582
  /**
574
583
  * Gate-side evaluation: matches on the full content, FIRES only when a match intersects the added
575
584
  * lines (introduction, not mere presence). `addedLines` empty/undefined = validation mode (any
576
585
  * match counts — used for silent-on-current / fires-on-bad checks).
577
586
  */
578
587
  declare function runAstSensorOnContent(input: {
579
- pattern: string;
588
+ pattern?: string;
589
+ rule?: AstRule;
590
+ language?: string;
580
591
  absent?: string;
581
592
  content: string;
582
593
  filePath: string;
@@ -593,8 +604,10 @@ declare const MemTriedInputSchema: {
593
604
  paths: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
594
605
  author: z.ZodOptional<z.ZodString>;
595
606
  sensor: z.ZodOptional<z.ZodObject<{
596
- kind: z.ZodDefault<z.ZodEnum<["regex", "shell", "test"]>>;
607
+ kind: z.ZodDefault<z.ZodEnum<["regex", "ast", "shell", "test"]>>;
597
608
  pattern: z.ZodOptional<z.ZodString>;
609
+ rule: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
610
+ language: z.ZodOptional<z.ZodString>;
598
611
  command: z.ZodOptional<z.ZodString>;
599
612
  timeout_ms: z.ZodOptional<z.ZodNumber>;
600
613
  absent: z.ZodOptional<z.ZodString>;
@@ -604,10 +617,12 @@ declare const MemTriedInputSchema: {
604
617
  red_ref: z.ZodOptional<z.ZodString>;
605
618
  bad_example: z.ZodOptional<z.ZodString>;
606
619
  }, "strip", z.ZodTypeAny, {
607
- kind: "regex" | "shell" | "test";
620
+ kind: "regex" | "ast" | "shell" | "test";
608
621
  severity: "warn" | "block";
609
622
  pattern?: string | undefined;
610
623
  message?: string | undefined;
624
+ rule?: Record<string, unknown> | undefined;
625
+ language?: string | undefined;
611
626
  command?: string | undefined;
612
627
  timeout_ms?: number | undefined;
613
628
  absent?: string | undefined;
@@ -617,7 +632,9 @@ declare const MemTriedInputSchema: {
617
632
  }, {
618
633
  pattern?: string | undefined;
619
634
  message?: string | undefined;
620
- kind?: "regex" | "shell" | "test" | undefined;
635
+ kind?: "regex" | "ast" | "shell" | "test" | undefined;
636
+ rule?: Record<string, unknown> | undefined;
637
+ language?: string | undefined;
621
638
  command?: string | undefined;
622
639
  timeout_ms?: number | undefined;
623
640
  absent?: string | undefined;
package/dist/server.js CHANGED
@@ -1132,7 +1132,7 @@ import {
1132
1132
  import { z as z16 } from "zod";
1133
1133
 
1134
1134
  // src/tools/propose-sensor.ts
1135
- import { execSync } from "child_process";
1135
+ import { execFileSync } from "child_process";
1136
1136
  import { readFile as readFile3, writeFile as writeFile8 } from "fs/promises";
1137
1137
  import { existsSync as existsSync15, rmSync, symlinkSync } from "fs";
1138
1138
  import os from "os";
@@ -1152,10 +1152,32 @@ import { z as z15 } from "zod";
1152
1152
  // src/ast-sensors.ts
1153
1153
  import path5 from "path";
1154
1154
  var cachedEngine;
1155
+ var registeredDynamicLanguages = /* @__PURE__ */ new Set();
1156
+ async function loadDynamicLanguages(engine) {
1157
+ const registrations = {};
1158
+ const candidates = [
1159
+ ["python", () => import("@ast-grep/lang-python")],
1160
+ ["go", () => import("@ast-grep/lang-go")],
1161
+ ["rust", () => import("@ast-grep/lang-rust")],
1162
+ ["java", () => import("@ast-grep/lang-java")]
1163
+ ];
1164
+ for (const [name, load] of candidates) {
1165
+ try {
1166
+ const mod = await load();
1167
+ registrations[name] = mod.default;
1168
+ registeredDynamicLanguages.add(name);
1169
+ } catch {
1170
+ }
1171
+ }
1172
+ if (Object.keys(registrations).length > 0) {
1173
+ engine.registerDynamicLanguage(registrations);
1174
+ }
1175
+ }
1155
1176
  async function loadAstEngine() {
1156
1177
  if (cachedEngine !== void 0) return cachedEngine;
1157
1178
  try {
1158
1179
  cachedEngine = await import("@ast-grep/napi");
1180
+ await loadDynamicLanguages(cachedEngine);
1159
1181
  } catch {
1160
1182
  cachedEngine = null;
1161
1183
  }
@@ -1164,11 +1186,22 @@ async function loadAstEngine() {
1164
1186
  async function astEngineAvailable() {
1165
1187
  return await loadAstEngine() !== null;
1166
1188
  }
1167
- function astLangForPath(filePath) {
1189
+ function astLangForPath(filePath, explicitLanguage) {
1190
+ if (explicitLanguage) {
1191
+ const normalized = explicitLanguage.toLowerCase();
1192
+ if (["typescript", "tsx", "javascript", "html", "css"].includes(normalized)) {
1193
+ return normalized === "typescript" ? "TypeScript" : normalized === "tsx" ? "Tsx" : normalized === "javascript" ? "JavaScript" : normalized[0].toUpperCase() + normalized.slice(1);
1194
+ }
1195
+ return registeredDynamicLanguages.has(normalized) ? normalized : null;
1196
+ }
1168
1197
  const ext = path5.extname(filePath).toLowerCase();
1169
1198
  if (ext === ".ts" || ext === ".mts" || ext === ".cts") return "TypeScript";
1170
1199
  if (ext === ".tsx") return "Tsx";
1171
1200
  if (ext === ".js" || ext === ".jsx" || ext === ".mjs" || ext === ".cjs") return "JavaScript";
1201
+ if (ext === ".py" && registeredDynamicLanguages.has("python")) return "python";
1202
+ if (ext === ".go" && registeredDynamicLanguages.has("go")) return "go";
1203
+ if (ext === ".rs" && registeredDynamicLanguages.has("rust")) return "rust";
1204
+ if (ext === ".java" && registeredDynamicLanguages.has("java")) return "java";
1172
1205
  return null;
1173
1206
  }
1174
1207
  function absentPresentInNode(node, absent) {
@@ -1183,12 +1216,12 @@ function absentPresentInNode(node, absent) {
1183
1216
  return text.includes(absent);
1184
1217
  }
1185
1218
  }
1186
- async function runAstPattern(content, filePath, pattern, absent) {
1219
+ async function runAstPattern(content, filePath, pattern, absent, rule, language) {
1187
1220
  const engine = await loadAstEngine();
1188
1221
  if (!engine) return { status: "engine-missing", matches: [] };
1189
- const langName = astLangForPath(filePath);
1222
+ const langName = astLangForPath(filePath, language);
1190
1223
  if (!langName) return { status: "unsupported-language", matches: [] };
1191
- const lang = engine.Lang[langName];
1224
+ const lang = langName in engine.Lang ? engine.Lang[langName] : langName;
1192
1225
  let root;
1193
1226
  try {
1194
1227
  root = engine.parse(lang, content).root();
@@ -1197,7 +1230,9 @@ async function runAstPattern(content, filePath, pattern, absent) {
1197
1230
  }
1198
1231
  let nodes;
1199
1232
  try {
1200
- nodes = root.findAll(pattern);
1233
+ const matcher = rule ? { rule: pattern ? { all: [{ pattern }, rule] } : rule } : pattern;
1234
+ if (!matcher) return { status: "invalid-pattern", matches: [], detail: "missing pattern/rule" };
1235
+ nodes = root.findAll(matcher);
1201
1236
  } catch (err) {
1202
1237
  return { status: "invalid-pattern", matches: [], detail: String(err).slice(0, 200) };
1203
1238
  }
@@ -1214,7 +1249,7 @@ async function runAstPattern(content, filePath, pattern, absent) {
1214
1249
  return { status: "ok", matches };
1215
1250
  }
1216
1251
  async function runAstSensorOnContent(input) {
1217
- const scan = await runAstPattern(input.content, input.filePath, input.pattern, input.absent);
1252
+ const scan = await runAstPattern(input.content, input.filePath, input.pattern, input.absent, input.rule, input.language);
1218
1253
  if (scan.status !== "ok" || !input.addedLines || input.addedLines.size === 0) return scan;
1219
1254
  const added = input.addedLines;
1220
1255
  return {
@@ -1234,7 +1269,9 @@ var ProposeSensorInputSchema = {
1234
1269
  kind: z15.enum(["regex", "ast", "shell", "test"]).default("regex").describe(
1235
1270
  "regex = pattern matched on added diff lines (default). ast = an ast-grep STRUCTURAL pattern (e.g. 'stripe.paymentIntents.create($$$)') matched on the AST of changed files \u2014 comments and strings can never false-positive; `absent` is a sub-pattern that must be missing INSIDE the match (requires the optional @ast-grep/napi engine). 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."
1236
1271
  ),
1237
- pattern: z15.string().optional().describe("kind=regex: regex matching the FAULTY usage (the risky call/token), e.g. 'stripe\\.paymentIntents\\.create'."),
1272
+ pattern: z15.string().optional().describe("kind=regex: regex matching the faulty usage; kind=ast: optional structural pattern (may be combined with `rule`)."),
1273
+ rule: z15.record(z15.unknown()).optional().describe("kind=ast: full ast-grep Rule object (kind/inside/has/not/all/any/etc.). May be used alone or combined with pattern."),
1274
+ language: z15.string().optional().describe("kind=ast: explicit built-in/dynamic language name for non-standard file extensions."),
1238
1275
  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."),
1239
1276
  timeout_ms: z15.number().int().positive().optional().describe("kind=shell|test: max runtime before the executor kills the command (default 120000)."),
1240
1277
  absent: z15.string().optional().describe(
@@ -1265,7 +1302,7 @@ async function readPresumedCorrectTargets(root, relPaths) {
1265
1302
  const targets = [];
1266
1303
  for (const rel of relPaths) {
1267
1304
  try {
1268
- const content = execSync(`git show ${JSON.stringify(`HEAD:./${rel}`)}`, {
1305
+ const content = execFileSync("git", ["show", `HEAD:./${rel}`], {
1269
1306
  cwd: root,
1270
1307
  encoding: "utf8",
1271
1308
  maxBuffer: 10 * 1024 * 1024,
@@ -1286,7 +1323,7 @@ async function readPresumedCorrectTargets(root, relPaths) {
1286
1323
  }
1287
1324
  function runCommandForValidation(command, root, timeoutMs = 12e4) {
1288
1325
  try {
1289
- execSync(`bash -c ${JSON.stringify(command)}`, {
1326
+ execFileSync("bash", ["-c", command], {
1290
1327
  cwd: root,
1291
1328
  timeout: timeoutMs,
1292
1329
  maxBuffer: 8 * 1024 * 1024,
@@ -1311,7 +1348,7 @@ function proveRedOnIncident(command, root, redRef, timeoutMs) {
1311
1348
  let added = false;
1312
1349
  try {
1313
1350
  try {
1314
- execSync(`git worktree add --detach ${JSON.stringify(worktree)} ${JSON.stringify(redRef)}`, {
1351
+ execFileSync("git", ["worktree", "add", "--detach", worktree, redRef], {
1315
1352
  cwd: root,
1316
1353
  stdio: ["ignore", "pipe", "pipe"],
1317
1354
  timeout: 6e4
@@ -1338,7 +1375,7 @@ function proveRedOnIncident(command, root, redRef, timeoutMs) {
1338
1375
  } finally {
1339
1376
  if (added) {
1340
1377
  try {
1341
- execSync(`git worktree remove --force ${JSON.stringify(worktree)}`, { cwd: root, stdio: "ignore", timeout: 6e4 });
1378
+ execFileSync("git", ["worktree", "remove", "--force", worktree], { cwd: root, stdio: "ignore", timeout: 6e4 });
1342
1379
  } catch {
1343
1380
  try {
1344
1381
  rmSync(worktree, { recursive: true, force: true });
@@ -1378,13 +1415,13 @@ async function proposeSensor(input, ctx) {
1378
1415
  };
1379
1416
  }
1380
1417
  } else if (kind === "ast") {
1381
- if (!input.pattern?.trim()) {
1418
+ if (!input.pattern?.trim() && !input.rule) {
1382
1419
  return {
1383
1420
  accepted: false,
1384
1421
  memory_id: input.memory_id,
1385
1422
  severity: input.severity,
1386
1423
  reason: "invalid-pattern",
1387
- guidance: "kind=ast requires a `pattern` (an ast-grep structural pattern).",
1424
+ guidance: "kind=ast requires a structural `pattern` or a full ast-grep `rule` object.",
1388
1425
  self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1389
1426
  };
1390
1427
  }
@@ -1405,7 +1442,7 @@ async function proposeSensor(input, ctx) {
1405
1442
  }
1406
1443
  const personalScopeNudge = found.memory.frontmatter.scope === "personal" ? ` Note: this lesson is personal-scoped, so the sensor guards only YOUR machine (personal memories are gitignored). Promote it so the gate travels with the repo: hivelore memory promote ${input.memory_id}.` : "";
1407
1444
  if (kind === "ast") {
1408
- const pattern = input.pattern.trim();
1445
+ const pattern = input.pattern?.trim();
1409
1446
  if (!await astEngineAvailable() && input.severity === "block") {
1410
1447
  return {
1411
1448
  accepted: false,
@@ -1416,7 +1453,7 @@ async function proposeSensor(input, ctx) {
1416
1453
  self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1417
1454
  };
1418
1455
  }
1419
- const brittleAst = sensorPatternBrittleness(pattern);
1456
+ const brittleAst = pattern ? sensorPatternBrittleness(pattern) : null;
1420
1457
  if (brittleAst && input.severity === "block") {
1421
1458
  return {
1422
1459
  accepted: false,
@@ -1431,7 +1468,7 @@ async function proposeSensor(input, ctx) {
1431
1468
  const currentTargetsAst = await readPresumedCorrectTargets(ctx.paths.root, anchorPathsAst);
1432
1469
  const firedOnAst = [];
1433
1470
  for (const target of currentTargetsAst) {
1434
- const scan = await runAstSensorOnContent({ pattern, absent: input.absent, content: target.content, filePath: target.path });
1471
+ const scan = await runAstSensorOnContent({ pattern, rule: input.rule, language: input.language, absent: input.absent, content: target.content, filePath: target.path });
1435
1472
  if (scan.status === "invalid-pattern") {
1436
1473
  return {
1437
1474
  accepted: false,
@@ -1460,10 +1497,10 @@ async function proposeSensor(input, ctx) {
1460
1497
  ];
1461
1498
  let firesOnBadAst = null;
1462
1499
  if (badExamplesAst.length > 0 && await astEngineAvailable()) {
1463
- const exampleLang = anchorPathsAst.find((p) => astLangForPath(p) !== null) ?? "example.tsx";
1500
+ const exampleLang = anchorPathsAst.find((p) => astLangForPath(p, input.language) !== null) ?? "example.tsx";
1464
1501
  firesOnBadAst = false;
1465
1502
  for (const example of badExamplesAst) {
1466
- const scan = await runAstSensorOnContent({ pattern, absent: input.absent, content: example, filePath: exampleLang });
1503
+ const scan = await runAstSensorOnContent({ pattern, rule: input.rule, language: input.language, absent: input.absent, content: example, filePath: exampleLang });
1467
1504
  if (scan.status === "ok" && scan.matches.length > 0) {
1468
1505
  firesOnBadAst = true;
1469
1506
  break;
@@ -1482,10 +1519,12 @@ async function proposeSensor(input, ctx) {
1482
1519
  }
1483
1520
  const sensorAst = {
1484
1521
  kind: "ast",
1485
- pattern,
1522
+ ...pattern ? { pattern } : {},
1523
+ ...input.rule ? { rule: input.rule } : {},
1524
+ ...input.language ? { language: input.language } : {},
1486
1525
  ...input.absent ? { absent: input.absent } : {},
1487
1526
  paths: anchorPathsAst,
1488
- message: input.message?.trim() || deriveMessage(found.memory.body, pattern, input.absent),
1527
+ message: input.message?.trim() || deriveMessage(found.memory.body, pattern ?? "AST rule", input.absent),
1489
1528
  ...input.incident?.trim() ? { incident: input.incident.trim() } : {},
1490
1529
  severity: input.severity,
1491
1530
  autogen: false,
@@ -1548,6 +1587,16 @@ ${verdictCmd.detail}`,
1548
1587
  self_check: { silent_on_current: false, fires_on_bad: null, fired_on: anchorPathsCmd }
1549
1588
  };
1550
1589
  }
1590
+ if (input.severity === "block" && !input.red_ref?.trim()) {
1591
+ return {
1592
+ accepted: false,
1593
+ memory_id: input.memory_id,
1594
+ severity: input.severity,
1595
+ reason: "red-required",
1596
+ guidance: "A blocking shell/test sensor requires `red_ref`: the oracle must PASS on the current tree and FAIL on the pre-fix incident state. Pass the incident commit/ref, or propose the sensor at warn severity until RED can be proven.",
1597
+ self_check: { silent_on_current: true, fires_on_bad: null, fired_on: [] }
1598
+ };
1599
+ }
1551
1600
  const sensorCmd = {
1552
1601
  kind,
1553
1602
  command: input.command.trim(),
@@ -1569,7 +1618,7 @@ ${verdictCmd.detail}`,
1569
1618
  accepted: true,
1570
1619
  memory_id: input.memory_id,
1571
1620
  severity: input.severity,
1572
- 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}).`) + (redProven ? " RED proven: the oracle demonstrably FAILS on the incident state (red_ref) \u2014 recorded as red_proven." : " Tip: pass red_ref (the pre-fix commit) to PROVE the oracle catches the incident, not merely that it passes today.") + (pendingTests.length > 0 ? ` Note: the routed test is still a PENDING stub (${pendingTests.join(", ")}) \u2014 it passes on anything; write the assertion to make this oracle real.` : "") + personalScopeNudge,
1621
+ 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}).`) + (redProven ? " RED proven: the oracle demonstrably FAILS on the incident state (red_ref) \u2014 recorded as red_proven." : " This warn-only oracle is not RED-proven; pass red_ref before promoting it to block.") + (pendingTests.length > 0 ? ` Note: the routed test is still a PENDING stub (${pendingTests.join(", ")}) \u2014 it passes on anything; write the assertion to make this oracle real.` : "") + personalScopeNudge,
1573
1622
  self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: redProven ? true : null, fired_on: [] },
1574
1623
  file_path: found.filePath
1575
1624
  };
@@ -1638,8 +1687,10 @@ var MemTriedInputSchema = {
1638
1687
  paths: z16.array(z16.string()).default([]).describe("Anchor file paths this applies to"),
1639
1688
  author: z16.string().optional().describe("Author handle or email"),
1640
1689
  sensor: z16.object({
1641
- kind: z16.enum(["regex", "shell", "test"]).default("regex").describe("regex pattern, or a shell/test COMMAND the gate executes (behaviour bridge)"),
1642
- pattern: z16.string().optional().describe("kind=regex: regex matching the FAULTY usage (added diff lines)"),
1690
+ kind: z16.enum(["regex", "ast", "shell", "test"]).default("regex").describe("regex/AST pattern, or a shell/test command"),
1691
+ pattern: z16.string().optional().describe("kind=regex|ast: pattern matching the faulty usage"),
1692
+ rule: z16.record(z16.unknown()).optional().describe("kind=ast: full ast-grep Rule object"),
1693
+ language: z16.string().optional().describe("kind=ast: explicit built-in/dynamic language"),
1643
1694
  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)"),
1644
1695
  timeout_ms: z16.number().int().positive().optional().describe("kind=shell|test: max runtime (default 120000)"),
1645
1696
  absent: z16.string().optional().describe("kind=regex: regex marking CORRECT usage nearby \u2014 excludes it from firing"),
@@ -1689,6 +1740,8 @@ async function memTried(input, ctx) {
1689
1740
  memory_id: frontmatter.id,
1690
1741
  kind: input.sensor.kind ?? "regex",
1691
1742
  pattern: input.sensor.pattern,
1743
+ rule: input.sensor.rule,
1744
+ language: input.sensor.language,
1692
1745
  command: input.sensor.command,
1693
1746
  timeout_ms: input.sensor.timeout_ms,
1694
1747
  absent: input.sensor.absent,
@@ -1991,7 +2044,7 @@ import {
1991
2044
  import { mkdir as mkdir6, writeFile as writeFile12, rm } from "fs/promises";
1992
2045
  import { existsSync as existsSync19 } from "fs";
1993
2046
  import path10 from "path";
1994
- import { execSync as execSync2 } from "child_process";
2047
+ import { execSync } from "child_process";
1995
2048
  function pendingDistillPath(ctx) {
1996
2049
  return path10.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
1997
2050
  }
@@ -2034,7 +2087,7 @@ var SessionTracker = class {
2034
2087
  }
2035
2088
  let gitDiff;
2036
2089
  try {
2037
- const raw = execSync2("git diff HEAD", {
2090
+ const raw = execSync("git diff HEAD", {
2038
2091
  cwd: this.ctx.paths.root,
2039
2092
  timeout: 5e3,
2040
2093
  encoding: "utf8",
@@ -2066,7 +2119,7 @@ var SessionTracker = class {
2066
2119
  const triedThreads = this.events.filter((e) => e.tool === "mem_tried").map((e) => e.summary ?? "").filter(Boolean);
2067
2120
  let diffStat;
2068
2121
  try {
2069
- diffStat = execSync2("git diff --stat HEAD", {
2122
+ diffStat = execSync("git diff --stat HEAD", {
2070
2123
  cwd: this.ctx.paths.root,
2071
2124
  timeout: 5e3,
2072
2125
  encoding: "utf8",
@@ -2478,6 +2531,8 @@ var GetBriefingInputSchema = {
2478
2531
  ),
2479
2532
  include_stale: z20.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
2480
2533
  track: z20.boolean().default(true).describe("Increment read_count on returned memories"),
2534
+ memory_scopes: z20.array(z20.enum(["personal", "team", "module", "shared"])).optional().describe("Restrict the candidate corpus to selected scopes. Omit to include every scope."),
2535
+ deterministic: z20.boolean().optional().describe("Ignore machine-local usage/impact signals so repeated evaluations rank the shared corpus reproducibly."),
2481
2536
  format: z20.enum(["full", "compact", "actions"]).default("full").describe(
2482
2537
  "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."
2483
2538
  ),
@@ -2506,9 +2561,11 @@ async function getBriefing(input, ctx) {
2506
2561
  let searchMode = "literal";
2507
2562
  let usage = { version: 1, updated_at: "", by_id: {} };
2508
2563
  let byId = /* @__PURE__ */ new Map();
2564
+ const allowedScopes = input.memory_scopes ? new Set(input.memory_scopes) : null;
2565
+ const scopeAllowed = (loaded) => allowedScopes === null || allowedScopes.has(loaded.memory.frontmatter.scope);
2509
2566
  let lastSession;
2510
2567
  if (existsSync22(ctx.paths.memoriesDir)) {
2511
- const allLoaded = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
2568
+ const allLoaded = (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed);
2512
2569
  const recaps = allLoaded.filter(({ memory }) => memory.frontmatter.type === "session_recap").sort(
2513
2570
  (a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
2514
2571
  );
@@ -2541,10 +2598,11 @@ async function getBriefing(input, ctx) {
2541
2598
  if (memoryHasExcludedTag(memory.frontmatter, excludeTags)) return false;
2542
2599
  return true;
2543
2600
  });
2544
- usage = await loadUsageIndex8(ctx.paths);
2601
+ usage = input.deterministic ? { version: 1, updated_at: "", by_id: {} } : await loadUsageIndex8(ctx.paths);
2545
2602
  byId = new Map(allMemories.map((m) => [m.memory.frontmatter.id, m]));
2546
- const semanticHits = input.task && input.semantic ? await trySemanticHits(ctx, input.task, allMemories.length * 2) : null;
2547
- if (input.task && input.semantic) {
2603
+ const semanticEnabled = input.semantic && input.deterministic !== true;
2604
+ const semanticHits = input.task && semanticEnabled ? await trySemanticHits(ctx, input.task, allMemories.length * 2) : null;
2605
+ if (input.task && semanticEnabled) {
2548
2606
  searchMode = semanticHits ? "semantic" : "literal_fallback";
2549
2607
  }
2550
2608
  const seen = /* @__PURE__ */ new Map();
@@ -2880,7 +2938,7 @@ ${m.content}`).join("\n\n---\n\n"),
2880
2938
  }
2881
2939
  }
2882
2940
  if (existsSync22(ctx.paths.memoriesDir)) {
2883
- const allMems = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
2941
+ const allMems = (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed);
2884
2942
  for (const { memory } of allMems) {
2885
2943
  const fm = memory.frontmatter;
2886
2944
  if (!fm.requires_human_approval) continue;
@@ -2937,7 +2995,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
2937
2995
  pcRaw = await readFile7(ctx.paths.projectContext, "utf8");
2938
2996
  } catch {
2939
2997
  }
2940
- const allForBootstrap = existsSync22(ctx.paths.memoriesDir) ? await loadMemoriesFromDir17(ctx.paths.memoriesDir) : [];
2998
+ const allForBootstrap = existsSync22(ctx.paths.memoriesDir) ? (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed) : [];
2941
2999
  const cmForBootstrap = await loadCodeMap(ctx.paths);
2942
3000
  let existingModules = [];
2943
3001
  try {
@@ -2948,7 +3006,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
2948
3006
  const bootstrap = assessBootstrapState({
2949
3007
  projectContextRaw: pcRaw,
2950
3008
  memories: allForBootstrap,
2951
- codeFiles: cmForBootstrap ? Object.keys(cmForBootstrap.files) : [],
3009
+ codeFiles: cmForBootstrap ? Object.keys(cmForBootstrap.files).filter((file) => existsSync22(path13.join(ctx.paths.root, file))) : [],
2952
3010
  existingModules
2953
3011
  });
2954
3012
  if (bootstrap.state !== "ready" && bootstrap.metrics.mainAreas > 0) {
@@ -3139,7 +3197,7 @@ function oneLine(value) {
3139
3197
  return value.replace(/\s+/g, " ").replace(/"/g, '\\"').trim().slice(0, 120);
3140
3198
  }
3141
3199
  function serverVersion() {
3142
- return true ? "0.42.1" : "dev";
3200
+ return true ? "0.43.2" : "dev";
3143
3201
  }
3144
3202
 
3145
3203
  // src/tools/code-map.ts
@@ -4312,6 +4370,7 @@ ${template}\`\`\`
4312
4370
  // src/prompts/bootstrap-repo.ts
4313
4371
  import { readFile as readFile8, readdir as readdir5 } from "fs/promises";
4314
4372
  import { existsSync as existsSync29 } from "fs";
4373
+ import path14 from "path";
4315
4374
  import {
4316
4375
  assessBootstrapState as assessBootstrapState2,
4317
4376
  loadCodeMap as loadCodeMap4,
@@ -4339,7 +4398,7 @@ async function currentAssessment(ctx) {
4339
4398
  return assessBootstrapState2({
4340
4399
  projectContextRaw,
4341
4400
  memories,
4342
- codeFiles: codeMap ? Object.keys(codeMap.files) : [],
4401
+ codeFiles: codeMap ? Object.keys(codeMap.files).filter((file) => existsSync29(path14.join(ctx.paths.root, file))) : [],
4343
4402
  existingModules
4344
4403
  });
4345
4404
  }
@@ -4567,7 +4626,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
4567
4626
  // src/server.ts
4568
4627
  import { hasRecentBriefingMarker, loadConfigSync } from "@hivelore/core";
4569
4628
  var SERVER_NAME = "hivelore";
4570
- var SERVER_VERSION = "0.42.1";
4629
+ var SERVER_VERSION = "0.43.2";
4571
4630
  function jsonResult(data) {
4572
4631
  return {
4573
4632
  content: [