@hivelore/mcp 0.39.2 → 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.js CHANGED
@@ -1122,7 +1122,7 @@ async function memApprove(input, ctx) {
1122
1122
  // src/tools/mem-tried.ts
1123
1123
  import { mkdir as mkdir3, writeFile as writeFile9 } from "fs/promises";
1124
1124
  import { existsSync as existsSync16 } from "fs";
1125
- import path6 from "path";
1125
+ import path7 from "path";
1126
1126
  import {
1127
1127
  buildFrontmatter as buildFrontmatter2,
1128
1128
  memoryFilePath as memoryFilePath2,
@@ -1132,25 +1132,146 @@ 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
- import { existsSync as existsSync15 } from "fs";
1138
- import path5 from "path";
1137
+ import { existsSync as existsSync15, rmSync, symlinkSync } from "fs";
1138
+ import os from "os";
1139
+ import path6 from "path";
1139
1140
  import {
1140
1141
  extractSensorExamples,
1141
1142
  extractTestFilePathsFromCommand,
1142
1143
  hasPendingTestMarker,
1143
1144
  judgeProposedSensor,
1144
1145
  loadMemoriesFromDir as loadMemoriesFromDir13,
1146
+ scrubbedCommandEnv,
1147
+ sensorPatternBrittleness,
1145
1148
  serializeMemory as serializeMemory7
1146
1149
  } from "@hivelore/core";
1147
1150
  import { z as z15 } from "zod";
1151
+
1152
+ // src/ast-sensors.ts
1153
+ import path5 from "path";
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
+ }
1176
+ async function loadAstEngine() {
1177
+ if (cachedEngine !== void 0) return cachedEngine;
1178
+ try {
1179
+ cachedEngine = await import("@ast-grep/napi");
1180
+ await loadDynamicLanguages(cachedEngine);
1181
+ } catch {
1182
+ cachedEngine = null;
1183
+ }
1184
+ return cachedEngine;
1185
+ }
1186
+ async function astEngineAvailable() {
1187
+ return await loadAstEngine() !== null;
1188
+ }
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
+ }
1197
+ const ext = path5.extname(filePath).toLowerCase();
1198
+ if (ext === ".ts" || ext === ".mts" || ext === ".cts") return "TypeScript";
1199
+ if (ext === ".tsx") return "Tsx";
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";
1205
+ return null;
1206
+ }
1207
+ function absentPresentInNode(node, absent) {
1208
+ try {
1209
+ if (node.find(absent)) return true;
1210
+ } catch {
1211
+ }
1212
+ const text = node.text();
1213
+ try {
1214
+ return new RegExp(absent).test(text);
1215
+ } catch {
1216
+ return text.includes(absent);
1217
+ }
1218
+ }
1219
+ async function runAstPattern(content, filePath, pattern, absent, rule, language) {
1220
+ const engine = await loadAstEngine();
1221
+ if (!engine) return { status: "engine-missing", matches: [] };
1222
+ const langName = astLangForPath(filePath, language);
1223
+ if (!langName) return { status: "unsupported-language", matches: [] };
1224
+ const lang = langName in engine.Lang ? engine.Lang[langName] : langName;
1225
+ let root;
1226
+ try {
1227
+ root = engine.parse(lang, content).root();
1228
+ } catch (err) {
1229
+ return { status: "parse-error", matches: [], detail: String(err).slice(0, 200) };
1230
+ }
1231
+ let nodes;
1232
+ try {
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);
1236
+ } catch (err) {
1237
+ return { status: "invalid-pattern", matches: [], detail: String(err).slice(0, 200) };
1238
+ }
1239
+ const matches = [];
1240
+ for (const node of nodes) {
1241
+ if (absent && absentPresentInNode(node, absent)) continue;
1242
+ const range = node.range();
1243
+ matches.push({
1244
+ startLine: range.start.line + 1,
1245
+ endLine: range.end.line + 1,
1246
+ text: node.text().trim().slice(0, 200)
1247
+ });
1248
+ }
1249
+ return { status: "ok", matches };
1250
+ }
1251
+ async function runAstSensorOnContent(input) {
1252
+ const scan = await runAstPattern(input.content, input.filePath, input.pattern, input.absent, input.rule, input.language);
1253
+ if (scan.status !== "ok" || !input.addedLines || input.addedLines.size === 0) return scan;
1254
+ const added = input.addedLines;
1255
+ return {
1256
+ status: "ok",
1257
+ matches: scan.matches.filter((m) => {
1258
+ for (let line = m.startLine; line <= m.endLine; line++) {
1259
+ if (added.has(line)) return true;
1260
+ }
1261
+ return false;
1262
+ })
1263
+ };
1264
+ }
1265
+
1266
+ // src/tools/propose-sensor.ts
1148
1267
  var ProposeSensorInputSchema = {
1149
1268
  memory_id: z15.string().min(1).describe("Id of the gotcha/attempt memory this sensor protects."),
1150
- kind: z15.enum(["regex", "shell", "test"]).default("regex").describe(
1151
- "regex = pattern matched on added diff lines (default). shell|test = a COMMAND the gate runs when the diff touches the sensor's paths \u2014 routes the team's own oracle (an existing test, an invariant script) to this lesson. Command sensors only execute where enforcement.runCommandSensors=true."
1269
+ kind: z15.enum(["regex", "ast", "shell", "test"]).default("regex").describe(
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."
1152
1271
  ),
1153
- 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."),
1154
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."),
1155
1276
  timeout_ms: z15.number().int().positive().optional().describe("kind=shell|test: max runtime before the executor kills the command (default 120000)."),
1156
1277
  absent: z15.string().optional().describe(
@@ -1162,6 +1283,9 @@ var ProposeSensorInputSchema = {
1162
1283
  incident: z15.string().optional().describe(
1163
1284
  "Provenance: the real incident this sensor guards \u2014 a ticket/prod ref ('prod #442', 'INC-1029', '2026-06 refund overcharge'). Turns 'a test failed' into 'this reproduces the incident the test exists to prevent'. Surfaced in the block message and the prevention receipt. Strongly recommended for command/test sensors routed from a post-incident test."
1164
1285
  ),
1286
+ red_ref: z15.string().optional().describe(
1287
+ "kind=shell|test: prove the oracle actually catches the incident. A git ref (commit/branch) of the PRE-FIX state; validation replays it in a scratch worktree and requires the command to FAIL there (RED) in addition to passing on the current tree (GREEN). On success the sensor records red_proven: true \u2014 'the test demonstrably catches the incident', shown in the prevention receipt."
1288
+ ),
1165
1289
  flags: z15.string().optional().describe("Optional regex flags (e.g. 'i' for case-insensitive)."),
1166
1290
  paths: z15.array(z15.string()).default([]).describe("Override scope paths. Defaults to the memory's anchor paths.")
1167
1291
  };
@@ -1178,7 +1302,7 @@ async function readPresumedCorrectTargets(root, relPaths) {
1178
1302
  const targets = [];
1179
1303
  for (const rel of relPaths) {
1180
1304
  try {
1181
- const content = execSync(`git show ${JSON.stringify(`HEAD:./${rel}`)}`, {
1305
+ const content = execFileSync("git", ["show", `HEAD:./${rel}`], {
1182
1306
  cwd: root,
1183
1307
  encoding: "utf8",
1184
1308
  maxBuffer: 10 * 1024 * 1024,
@@ -1188,7 +1312,7 @@ async function readPresumedCorrectTargets(root, relPaths) {
1188
1312
  continue;
1189
1313
  } catch {
1190
1314
  }
1191
- const abs = path5.resolve(root, rel);
1315
+ const abs = path6.resolve(root, rel);
1192
1316
  if (!existsSync15(abs)) continue;
1193
1317
  try {
1194
1318
  targets.push({ path: rel, content: await readFile3(abs, "utf8") });
@@ -1199,11 +1323,13 @@ async function readPresumedCorrectTargets(root, relPaths) {
1199
1323
  }
1200
1324
  function runCommandForValidation(command, root, timeoutMs = 12e4) {
1201
1325
  try {
1202
- execSync(`bash -c ${JSON.stringify(command)}`, {
1326
+ execFileSync("bash", ["-c", command], {
1203
1327
  cwd: root,
1204
1328
  timeout: timeoutMs,
1205
1329
  maxBuffer: 8 * 1024 * 1024,
1206
- stdio: ["ignore", "pipe", "pipe"]
1330
+ stdio: ["ignore", "pipe", "pipe"],
1331
+ // Same containment as the gate executor: an oracle gets a test-runner env, not credentials.
1332
+ env: { ...scrubbedCommandEnv(process.env), HIVELORE_SENSOR: "validation" }
1207
1333
  });
1208
1334
  return { status: "passed", detail: "exit 0" };
1209
1335
  } catch (err) {
@@ -1217,6 +1343,48 @@ ${e.stderr?.toString() ?? ""}`.split("\n").filter(Boolean).slice(-8).join("\n");
1217
1343
  return { status: "failed", detail: out || `exit ${e.status ?? "?"}` };
1218
1344
  }
1219
1345
  }
1346
+ function proveRedOnIncident(command, root, redRef, timeoutMs) {
1347
+ const worktree = path6.join(os.tmpdir(), `hivelore-red-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
1348
+ let added = false;
1349
+ try {
1350
+ try {
1351
+ execFileSync("git", ["worktree", "add", "--detach", worktree, redRef], {
1352
+ cwd: root,
1353
+ stdio: ["ignore", "pipe", "pipe"],
1354
+ timeout: 6e4
1355
+ });
1356
+ added = true;
1357
+ } catch (err) {
1358
+ const e = err;
1359
+ return { proven: false, reason: "red-ref-invalid", detail: (e.stderr?.toString() ?? String(err)).slice(0, 300) };
1360
+ }
1361
+ const mainModules = path6.join(root, "node_modules");
1362
+ const wtModules = path6.join(worktree, "node_modules");
1363
+ if (existsSync15(mainModules) && !existsSync15(wtModules)) {
1364
+ try {
1365
+ symlinkSync(mainModules, wtModules, "dir");
1366
+ } catch {
1367
+ }
1368
+ }
1369
+ const run = runCommandForValidation(command, worktree, timeoutMs);
1370
+ if (run.status === "failed") return { proven: true, detail: run.detail };
1371
+ if (run.status === "passed") {
1372
+ return { proven: false, reason: "red-not-proven", detail: "oracle PASSED on the incident state \u2014 it does not catch the incident" };
1373
+ }
1374
+ return { proven: false, reason: "red-unrunnable", detail: run.detail };
1375
+ } finally {
1376
+ if (added) {
1377
+ try {
1378
+ execFileSync("git", ["worktree", "remove", "--force", worktree], { cwd: root, stdio: "ignore", timeout: 6e4 });
1379
+ } catch {
1380
+ try {
1381
+ rmSync(worktree, { recursive: true, force: true });
1382
+ } catch {
1383
+ }
1384
+ }
1385
+ }
1386
+ }
1387
+ }
1220
1388
  async function proposeSensor(input, ctx) {
1221
1389
  if (!existsSync15(ctx.paths.memoriesDir)) {
1222
1390
  throw new Error(`No .ai/memories at ${ctx.paths.root}. Run 'hivelore init' first.`);
@@ -1246,6 +1414,17 @@ async function proposeSensor(input, ctx) {
1246
1414
  self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1247
1415
  };
1248
1416
  }
1417
+ } else if (kind === "ast") {
1418
+ if (!input.pattern?.trim() && !input.rule) {
1419
+ return {
1420
+ accepted: false,
1421
+ memory_id: input.memory_id,
1422
+ severity: input.severity,
1423
+ reason: "invalid-pattern",
1424
+ guidance: "kind=ast requires a structural `pattern` or a full ast-grep `rule` object.",
1425
+ self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1426
+ };
1427
+ }
1249
1428
  } else if (!input.command?.trim()) {
1250
1429
  return {
1251
1430
  accepted: false,
@@ -1262,12 +1441,111 @@ async function proposeSensor(input, ctx) {
1262
1441
  throw new Error(`No memory found with id ${input.memory_id}`);
1263
1442
  }
1264
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}.` : "";
1265
- if (kind !== "regex") {
1266
- const referencedTests = extractTestFilePathsFromCommand(input.command.trim()).filter((rel) => existsSync15(path5.resolve(ctx.paths.root, rel)));
1444
+ if (kind === "ast") {
1445
+ const pattern = input.pattern?.trim();
1446
+ if (!await astEngineAvailable() && input.severity === "block") {
1447
+ return {
1448
+ accepted: false,
1449
+ memory_id: input.memory_id,
1450
+ severity: input.severity,
1451
+ reason: "ast-engine-missing",
1452
+ guidance: "The optional AST engine is not installed, so this proposal cannot be validated \u2014 a block sensor is only trusted after proof. Install it (`npm i -g @ast-grep/napi`, or add it to the repo) and re-propose.",
1453
+ self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1454
+ };
1455
+ }
1456
+ const brittleAst = pattern ? sensorPatternBrittleness(pattern) : null;
1457
+ if (brittleAst && input.severity === "block") {
1458
+ return {
1459
+ accepted: false,
1460
+ memory_id: input.memory_id,
1461
+ severity: input.severity,
1462
+ reason: "brittle",
1463
+ guidance: `The pattern is brittle (${brittleAst}). Use a durable structural pattern, then re-propose.`,
1464
+ self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1465
+ };
1466
+ }
1467
+ const anchorPathsAst = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
1468
+ const currentTargetsAst = await readPresumedCorrectTargets(ctx.paths.root, anchorPathsAst);
1469
+ const firedOnAst = [];
1470
+ for (const target of currentTargetsAst) {
1471
+ const scan = await runAstSensorOnContent({ pattern, rule: input.rule, language: input.language, absent: input.absent, content: target.content, filePath: target.path });
1472
+ if (scan.status === "invalid-pattern") {
1473
+ return {
1474
+ accepted: false,
1475
+ memory_id: input.memory_id,
1476
+ severity: input.severity,
1477
+ reason: "invalid-pattern",
1478
+ guidance: `The ast-grep pattern is invalid: ${scan.detail ?? "unparseable"}. Fix it and re-propose.`,
1479
+ self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1480
+ };
1481
+ }
1482
+ if (scan.status === "ok" && scan.matches.length > 0) firedOnAst.push(target.path);
1483
+ }
1484
+ if (firedOnAst.length > 0 && input.severity === "block") {
1485
+ return {
1486
+ accepted: false,
1487
+ memory_id: input.memory_id,
1488
+ severity: input.severity,
1489
+ reason: "fires-on-current",
1490
+ guidance: `The pattern matches the CURRENT (correct) code in ${firedOnAst.join(", ")}. Add/tighten the 'absent' companion sub-pattern so correct usage is excluded, then re-propose.`,
1491
+ self_check: { silent_on_current: false, fires_on_bad: null, fired_on: firedOnAst }
1492
+ };
1493
+ }
1494
+ const badExamplesAst = [
1495
+ ...input.bad_example ? [input.bad_example] : [],
1496
+ ...extractSensorExamples(found.memory.body)
1497
+ ];
1498
+ let firesOnBadAst = null;
1499
+ if (badExamplesAst.length > 0 && await astEngineAvailable()) {
1500
+ const exampleLang = anchorPathsAst.find((p) => astLangForPath(p, input.language) !== null) ?? "example.tsx";
1501
+ firesOnBadAst = false;
1502
+ for (const example of badExamplesAst) {
1503
+ const scan = await runAstSensorOnContent({ pattern, rule: input.rule, language: input.language, absent: input.absent, content: example, filePath: exampleLang });
1504
+ if (scan.status === "ok" && scan.matches.length > 0) {
1505
+ firesOnBadAst = true;
1506
+ break;
1507
+ }
1508
+ }
1509
+ }
1510
+ if (firesOnBadAst === false && input.severity === "block") {
1511
+ return {
1512
+ accepted: false,
1513
+ memory_id: input.memory_id,
1514
+ severity: input.severity,
1515
+ reason: "missed-bad-example",
1516
+ guidance: "The pattern did not match the bad example structurally, so it won't catch the mistake. Adjust it, then re-propose.",
1517
+ self_check: { silent_on_current: firedOnAst.length === 0, fires_on_bad: false, fired_on: [] }
1518
+ };
1519
+ }
1520
+ const sensorAst = {
1521
+ kind: "ast",
1522
+ ...pattern ? { pattern } : {},
1523
+ ...input.rule ? { rule: input.rule } : {},
1524
+ ...input.language ? { language: input.language } : {},
1525
+ ...input.absent ? { absent: input.absent } : {},
1526
+ paths: anchorPathsAst,
1527
+ message: input.message?.trim() || deriveMessage(found.memory.body, pattern ?? "AST rule", input.absent),
1528
+ ...input.incident?.trim() ? { incident: input.incident.trim() } : {},
1529
+ severity: input.severity,
1530
+ autogen: false,
1531
+ last_fired: null
1532
+ };
1533
+ await writeFile8(found.filePath, serializeMemory7({ frontmatter: { ...found.memory.frontmatter, sensor: sensorAst }, body: found.memory.body }), "utf8");
1534
+ return {
1535
+ accepted: true,
1536
+ memory_id: input.memory_id,
1537
+ severity: input.severity,
1538
+ guidance: "Structural sensor accepted \u2014 it matches the AST, so comments/strings can never false-positive." + (await astEngineAvailable() ? "" : " Note: the AST engine is not installed here; the sensor is UNRUNNABLE (warn-only) until @ast-grep/napi is available.") + personalScopeNudge,
1539
+ self_check: { silent_on_current: firedOnAst.length === 0, fires_on_bad: firesOnBadAst, fired_on: firedOnAst },
1540
+ file_path: found.filePath
1541
+ };
1542
+ }
1543
+ if (kind === "shell" || kind === "test") {
1544
+ const referencedTests = extractTestFilePathsFromCommand(input.command.trim()).filter((rel) => existsSync15(path6.resolve(ctx.paths.root, rel)));
1267
1545
  const pendingTests = [];
1268
1546
  for (const rel of referencedTests) {
1269
1547
  try {
1270
- if (hasPendingTestMarker(await readFile3(path5.resolve(ctx.paths.root, rel), "utf8"))) pendingTests.push(rel);
1548
+ if (hasPendingTestMarker(await readFile3(path6.resolve(ctx.paths.root, rel), "utf8"))) pendingTests.push(rel);
1271
1549
  } catch {
1272
1550
  }
1273
1551
  }
@@ -1283,6 +1561,21 @@ async function proposeSensor(input, ctx) {
1283
1561
  }
1284
1562
  const verdictCmd = runCommandForValidation(input.command.trim(), ctx.paths.root, input.timeout_ms);
1285
1563
  const anchorPathsCmd = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
1564
+ let redProven = false;
1565
+ if (input.red_ref?.trim()) {
1566
+ const red = proveRedOnIncident(input.command.trim(), ctx.paths.root, input.red_ref.trim(), input.timeout_ms);
1567
+ if (!red.proven && input.severity === "block") {
1568
+ return {
1569
+ accepted: false,
1570
+ memory_id: input.memory_id,
1571
+ severity: input.severity,
1572
+ reason: red.reason ?? "red-not-proven",
1573
+ guidance: red.reason === "red-ref-invalid" ? `red_ref could not be checked out (${red.detail}). Pass a valid commit/ref of the pre-fix state.` : red.reason === "red-unrunnable" ? `The oracle could not RUN on the incident state (${red.detail}) \u2014 it proves nothing there. Fix the command or drop red_ref.` : "The oracle PASSED on the incident state, so it does not catch the incident it claims to guard. Strengthen the assertion until it goes RED on red_ref, then re-propose. Output: " + red.detail.slice(0, 300),
1574
+ self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: false, fired_on: [] }
1575
+ };
1576
+ }
1577
+ redProven = red.proven;
1578
+ }
1286
1579
  if (verdictCmd.status !== "passed" && input.severity === "block") {
1287
1580
  return {
1288
1581
  accepted: false,
@@ -1294,6 +1587,16 @@ ${verdictCmd.detail}`,
1294
1587
  self_check: { silent_on_current: false, fires_on_bad: null, fired_on: anchorPathsCmd }
1295
1588
  };
1296
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
+ }
1297
1600
  const sensorCmd = {
1298
1601
  kind,
1299
1602
  command: input.command.trim(),
@@ -1301,6 +1604,7 @@ ${verdictCmd.detail}`,
1301
1604
  paths: anchorPathsCmd,
1302
1605
  message: input.message?.trim() || deriveMessage(found.memory.body, input.command.trim(), void 0),
1303
1606
  ...input.incident?.trim() ? { incident: input.incident.trim() } : {},
1607
+ ...redProven ? { red_proven: true } : {},
1304
1608
  severity: input.severity,
1305
1609
  autogen: false,
1306
1610
  last_fired: null
@@ -1314,8 +1618,9 @@ ${verdictCmd.detail}`,
1314
1618
  accepted: true,
1315
1619
  memory_id: input.memory_id,
1316
1620
  severity: input.severity,
1317
- 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}).`) + (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,
1318
- self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: null, fired_on: [] }
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,
1622
+ self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: redProven ? true : null, fired_on: [] },
1623
+ file_path: found.filePath
1319
1624
  };
1320
1625
  }
1321
1626
  const anchorPaths = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
@@ -1382,14 +1687,17 @@ var MemTriedInputSchema = {
1382
1687
  paths: z16.array(z16.string()).default([]).describe("Anchor file paths this applies to"),
1383
1688
  author: z16.string().optional().describe("Author handle or email"),
1384
1689
  sensor: z16.object({
1385
- kind: z16.enum(["regex", "shell", "test"]).default("regex").describe("regex pattern, or a shell/test COMMAND the gate executes (behaviour bridge)"),
1386
- 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"),
1387
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)"),
1388
1695
  timeout_ms: z16.number().int().positive().optional().describe("kind=shell|test: max runtime (default 120000)"),
1389
1696
  absent: z16.string().optional().describe("kind=regex: regex marking CORRECT usage nearby \u2014 excludes it from firing"),
1390
1697
  severity: z16.enum(["warn", "block"]).default("block").describe("block = deterministic gate refusal"),
1391
1698
  message: z16.string().optional().describe("Self-correction message shown when the sensor fires"),
1392
1699
  incident: z16.string().optional().describe("Provenance: the incident this sensor guards (e.g. 'prod #442') \u2014 surfaced when it fires and in the receipt"),
1700
+ red_ref: z16.string().optional().describe("kind=shell|test: pre-fix commit/ref \u2014 the oracle must FAIL on it (proves the test catches the incident; records red_proven)"),
1393
1701
  bad_example: z16.string().optional().describe("kind=regex: code snippet the sensor MUST fire on (validation)")
1394
1702
  }).optional().describe(
1395
1703
  "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."
@@ -1421,7 +1729,7 @@ async function memTried(input, ctx) {
1421
1729
  }
1422
1730
  const body = lines.join("\n") + "\n";
1423
1731
  const file = memoryFilePath2(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
1424
- await mkdir3(path6.dirname(file), { recursive: true });
1732
+ await mkdir3(path7.dirname(file), { recursive: true });
1425
1733
  if (existsSync16(file)) {
1426
1734
  throw new Error(`Memory already exists at ${file}`);
1427
1735
  }
@@ -1432,12 +1740,15 @@ async function memTried(input, ctx) {
1432
1740
  memory_id: frontmatter.id,
1433
1741
  kind: input.sensor.kind ?? "regex",
1434
1742
  pattern: input.sensor.pattern,
1743
+ rule: input.sensor.rule,
1744
+ language: input.sensor.language,
1435
1745
  command: input.sensor.command,
1436
1746
  timeout_ms: input.sensor.timeout_ms,
1437
1747
  absent: input.sensor.absent,
1438
1748
  severity: input.sensor.severity ?? "block",
1439
1749
  message: input.sensor.message,
1440
1750
  incident: input.sensor.incident,
1751
+ red_ref: input.sensor.red_ref,
1441
1752
  bad_example: input.sensor.bad_example,
1442
1753
  flags: void 0,
1443
1754
  paths: []
@@ -1479,7 +1790,7 @@ async function memTried(input, ctx) {
1479
1790
  // src/tools/scaffold-test.ts
1480
1791
  import { existsSync as existsSync17, statSync } from "fs";
1481
1792
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile10 } from "fs/promises";
1482
- import path7 from "path";
1793
+ import path8 from "path";
1483
1794
  import { z as z17 } from "zod";
1484
1795
  import {
1485
1796
  buildProposeCommand,
@@ -1491,17 +1802,17 @@ import {
1491
1802
  } from "@hivelore/core";
1492
1803
  var PY_SIGNALS = ["pyproject.toml", "setup.py", "pytest.ini", "requirements.txt", "tox.ini"];
1493
1804
  async function detectForAnchor(root, rel) {
1494
- let dir = path7.resolve(root, rel);
1805
+ let dir = path8.resolve(root, rel);
1495
1806
  try {
1496
- if (!statSync(dir).isDirectory()) dir = path7.dirname(dir);
1807
+ if (!statSync(dir).isDirectory()) dir = path8.dirname(dir);
1497
1808
  } catch {
1498
- if (path7.extname(dir)) dir = path7.dirname(dir);
1809
+ if (path8.extname(dir)) dir = path8.dirname(dir);
1499
1810
  }
1500
1811
  while (dir.startsWith(root)) {
1501
- const pkgJson = path7.join(dir, "package.json");
1812
+ const pkgJson = path8.join(dir, "package.json");
1502
1813
  const hasPkg = existsSync17(pkgJson);
1503
- const goMod = existsSync17(path7.join(dir, "go.mod"));
1504
- const pySignal = PY_SIGNALS.some((s) => existsSync17(path7.join(dir, s)));
1814
+ const goMod = existsSync17(path8.join(dir, "go.mod"));
1815
+ const pySignal = PY_SIGNALS.some((s) => existsSync17(path8.join(dir, s)));
1505
1816
  if (hasPkg || goMod || pySignal) {
1506
1817
  let pkg = null;
1507
1818
  if (hasPkg) {
@@ -1511,10 +1822,10 @@ async function detectForAnchor(root, rel) {
1511
1822
  pkg = null;
1512
1823
  }
1513
1824
  }
1514
- const baseDir = path7.relative(root, dir).split(path7.sep).join("/");
1825
+ const baseDir = path8.relative(root, dir).split(path8.sep).join("/");
1515
1826
  return { framework: pickTestFramework(pkg, { goMod, pySignal }), baseDir };
1516
1827
  }
1517
- const parent = path7.dirname(dir);
1828
+ const parent = path8.dirname(dir);
1518
1829
  if (parent === dir || dir === root) break;
1519
1830
  dir = parent;
1520
1831
  }
@@ -1580,14 +1891,14 @@ async function scaffoldTest(input, ctx) {
1580
1891
  }
1581
1892
  const results = [];
1582
1893
  for (const scaffold of scaffolds) {
1583
- const abs = path7.isAbsolute(scaffold.relPath) ? scaffold.relPath : path7.resolve(ctx.paths.root, scaffold.relPath);
1894
+ const abs = path8.isAbsolute(scaffold.relPath) ? scaffold.relPath : path8.resolve(ctx.paths.root, scaffold.relPath);
1584
1895
  let written = false;
1585
1896
  let alreadyExists = false;
1586
1897
  if (input.write) {
1587
1898
  if (existsSync17(abs)) {
1588
1899
  alreadyExists = true;
1589
1900
  } else {
1590
- await mkdir4(path7.dirname(abs), { recursive: true });
1901
+ await mkdir4(path8.dirname(abs), { recursive: true });
1591
1902
  await writeFile10(abs, scaffold.content, "utf8");
1592
1903
  written = true;
1593
1904
  }
@@ -1621,7 +1932,7 @@ async function scaffoldTest(input, ctx) {
1621
1932
  // src/tools/ingest-findings.ts
1622
1933
  import { existsSync as existsSync18 } from "fs";
1623
1934
  import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile11 } from "fs/promises";
1624
- import path8 from "path";
1935
+ import path9 from "path";
1625
1936
  import {
1626
1937
  draftsFromFindings,
1627
1938
  filterNewDrafts,
@@ -1652,7 +1963,7 @@ async function ingestFindings(input, ctx) {
1652
1963
  if (input.report && input.report.trim()) {
1653
1964
  raw = input.report;
1654
1965
  } else if (input.report_path) {
1655
- const file = path8.resolve(ctx.paths.root, input.report_path);
1966
+ const file = path9.resolve(ctx.paths.root, input.report_path);
1656
1967
  if (!existsSync18(file)) throw new Error(`Report file not found: ${file}`);
1657
1968
  raw = await readFile5(file, "utf8");
1658
1969
  } else {
@@ -1706,7 +2017,7 @@ async function writeDraft(ctx, draft) {
1706
2017
  draft.frontmatter.id,
1707
2018
  draft.frontmatter.module
1708
2019
  );
1709
- await mkdir5(path8.dirname(file), { recursive: true });
2020
+ await mkdir5(path9.dirname(file), { recursive: true });
1710
2021
  await writeFile11(file, serializeMemory9({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
1711
2022
  return file;
1712
2023
  }
@@ -1714,7 +2025,7 @@ async function writeDraft(ctx, draft) {
1714
2025
  // src/tools/mem-session-end.ts
1715
2026
  import { writeFile as writeFile13, mkdir as mkdir7 } from "fs/promises";
1716
2027
  import { existsSync as existsSync20 } from "fs";
1717
- import path10 from "path";
2028
+ import path11 from "path";
1718
2029
  import {
1719
2030
  buildFrontmatter as buildFrontmatter3,
1720
2031
  loadMemoriesFromDir as loadMemoriesFromDir16,
@@ -1732,10 +2043,10 @@ import {
1732
2043
  } from "@hivelore/core";
1733
2044
  import { mkdir as mkdir6, writeFile as writeFile12, rm } from "fs/promises";
1734
2045
  import { existsSync as existsSync19 } from "fs";
1735
- import path9 from "path";
1736
- import { execSync as execSync2 } from "child_process";
2046
+ import path10 from "path";
2047
+ import { execSync } from "child_process";
1737
2048
  function pendingDistillPath(ctx) {
1738
- return path9.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
2049
+ return path10.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
1739
2050
  }
1740
2051
  var SessionTracker = class {
1741
2052
  events = [];
@@ -1776,7 +2087,7 @@ var SessionTracker = class {
1776
2087
  }
1777
2088
  let gitDiff;
1778
2089
  try {
1779
- const raw = execSync2("git diff HEAD", {
2090
+ const raw = execSync("git diff HEAD", {
1780
2091
  cwd: this.ctx.paths.root,
1781
2092
  timeout: 5e3,
1782
2093
  encoding: "utf8",
@@ -1808,7 +2119,7 @@ var SessionTracker = class {
1808
2119
  const triedThreads = this.events.filter((e) => e.tool === "mem_tried").map((e) => e.summary ?? "").filter(Boolean);
1809
2120
  let diffStat;
1810
2121
  try {
1811
- diffStat = execSync2("git diff --stat HEAD", {
2122
+ diffStat = execSync("git diff --stat HEAD", {
1812
2123
  cwd: this.ctx.paths.root,
1813
2124
  timeout: 5e3,
1814
2125
  encoding: "utf8",
@@ -1852,7 +2163,7 @@ var SessionTracker = class {
1852
2163
  ...gitDiff ? { git_diff: gitDiff } : {},
1853
2164
  ...recapId ? { recap_id: recapId } : {}
1854
2165
  };
1855
- const cacheDir = path9.join(this.ctx.paths.haiveDir, ".cache");
2166
+ const cacheDir = path10.join(this.ctx.paths.haiveDir, ".cache");
1856
2167
  await mkdir6(cacheDir, { recursive: true });
1857
2168
  await writeFile12(
1858
2169
  pendingDistillPath(this.ctx),
@@ -1934,12 +2245,12 @@ async function memSessionEnd(input, ctx) {
1934
2245
  const body = buildBody(input);
1935
2246
  const topic = recapTopic(input.scope, input.module);
1936
2247
  const normalizedFiles = input.files_touched.map((p) => {
1937
- if (!p || !path10.isAbsolute(p)) return p;
1938
- const rel = path10.relative(ctx.paths.root, p);
2248
+ if (!p || !path11.isAbsolute(p)) return p;
2249
+ const rel = path11.relative(ctx.paths.root, p);
1939
2250
  return rel.startsWith("..") ? p : rel;
1940
2251
  });
1941
2252
  const invalidPaths = normalizedFiles.filter(
1942
- (p) => !existsSync20(path10.resolve(ctx.paths.root, p))
2253
+ (p) => !existsSync20(path11.resolve(ctx.paths.root, p))
1943
2254
  );
1944
2255
  if (invalidPaths.length > 0) {
1945
2256
  console.warn(`[haive] session end: anchor path(s) not found: ${invalidPaths.join(", ")}`);
@@ -1990,7 +2301,7 @@ async function memSessionEnd(input, ctx) {
1990
2301
  frontmatter.id,
1991
2302
  frontmatter.module
1992
2303
  );
1993
- await mkdir7(path10.dirname(file), { recursive: true });
2304
+ await mkdir7(path11.dirname(file), { recursive: true });
1994
2305
  await writeFile13(file, serializeMemory10({ frontmatter, body }), "utf8");
1995
2306
  await clearPendingDistill(ctx);
1996
2307
  return {
@@ -2005,7 +2316,7 @@ async function memSessionEnd(input, ctx) {
2005
2316
  // src/tools/get-briefing.ts
2006
2317
  import { readFile as readFile7, writeFile as writeFile14, readdir as readdir4 } from "fs/promises";
2007
2318
  import { existsSync as existsSync22 } from "fs";
2008
- import path12 from "path";
2319
+ import path13 from "path";
2009
2320
  import {
2010
2321
  allocateBudget,
2011
2322
  assessBootstrapState,
@@ -2052,7 +2363,7 @@ import { z as z20 } from "zod";
2052
2363
  // src/tools/briefing-helpers.ts
2053
2364
  import { readdir as readdir3, readFile as readFile6 } from "fs/promises";
2054
2365
  import { existsSync as existsSync21 } from "fs";
2055
- import path11 from "path";
2366
+ import path12 from "path";
2056
2367
  import {
2057
2368
  classifyMemoryPriority as coreClassifyPriority,
2058
2369
  isGlobPath,
@@ -2192,7 +2503,7 @@ async function loadModuleContexts2(ctx, modules) {
2192
2503
  const out = [];
2193
2504
  for (const m of modules) {
2194
2505
  if (!available.has(m)) continue;
2195
- const file = path11.join(ctx.paths.modulesContextDir, m, "context.md");
2506
+ const file = path12.join(ctx.paths.modulesContextDir, m, "context.md");
2196
2507
  if (existsSync21(file)) {
2197
2508
  out.push({ name: m, content: await readFile6(file, "utf8") });
2198
2509
  }
@@ -2220,6 +2531,8 @@ var GetBriefingInputSchema = {
2220
2531
  ),
2221
2532
  include_stale: z20.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
2222
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."),
2223
2536
  format: z20.enum(["full", "compact", "actions"]).default("full").describe(
2224
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."
2225
2538
  ),
@@ -2248,9 +2561,11 @@ async function getBriefing(input, ctx) {
2248
2561
  let searchMode = "literal";
2249
2562
  let usage = { version: 1, updated_at: "", by_id: {} };
2250
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);
2251
2566
  let lastSession;
2252
2567
  if (existsSync22(ctx.paths.memoriesDir)) {
2253
- const allLoaded = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
2568
+ const allLoaded = (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed);
2254
2569
  const recaps = allLoaded.filter(({ memory }) => memory.frontmatter.type === "session_recap").sort(
2255
2570
  (a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
2256
2571
  );
@@ -2283,10 +2598,11 @@ async function getBriefing(input, ctx) {
2283
2598
  if (memoryHasExcludedTag(memory.frontmatter, excludeTags)) return false;
2284
2599
  return true;
2285
2600
  });
2286
- usage = await loadUsageIndex8(ctx.paths);
2601
+ usage = input.deterministic ? { version: 1, updated_at: "", by_id: {} } : await loadUsageIndex8(ctx.paths);
2287
2602
  byId = new Map(allMemories.map((m) => [m.memory.frontmatter.id, m]));
2288
- const semanticHits = input.task && input.semantic ? await trySemanticHits(ctx, input.task, allMemories.length * 2) : null;
2289
- 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) {
2290
2606
  searchMode = semanticHits ? "semantic" : "literal_fallback";
2291
2607
  }
2292
2608
  const seen = /* @__PURE__ */ new Map();
@@ -2622,7 +2938,7 @@ ${m.content}`).join("\n\n---\n\n"),
2622
2938
  }
2623
2939
  }
2624
2940
  if (existsSync22(ctx.paths.memoriesDir)) {
2625
- const allMems = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
2941
+ const allMems = (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed);
2626
2942
  for (const { memory } of allMems) {
2627
2943
  const fm = memory.frontmatter;
2628
2944
  if (!fm.requires_human_approval) continue;
@@ -2679,7 +2995,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
2679
2995
  pcRaw = await readFile7(ctx.paths.projectContext, "utf8");
2680
2996
  } catch {
2681
2997
  }
2682
- 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) : [];
2683
2999
  const cmForBootstrap = await loadCodeMap(ctx.paths);
2684
3000
  let existingModules = [];
2685
3001
  try {
@@ -2690,7 +3006,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
2690
3006
  const bootstrap = assessBootstrapState({
2691
3007
  projectContextRaw: pcRaw,
2692
3008
  memories: allForBootstrap,
2693
- codeFiles: cmForBootstrap ? Object.keys(cmForBootstrap.files) : [],
3009
+ codeFiles: cmForBootstrap ? Object.keys(cmForBootstrap.files).filter((file) => existsSync22(path13.join(ctx.paths.root, file))) : [],
2694
3010
  existingModules
2695
3011
  });
2696
3012
  if (bootstrap.state !== "ready" && bootstrap.metrics.mainAreas > 0) {
@@ -2813,7 +3129,7 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
2813
3129
  };
2814
3130
  }
2815
3131
  async function detectRunCommands(root) {
2816
- const pkgPath = path12.join(root, "package.json");
3132
+ const pkgPath = path13.join(root, "package.json");
2817
3133
  if (!existsSync22(pkgPath)) return null;
2818
3134
  try {
2819
3135
  const pkg = JSON.parse(await readFile7(pkgPath, "utf8"));
@@ -2881,7 +3197,7 @@ function oneLine(value) {
2881
3197
  return value.replace(/\s+/g, " ").replace(/"/g, '\\"').trim().slice(0, 120);
2882
3198
  }
2883
3199
  function serverVersion() {
2884
- return true ? "0.39.2" : "dev";
3200
+ return true ? "0.43.2" : "dev";
2885
3201
  }
2886
3202
 
2887
3203
  // src/tools/code-map.ts
@@ -4054,6 +4370,7 @@ ${template}\`\`\`
4054
4370
  // src/prompts/bootstrap-repo.ts
4055
4371
  import { readFile as readFile8, readdir as readdir5 } from "fs/promises";
4056
4372
  import { existsSync as existsSync29 } from "fs";
4373
+ import path14 from "path";
4057
4374
  import {
4058
4375
  assessBootstrapState as assessBootstrapState2,
4059
4376
  loadCodeMap as loadCodeMap4,
@@ -4081,7 +4398,7 @@ async function currentAssessment(ctx) {
4081
4398
  return assessBootstrapState2({
4082
4399
  projectContextRaw,
4083
4400
  memories,
4084
- codeFiles: codeMap ? Object.keys(codeMap.files) : [],
4401
+ codeFiles: codeMap ? Object.keys(codeMap.files).filter((file) => existsSync29(path14.join(ctx.paths.root, file))) : [],
4085
4402
  existingModules
4086
4403
  });
4087
4404
  }
@@ -4309,7 +4626,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
4309
4626
  // src/server.ts
4310
4627
  import { hasRecentBriefingMarker, loadConfigSync } from "@hivelore/core";
4311
4628
  var SERVER_NAME = "hivelore";
4312
- var SERVER_VERSION = "0.39.2";
4629
+ var SERVER_VERSION = "0.43.2";
4313
4630
  function jsonResult(data) {
4314
4631
  return {
4315
4632
  content: [
@@ -5261,6 +5578,8 @@ export {
5261
5578
  SERVER_VERSION,
5262
5579
  TOOL_PROFILES,
5263
5580
  antiPatternsCheck,
5581
+ astEngineAvailable,
5582
+ astLangForPath,
5264
5583
  codeMapTool,
5265
5584
  codeSearch,
5266
5585
  createHaiveServer,
@@ -5281,6 +5600,8 @@ export {
5281
5600
  printHaiveMcpVersion,
5282
5601
  proposeSensor,
5283
5602
  readPresumedCorrectTargets,
5603
+ runAstPattern,
5604
+ runAstSensorOnContent,
5284
5605
  runHaiveMcpStdio,
5285
5606
  scaffoldTest
5286
5607
  };