@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/index.js CHANGED
@@ -1124,7 +1124,7 @@ async function memApprove(input, ctx) {
1124
1124
  // src/tools/mem-tried.ts
1125
1125
  import { mkdir as mkdir3, writeFile as writeFile9 } from "fs/promises";
1126
1126
  import { existsSync as existsSync16 } from "fs";
1127
- import path6 from "path";
1127
+ import path7 from "path";
1128
1128
  import {
1129
1129
  buildFrontmatter as buildFrontmatter2,
1130
1130
  memoryFilePath as memoryFilePath2,
@@ -1134,25 +1134,146 @@ import {
1134
1134
  import { z as z16 } from "zod";
1135
1135
 
1136
1136
  // src/tools/propose-sensor.ts
1137
- import { execSync } from "child_process";
1137
+ import { execFileSync } from "child_process";
1138
1138
  import { readFile as readFile3, writeFile as writeFile8 } from "fs/promises";
1139
- import { existsSync as existsSync15 } from "fs";
1140
- import path5 from "path";
1139
+ import { existsSync as existsSync15, rmSync, symlinkSync } from "fs";
1140
+ import os from "os";
1141
+ import path6 from "path";
1141
1142
  import {
1142
1143
  extractSensorExamples,
1143
1144
  extractTestFilePathsFromCommand,
1144
1145
  hasPendingTestMarker,
1145
1146
  judgeProposedSensor,
1146
1147
  loadMemoriesFromDir as loadMemoriesFromDir13,
1148
+ scrubbedCommandEnv,
1149
+ sensorPatternBrittleness,
1147
1150
  serializeMemory as serializeMemory7
1148
1151
  } from "@hivelore/core";
1149
1152
  import { z as z15 } from "zod";
1153
+
1154
+ // src/ast-sensors.ts
1155
+ import path5 from "path";
1156
+ var cachedEngine;
1157
+ var registeredDynamicLanguages = /* @__PURE__ */ new Set();
1158
+ async function loadDynamicLanguages(engine) {
1159
+ const registrations = {};
1160
+ const candidates = [
1161
+ ["python", () => import("@ast-grep/lang-python")],
1162
+ ["go", () => import("@ast-grep/lang-go")],
1163
+ ["rust", () => import("@ast-grep/lang-rust")],
1164
+ ["java", () => import("@ast-grep/lang-java")]
1165
+ ];
1166
+ for (const [name, load] of candidates) {
1167
+ try {
1168
+ const mod = await load();
1169
+ registrations[name] = mod.default;
1170
+ registeredDynamicLanguages.add(name);
1171
+ } catch {
1172
+ }
1173
+ }
1174
+ if (Object.keys(registrations).length > 0) {
1175
+ engine.registerDynamicLanguage(registrations);
1176
+ }
1177
+ }
1178
+ async function loadAstEngine() {
1179
+ if (cachedEngine !== void 0) return cachedEngine;
1180
+ try {
1181
+ cachedEngine = await import("@ast-grep/napi");
1182
+ await loadDynamicLanguages(cachedEngine);
1183
+ } catch {
1184
+ cachedEngine = null;
1185
+ }
1186
+ return cachedEngine;
1187
+ }
1188
+ async function astEngineAvailable() {
1189
+ return await loadAstEngine() !== null;
1190
+ }
1191
+ function astLangForPath(filePath, explicitLanguage) {
1192
+ if (explicitLanguage) {
1193
+ const normalized = explicitLanguage.toLowerCase();
1194
+ if (["typescript", "tsx", "javascript", "html", "css"].includes(normalized)) {
1195
+ return normalized === "typescript" ? "TypeScript" : normalized === "tsx" ? "Tsx" : normalized === "javascript" ? "JavaScript" : normalized[0].toUpperCase() + normalized.slice(1);
1196
+ }
1197
+ return registeredDynamicLanguages.has(normalized) ? normalized : null;
1198
+ }
1199
+ const ext = path5.extname(filePath).toLowerCase();
1200
+ if (ext === ".ts" || ext === ".mts" || ext === ".cts") return "TypeScript";
1201
+ if (ext === ".tsx") return "Tsx";
1202
+ if (ext === ".js" || ext === ".jsx" || ext === ".mjs" || ext === ".cjs") return "JavaScript";
1203
+ if (ext === ".py" && registeredDynamicLanguages.has("python")) return "python";
1204
+ if (ext === ".go" && registeredDynamicLanguages.has("go")) return "go";
1205
+ if (ext === ".rs" && registeredDynamicLanguages.has("rust")) return "rust";
1206
+ if (ext === ".java" && registeredDynamicLanguages.has("java")) return "java";
1207
+ return null;
1208
+ }
1209
+ function absentPresentInNode(node, absent) {
1210
+ try {
1211
+ if (node.find(absent)) return true;
1212
+ } catch {
1213
+ }
1214
+ const text = node.text();
1215
+ try {
1216
+ return new RegExp(absent).test(text);
1217
+ } catch {
1218
+ return text.includes(absent);
1219
+ }
1220
+ }
1221
+ async function runAstPattern(content, filePath, pattern, absent, rule, language) {
1222
+ const engine = await loadAstEngine();
1223
+ if (!engine) return { status: "engine-missing", matches: [] };
1224
+ const langName = astLangForPath(filePath, language);
1225
+ if (!langName) return { status: "unsupported-language", matches: [] };
1226
+ const lang = langName in engine.Lang ? engine.Lang[langName] : langName;
1227
+ let root;
1228
+ try {
1229
+ root = engine.parse(lang, content).root();
1230
+ } catch (err) {
1231
+ return { status: "parse-error", matches: [], detail: String(err).slice(0, 200) };
1232
+ }
1233
+ let nodes;
1234
+ try {
1235
+ const matcher = rule ? { rule: pattern ? { all: [{ pattern }, rule] } : rule } : pattern;
1236
+ if (!matcher) return { status: "invalid-pattern", matches: [], detail: "missing pattern/rule" };
1237
+ nodes = root.findAll(matcher);
1238
+ } catch (err) {
1239
+ return { status: "invalid-pattern", matches: [], detail: String(err).slice(0, 200) };
1240
+ }
1241
+ const matches = [];
1242
+ for (const node of nodes) {
1243
+ if (absent && absentPresentInNode(node, absent)) continue;
1244
+ const range = node.range();
1245
+ matches.push({
1246
+ startLine: range.start.line + 1,
1247
+ endLine: range.end.line + 1,
1248
+ text: node.text().trim().slice(0, 200)
1249
+ });
1250
+ }
1251
+ return { status: "ok", matches };
1252
+ }
1253
+ async function runAstSensorOnContent(input) {
1254
+ const scan = await runAstPattern(input.content, input.filePath, input.pattern, input.absent, input.rule, input.language);
1255
+ if (scan.status !== "ok" || !input.addedLines || input.addedLines.size === 0) return scan;
1256
+ const added = input.addedLines;
1257
+ return {
1258
+ status: "ok",
1259
+ matches: scan.matches.filter((m) => {
1260
+ for (let line = m.startLine; line <= m.endLine; line++) {
1261
+ if (added.has(line)) return true;
1262
+ }
1263
+ return false;
1264
+ })
1265
+ };
1266
+ }
1267
+
1268
+ // src/tools/propose-sensor.ts
1150
1269
  var ProposeSensorInputSchema = {
1151
1270
  memory_id: z15.string().min(1).describe("Id of the gotcha/attempt memory this sensor protects."),
1152
- kind: z15.enum(["regex", "shell", "test"]).default("regex").describe(
1153
- "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."
1271
+ kind: z15.enum(["regex", "ast", "shell", "test"]).default("regex").describe(
1272
+ "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."
1154
1273
  ),
1155
- pattern: z15.string().optional().describe("kind=regex: regex matching the FAULTY usage (the risky call/token), e.g. 'stripe\\.paymentIntents\\.create'."),
1274
+ pattern: z15.string().optional().describe("kind=regex: regex matching the faulty usage; kind=ast: optional structural pattern (may be combined with `rule`)."),
1275
+ 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."),
1276
+ language: z15.string().optional().describe("kind=ast: explicit built-in/dynamic language name for non-standard file extensions."),
1156
1277
  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."),
1157
1278
  timeout_ms: z15.number().int().positive().optional().describe("kind=shell|test: max runtime before the executor kills the command (default 120000)."),
1158
1279
  absent: z15.string().optional().describe(
@@ -1164,6 +1285,9 @@ var ProposeSensorInputSchema = {
1164
1285
  incident: z15.string().optional().describe(
1165
1286
  "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."
1166
1287
  ),
1288
+ red_ref: z15.string().optional().describe(
1289
+ "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."
1290
+ ),
1167
1291
  flags: z15.string().optional().describe("Optional regex flags (e.g. 'i' for case-insensitive)."),
1168
1292
  paths: z15.array(z15.string()).default([]).describe("Override scope paths. Defaults to the memory's anchor paths.")
1169
1293
  };
@@ -1180,7 +1304,7 @@ async function readPresumedCorrectTargets(root, relPaths) {
1180
1304
  const targets = [];
1181
1305
  for (const rel of relPaths) {
1182
1306
  try {
1183
- const content = execSync(`git show ${JSON.stringify(`HEAD:./${rel}`)}`, {
1307
+ const content = execFileSync("git", ["show", `HEAD:./${rel}`], {
1184
1308
  cwd: root,
1185
1309
  encoding: "utf8",
1186
1310
  maxBuffer: 10 * 1024 * 1024,
@@ -1190,7 +1314,7 @@ async function readPresumedCorrectTargets(root, relPaths) {
1190
1314
  continue;
1191
1315
  } catch {
1192
1316
  }
1193
- const abs = path5.resolve(root, rel);
1317
+ const abs = path6.resolve(root, rel);
1194
1318
  if (!existsSync15(abs)) continue;
1195
1319
  try {
1196
1320
  targets.push({ path: rel, content: await readFile3(abs, "utf8") });
@@ -1201,11 +1325,13 @@ async function readPresumedCorrectTargets(root, relPaths) {
1201
1325
  }
1202
1326
  function runCommandForValidation(command, root, timeoutMs = 12e4) {
1203
1327
  try {
1204
- execSync(`bash -c ${JSON.stringify(command)}`, {
1328
+ execFileSync("bash", ["-c", command], {
1205
1329
  cwd: root,
1206
1330
  timeout: timeoutMs,
1207
1331
  maxBuffer: 8 * 1024 * 1024,
1208
- stdio: ["ignore", "pipe", "pipe"]
1332
+ stdio: ["ignore", "pipe", "pipe"],
1333
+ // Same containment as the gate executor: an oracle gets a test-runner env, not credentials.
1334
+ env: { ...scrubbedCommandEnv(process.env), HIVELORE_SENSOR: "validation" }
1209
1335
  });
1210
1336
  return { status: "passed", detail: "exit 0" };
1211
1337
  } catch (err) {
@@ -1219,6 +1345,48 @@ ${e.stderr?.toString() ?? ""}`.split("\n").filter(Boolean).slice(-8).join("\n");
1219
1345
  return { status: "failed", detail: out || `exit ${e.status ?? "?"}` };
1220
1346
  }
1221
1347
  }
1348
+ function proveRedOnIncident(command, root, redRef, timeoutMs) {
1349
+ const worktree = path6.join(os.tmpdir(), `hivelore-red-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
1350
+ let added = false;
1351
+ try {
1352
+ try {
1353
+ execFileSync("git", ["worktree", "add", "--detach", worktree, redRef], {
1354
+ cwd: root,
1355
+ stdio: ["ignore", "pipe", "pipe"],
1356
+ timeout: 6e4
1357
+ });
1358
+ added = true;
1359
+ } catch (err) {
1360
+ const e = err;
1361
+ return { proven: false, reason: "red-ref-invalid", detail: (e.stderr?.toString() ?? String(err)).slice(0, 300) };
1362
+ }
1363
+ const mainModules = path6.join(root, "node_modules");
1364
+ const wtModules = path6.join(worktree, "node_modules");
1365
+ if (existsSync15(mainModules) && !existsSync15(wtModules)) {
1366
+ try {
1367
+ symlinkSync(mainModules, wtModules, "dir");
1368
+ } catch {
1369
+ }
1370
+ }
1371
+ const run = runCommandForValidation(command, worktree, timeoutMs);
1372
+ if (run.status === "failed") return { proven: true, detail: run.detail };
1373
+ if (run.status === "passed") {
1374
+ return { proven: false, reason: "red-not-proven", detail: "oracle PASSED on the incident state \u2014 it does not catch the incident" };
1375
+ }
1376
+ return { proven: false, reason: "red-unrunnable", detail: run.detail };
1377
+ } finally {
1378
+ if (added) {
1379
+ try {
1380
+ execFileSync("git", ["worktree", "remove", "--force", worktree], { cwd: root, stdio: "ignore", timeout: 6e4 });
1381
+ } catch {
1382
+ try {
1383
+ rmSync(worktree, { recursive: true, force: true });
1384
+ } catch {
1385
+ }
1386
+ }
1387
+ }
1388
+ }
1389
+ }
1222
1390
  async function proposeSensor(input, ctx) {
1223
1391
  if (!existsSync15(ctx.paths.memoriesDir)) {
1224
1392
  throw new Error(`No .ai/memories at ${ctx.paths.root}. Run 'hivelore init' first.`);
@@ -1248,6 +1416,17 @@ async function proposeSensor(input, ctx) {
1248
1416
  self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1249
1417
  };
1250
1418
  }
1419
+ } else if (kind === "ast") {
1420
+ if (!input.pattern?.trim() && !input.rule) {
1421
+ return {
1422
+ accepted: false,
1423
+ memory_id: input.memory_id,
1424
+ severity: input.severity,
1425
+ reason: "invalid-pattern",
1426
+ guidance: "kind=ast requires a structural `pattern` or a full ast-grep `rule` object.",
1427
+ self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1428
+ };
1429
+ }
1251
1430
  } else if (!input.command?.trim()) {
1252
1431
  return {
1253
1432
  accepted: false,
@@ -1264,12 +1443,111 @@ async function proposeSensor(input, ctx) {
1264
1443
  throw new Error(`No memory found with id ${input.memory_id}`);
1265
1444
  }
1266
1445
  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}.` : "";
1267
- if (kind !== "regex") {
1268
- const referencedTests = extractTestFilePathsFromCommand(input.command.trim()).filter((rel) => existsSync15(path5.resolve(ctx.paths.root, rel)));
1446
+ if (kind === "ast") {
1447
+ const pattern = input.pattern?.trim();
1448
+ if (!await astEngineAvailable() && input.severity === "block") {
1449
+ return {
1450
+ accepted: false,
1451
+ memory_id: input.memory_id,
1452
+ severity: input.severity,
1453
+ reason: "ast-engine-missing",
1454
+ 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.",
1455
+ self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1456
+ };
1457
+ }
1458
+ const brittleAst = pattern ? sensorPatternBrittleness(pattern) : null;
1459
+ if (brittleAst && input.severity === "block") {
1460
+ return {
1461
+ accepted: false,
1462
+ memory_id: input.memory_id,
1463
+ severity: input.severity,
1464
+ reason: "brittle",
1465
+ guidance: `The pattern is brittle (${brittleAst}). Use a durable structural pattern, then re-propose.`,
1466
+ self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1467
+ };
1468
+ }
1469
+ const anchorPathsAst = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
1470
+ const currentTargetsAst = await readPresumedCorrectTargets(ctx.paths.root, anchorPathsAst);
1471
+ const firedOnAst = [];
1472
+ for (const target of currentTargetsAst) {
1473
+ const scan = await runAstSensorOnContent({ pattern, rule: input.rule, language: input.language, absent: input.absent, content: target.content, filePath: target.path });
1474
+ if (scan.status === "invalid-pattern") {
1475
+ return {
1476
+ accepted: false,
1477
+ memory_id: input.memory_id,
1478
+ severity: input.severity,
1479
+ reason: "invalid-pattern",
1480
+ guidance: `The ast-grep pattern is invalid: ${scan.detail ?? "unparseable"}. Fix it and re-propose.`,
1481
+ self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1482
+ };
1483
+ }
1484
+ if (scan.status === "ok" && scan.matches.length > 0) firedOnAst.push(target.path);
1485
+ }
1486
+ if (firedOnAst.length > 0 && input.severity === "block") {
1487
+ return {
1488
+ accepted: false,
1489
+ memory_id: input.memory_id,
1490
+ severity: input.severity,
1491
+ reason: "fires-on-current",
1492
+ 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.`,
1493
+ self_check: { silent_on_current: false, fires_on_bad: null, fired_on: firedOnAst }
1494
+ };
1495
+ }
1496
+ const badExamplesAst = [
1497
+ ...input.bad_example ? [input.bad_example] : [],
1498
+ ...extractSensorExamples(found.memory.body)
1499
+ ];
1500
+ let firesOnBadAst = null;
1501
+ if (badExamplesAst.length > 0 && await astEngineAvailable()) {
1502
+ const exampleLang = anchorPathsAst.find((p) => astLangForPath(p, input.language) !== null) ?? "example.tsx";
1503
+ firesOnBadAst = false;
1504
+ for (const example of badExamplesAst) {
1505
+ const scan = await runAstSensorOnContent({ pattern, rule: input.rule, language: input.language, absent: input.absent, content: example, filePath: exampleLang });
1506
+ if (scan.status === "ok" && scan.matches.length > 0) {
1507
+ firesOnBadAst = true;
1508
+ break;
1509
+ }
1510
+ }
1511
+ }
1512
+ if (firesOnBadAst === false && input.severity === "block") {
1513
+ return {
1514
+ accepted: false,
1515
+ memory_id: input.memory_id,
1516
+ severity: input.severity,
1517
+ reason: "missed-bad-example",
1518
+ guidance: "The pattern did not match the bad example structurally, so it won't catch the mistake. Adjust it, then re-propose.",
1519
+ self_check: { silent_on_current: firedOnAst.length === 0, fires_on_bad: false, fired_on: [] }
1520
+ };
1521
+ }
1522
+ const sensorAst = {
1523
+ kind: "ast",
1524
+ ...pattern ? { pattern } : {},
1525
+ ...input.rule ? { rule: input.rule } : {},
1526
+ ...input.language ? { language: input.language } : {},
1527
+ ...input.absent ? { absent: input.absent } : {},
1528
+ paths: anchorPathsAst,
1529
+ message: input.message?.trim() || deriveMessage(found.memory.body, pattern ?? "AST rule", input.absent),
1530
+ ...input.incident?.trim() ? { incident: input.incident.trim() } : {},
1531
+ severity: input.severity,
1532
+ autogen: false,
1533
+ last_fired: null
1534
+ };
1535
+ await writeFile8(found.filePath, serializeMemory7({ frontmatter: { ...found.memory.frontmatter, sensor: sensorAst }, body: found.memory.body }), "utf8");
1536
+ return {
1537
+ accepted: true,
1538
+ memory_id: input.memory_id,
1539
+ severity: input.severity,
1540
+ 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,
1541
+ self_check: { silent_on_current: firedOnAst.length === 0, fires_on_bad: firesOnBadAst, fired_on: firedOnAst },
1542
+ file_path: found.filePath
1543
+ };
1544
+ }
1545
+ if (kind === "shell" || kind === "test") {
1546
+ const referencedTests = extractTestFilePathsFromCommand(input.command.trim()).filter((rel) => existsSync15(path6.resolve(ctx.paths.root, rel)));
1269
1547
  const pendingTests = [];
1270
1548
  for (const rel of referencedTests) {
1271
1549
  try {
1272
- if (hasPendingTestMarker(await readFile3(path5.resolve(ctx.paths.root, rel), "utf8"))) pendingTests.push(rel);
1550
+ if (hasPendingTestMarker(await readFile3(path6.resolve(ctx.paths.root, rel), "utf8"))) pendingTests.push(rel);
1273
1551
  } catch {
1274
1552
  }
1275
1553
  }
@@ -1285,6 +1563,21 @@ async function proposeSensor(input, ctx) {
1285
1563
  }
1286
1564
  const verdictCmd = runCommandForValidation(input.command.trim(), ctx.paths.root, input.timeout_ms);
1287
1565
  const anchorPathsCmd = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
1566
+ let redProven = false;
1567
+ if (input.red_ref?.trim()) {
1568
+ const red = proveRedOnIncident(input.command.trim(), ctx.paths.root, input.red_ref.trim(), input.timeout_ms);
1569
+ if (!red.proven && input.severity === "block") {
1570
+ return {
1571
+ accepted: false,
1572
+ memory_id: input.memory_id,
1573
+ severity: input.severity,
1574
+ reason: red.reason ?? "red-not-proven",
1575
+ 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),
1576
+ self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: false, fired_on: [] }
1577
+ };
1578
+ }
1579
+ redProven = red.proven;
1580
+ }
1288
1581
  if (verdictCmd.status !== "passed" && input.severity === "block") {
1289
1582
  return {
1290
1583
  accepted: false,
@@ -1296,6 +1589,16 @@ ${verdictCmd.detail}`,
1296
1589
  self_check: { silent_on_current: false, fires_on_bad: null, fired_on: anchorPathsCmd }
1297
1590
  };
1298
1591
  }
1592
+ if (input.severity === "block" && !input.red_ref?.trim()) {
1593
+ return {
1594
+ accepted: false,
1595
+ memory_id: input.memory_id,
1596
+ severity: input.severity,
1597
+ reason: "red-required",
1598
+ 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.",
1599
+ self_check: { silent_on_current: true, fires_on_bad: null, fired_on: [] }
1600
+ };
1601
+ }
1299
1602
  const sensorCmd = {
1300
1603
  kind,
1301
1604
  command: input.command.trim(),
@@ -1303,6 +1606,7 @@ ${verdictCmd.detail}`,
1303
1606
  paths: anchorPathsCmd,
1304
1607
  message: input.message?.trim() || deriveMessage(found.memory.body, input.command.trim(), void 0),
1305
1608
  ...input.incident?.trim() ? { incident: input.incident.trim() } : {},
1609
+ ...redProven ? { red_proven: true } : {},
1306
1610
  severity: input.severity,
1307
1611
  autogen: false,
1308
1612
  last_fired: null
@@ -1316,8 +1620,9 @@ ${verdictCmd.detail}`,
1316
1620
  accepted: true,
1317
1621
  memory_id: input.memory_id,
1318
1622
  severity: input.severity,
1319
- 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,
1320
- self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: null, fired_on: [] }
1623
+ 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,
1624
+ self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: redProven ? true : null, fired_on: [] },
1625
+ file_path: found.filePath
1321
1626
  };
1322
1627
  }
1323
1628
  const anchorPaths = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
@@ -1384,14 +1689,17 @@ var MemTriedInputSchema = {
1384
1689
  paths: z16.array(z16.string()).default([]).describe("Anchor file paths this applies to"),
1385
1690
  author: z16.string().optional().describe("Author handle or email"),
1386
1691
  sensor: z16.object({
1387
- kind: z16.enum(["regex", "shell", "test"]).default("regex").describe("regex pattern, or a shell/test COMMAND the gate executes (behaviour bridge)"),
1388
- pattern: z16.string().optional().describe("kind=regex: regex matching the FAULTY usage (added diff lines)"),
1692
+ kind: z16.enum(["regex", "ast", "shell", "test"]).default("regex").describe("regex/AST pattern, or a shell/test command"),
1693
+ pattern: z16.string().optional().describe("kind=regex|ast: pattern matching the faulty usage"),
1694
+ rule: z16.record(z16.unknown()).optional().describe("kind=ast: full ast-grep Rule object"),
1695
+ language: z16.string().optional().describe("kind=ast: explicit built-in/dynamic language"),
1389
1696
  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)"),
1390
1697
  timeout_ms: z16.number().int().positive().optional().describe("kind=shell|test: max runtime (default 120000)"),
1391
1698
  absent: z16.string().optional().describe("kind=regex: regex marking CORRECT usage nearby \u2014 excludes it from firing"),
1392
1699
  severity: z16.enum(["warn", "block"]).default("block").describe("block = deterministic gate refusal"),
1393
1700
  message: z16.string().optional().describe("Self-correction message shown when the sensor fires"),
1394
1701
  incident: z16.string().optional().describe("Provenance: the incident this sensor guards (e.g. 'prod #442') \u2014 surfaced when it fires and in the receipt"),
1702
+ 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)"),
1395
1703
  bad_example: z16.string().optional().describe("kind=regex: code snippet the sensor MUST fire on (validation)")
1396
1704
  }).optional().describe(
1397
1705
  "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."
@@ -1423,7 +1731,7 @@ async function memTried(input, ctx) {
1423
1731
  }
1424
1732
  const body = lines.join("\n") + "\n";
1425
1733
  const file = memoryFilePath2(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
1426
- await mkdir3(path6.dirname(file), { recursive: true });
1734
+ await mkdir3(path7.dirname(file), { recursive: true });
1427
1735
  if (existsSync16(file)) {
1428
1736
  throw new Error(`Memory already exists at ${file}`);
1429
1737
  }
@@ -1434,12 +1742,15 @@ async function memTried(input, ctx) {
1434
1742
  memory_id: frontmatter.id,
1435
1743
  kind: input.sensor.kind ?? "regex",
1436
1744
  pattern: input.sensor.pattern,
1745
+ rule: input.sensor.rule,
1746
+ language: input.sensor.language,
1437
1747
  command: input.sensor.command,
1438
1748
  timeout_ms: input.sensor.timeout_ms,
1439
1749
  absent: input.sensor.absent,
1440
1750
  severity: input.sensor.severity ?? "block",
1441
1751
  message: input.sensor.message,
1442
1752
  incident: input.sensor.incident,
1753
+ red_ref: input.sensor.red_ref,
1443
1754
  bad_example: input.sensor.bad_example,
1444
1755
  flags: void 0,
1445
1756
  paths: []
@@ -1481,7 +1792,7 @@ async function memTried(input, ctx) {
1481
1792
  // src/tools/scaffold-test.ts
1482
1793
  import { existsSync as existsSync17, statSync } from "fs";
1483
1794
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile10 } from "fs/promises";
1484
- import path7 from "path";
1795
+ import path8 from "path";
1485
1796
  import { z as z17 } from "zod";
1486
1797
  import {
1487
1798
  buildProposeCommand,
@@ -1493,17 +1804,17 @@ import {
1493
1804
  } from "@hivelore/core";
1494
1805
  var PY_SIGNALS = ["pyproject.toml", "setup.py", "pytest.ini", "requirements.txt", "tox.ini"];
1495
1806
  async function detectForAnchor(root, rel) {
1496
- let dir = path7.resolve(root, rel);
1807
+ let dir = path8.resolve(root, rel);
1497
1808
  try {
1498
- if (!statSync(dir).isDirectory()) dir = path7.dirname(dir);
1809
+ if (!statSync(dir).isDirectory()) dir = path8.dirname(dir);
1499
1810
  } catch {
1500
- if (path7.extname(dir)) dir = path7.dirname(dir);
1811
+ if (path8.extname(dir)) dir = path8.dirname(dir);
1501
1812
  }
1502
1813
  while (dir.startsWith(root)) {
1503
- const pkgJson = path7.join(dir, "package.json");
1814
+ const pkgJson = path8.join(dir, "package.json");
1504
1815
  const hasPkg = existsSync17(pkgJson);
1505
- const goMod = existsSync17(path7.join(dir, "go.mod"));
1506
- const pySignal = PY_SIGNALS.some((s) => existsSync17(path7.join(dir, s)));
1816
+ const goMod = existsSync17(path8.join(dir, "go.mod"));
1817
+ const pySignal = PY_SIGNALS.some((s) => existsSync17(path8.join(dir, s)));
1507
1818
  if (hasPkg || goMod || pySignal) {
1508
1819
  let pkg = null;
1509
1820
  if (hasPkg) {
@@ -1513,10 +1824,10 @@ async function detectForAnchor(root, rel) {
1513
1824
  pkg = null;
1514
1825
  }
1515
1826
  }
1516
- const baseDir = path7.relative(root, dir).split(path7.sep).join("/");
1827
+ const baseDir = path8.relative(root, dir).split(path8.sep).join("/");
1517
1828
  return { framework: pickTestFramework(pkg, { goMod, pySignal }), baseDir };
1518
1829
  }
1519
- const parent = path7.dirname(dir);
1830
+ const parent = path8.dirname(dir);
1520
1831
  if (parent === dir || dir === root) break;
1521
1832
  dir = parent;
1522
1833
  }
@@ -1574,14 +1885,14 @@ async function scaffoldTest(input, ctx) {
1574
1885
  }
1575
1886
  const results = [];
1576
1887
  for (const scaffold of scaffolds) {
1577
- const abs = path7.isAbsolute(scaffold.relPath) ? scaffold.relPath : path7.resolve(ctx.paths.root, scaffold.relPath);
1888
+ const abs = path8.isAbsolute(scaffold.relPath) ? scaffold.relPath : path8.resolve(ctx.paths.root, scaffold.relPath);
1578
1889
  let written = false;
1579
1890
  let alreadyExists = false;
1580
1891
  if (input.write) {
1581
1892
  if (existsSync17(abs)) {
1582
1893
  alreadyExists = true;
1583
1894
  } else {
1584
- await mkdir4(path7.dirname(abs), { recursive: true });
1895
+ await mkdir4(path8.dirname(abs), { recursive: true });
1585
1896
  await writeFile10(abs, scaffold.content, "utf8");
1586
1897
  written = true;
1587
1898
  }
@@ -1615,7 +1926,7 @@ async function scaffoldTest(input, ctx) {
1615
1926
  // src/tools/ingest-findings.ts
1616
1927
  import { existsSync as existsSync18 } from "fs";
1617
1928
  import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile11 } from "fs/promises";
1618
- import path8 from "path";
1929
+ import path9 from "path";
1619
1930
  import {
1620
1931
  draftsFromFindings,
1621
1932
  filterNewDrafts,
@@ -1646,7 +1957,7 @@ async function ingestFindings(input, ctx) {
1646
1957
  if (input.report && input.report.trim()) {
1647
1958
  raw = input.report;
1648
1959
  } else if (input.report_path) {
1649
- const file = path8.resolve(ctx.paths.root, input.report_path);
1960
+ const file = path9.resolve(ctx.paths.root, input.report_path);
1650
1961
  if (!existsSync18(file)) throw new Error(`Report file not found: ${file}`);
1651
1962
  raw = await readFile5(file, "utf8");
1652
1963
  } else {
@@ -1700,7 +2011,7 @@ async function writeDraft(ctx, draft) {
1700
2011
  draft.frontmatter.id,
1701
2012
  draft.frontmatter.module
1702
2013
  );
1703
- await mkdir5(path8.dirname(file), { recursive: true });
2014
+ await mkdir5(path9.dirname(file), { recursive: true });
1704
2015
  await writeFile11(file, serializeMemory9({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
1705
2016
  return file;
1706
2017
  }
@@ -1708,7 +2019,7 @@ async function writeDraft(ctx, draft) {
1708
2019
  // src/tools/mem-session-end.ts
1709
2020
  import { writeFile as writeFile13, mkdir as mkdir7 } from "fs/promises";
1710
2021
  import { existsSync as existsSync20 } from "fs";
1711
- import path10 from "path";
2022
+ import path11 from "path";
1712
2023
  import {
1713
2024
  buildFrontmatter as buildFrontmatter3,
1714
2025
  loadMemoriesFromDir as loadMemoriesFromDir16,
@@ -1726,10 +2037,10 @@ import {
1726
2037
  } from "@hivelore/core";
1727
2038
  import { mkdir as mkdir6, writeFile as writeFile12, rm } from "fs/promises";
1728
2039
  import { existsSync as existsSync19 } from "fs";
1729
- import path9 from "path";
1730
- import { execSync as execSync2 } from "child_process";
2040
+ import path10 from "path";
2041
+ import { execSync } from "child_process";
1731
2042
  function pendingDistillPath(ctx) {
1732
- return path9.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
2043
+ return path10.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
1733
2044
  }
1734
2045
  var SessionTracker = class {
1735
2046
  events = [];
@@ -1770,7 +2081,7 @@ var SessionTracker = class {
1770
2081
  }
1771
2082
  let gitDiff;
1772
2083
  try {
1773
- const raw = execSync2("git diff HEAD", {
2084
+ const raw = execSync("git diff HEAD", {
1774
2085
  cwd: this.ctx.paths.root,
1775
2086
  timeout: 5e3,
1776
2087
  encoding: "utf8",
@@ -1802,7 +2113,7 @@ var SessionTracker = class {
1802
2113
  const triedThreads = this.events.filter((e) => e.tool === "mem_tried").map((e) => e.summary ?? "").filter(Boolean);
1803
2114
  let diffStat;
1804
2115
  try {
1805
- diffStat = execSync2("git diff --stat HEAD", {
2116
+ diffStat = execSync("git diff --stat HEAD", {
1806
2117
  cwd: this.ctx.paths.root,
1807
2118
  timeout: 5e3,
1808
2119
  encoding: "utf8",
@@ -1846,7 +2157,7 @@ var SessionTracker = class {
1846
2157
  ...gitDiff ? { git_diff: gitDiff } : {},
1847
2158
  ...recapId ? { recap_id: recapId } : {}
1848
2159
  };
1849
- const cacheDir = path9.join(this.ctx.paths.haiveDir, ".cache");
2160
+ const cacheDir = path10.join(this.ctx.paths.haiveDir, ".cache");
1850
2161
  await mkdir6(cacheDir, { recursive: true });
1851
2162
  await writeFile12(
1852
2163
  pendingDistillPath(this.ctx),
@@ -1928,12 +2239,12 @@ async function memSessionEnd(input, ctx) {
1928
2239
  const body = buildBody(input);
1929
2240
  const topic = recapTopic(input.scope, input.module);
1930
2241
  const normalizedFiles = input.files_touched.map((p) => {
1931
- if (!p || !path10.isAbsolute(p)) return p;
1932
- const rel = path10.relative(ctx.paths.root, p);
2242
+ if (!p || !path11.isAbsolute(p)) return p;
2243
+ const rel = path11.relative(ctx.paths.root, p);
1933
2244
  return rel.startsWith("..") ? p : rel;
1934
2245
  });
1935
2246
  const invalidPaths = normalizedFiles.filter(
1936
- (p) => !existsSync20(path10.resolve(ctx.paths.root, p))
2247
+ (p) => !existsSync20(path11.resolve(ctx.paths.root, p))
1937
2248
  );
1938
2249
  if (invalidPaths.length > 0) {
1939
2250
  console.warn(`[haive] session end: anchor path(s) not found: ${invalidPaths.join(", ")}`);
@@ -1984,7 +2295,7 @@ async function memSessionEnd(input, ctx) {
1984
2295
  frontmatter.id,
1985
2296
  frontmatter.module
1986
2297
  );
1987
- await mkdir7(path10.dirname(file), { recursive: true });
2298
+ await mkdir7(path11.dirname(file), { recursive: true });
1988
2299
  await writeFile13(file, serializeMemory10({ frontmatter, body }), "utf8");
1989
2300
  await clearPendingDistill(ctx);
1990
2301
  return {
@@ -1999,7 +2310,7 @@ async function memSessionEnd(input, ctx) {
1999
2310
  // src/tools/get-briefing.ts
2000
2311
  import { readFile as readFile7, writeFile as writeFile14, readdir as readdir4 } from "fs/promises";
2001
2312
  import { existsSync as existsSync22 } from "fs";
2002
- import path12 from "path";
2313
+ import path13 from "path";
2003
2314
  import {
2004
2315
  allocateBudget,
2005
2316
  assessBootstrapState,
@@ -2046,7 +2357,7 @@ import { z as z20 } from "zod";
2046
2357
  // src/tools/briefing-helpers.ts
2047
2358
  import { readdir as readdir3, readFile as readFile6 } from "fs/promises";
2048
2359
  import { existsSync as existsSync21 } from "fs";
2049
- import path11 from "path";
2360
+ import path12 from "path";
2050
2361
  import {
2051
2362
  classifyMemoryPriority as coreClassifyPriority,
2052
2363
  isGlobPath,
@@ -2186,7 +2497,7 @@ async function loadModuleContexts2(ctx, modules) {
2186
2497
  const out = [];
2187
2498
  for (const m of modules) {
2188
2499
  if (!available.has(m)) continue;
2189
- const file = path11.join(ctx.paths.modulesContextDir, m, "context.md");
2500
+ const file = path12.join(ctx.paths.modulesContextDir, m, "context.md");
2190
2501
  if (existsSync21(file)) {
2191
2502
  out.push({ name: m, content: await readFile6(file, "utf8") });
2192
2503
  }
@@ -2214,6 +2525,8 @@ var GetBriefingInputSchema = {
2214
2525
  ),
2215
2526
  include_stale: z20.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
2216
2527
  track: z20.boolean().default(true).describe("Increment read_count on returned memories"),
2528
+ memory_scopes: z20.array(z20.enum(["personal", "team", "module", "shared"])).optional().describe("Restrict the candidate corpus to selected scopes. Omit to include every scope."),
2529
+ deterministic: z20.boolean().optional().describe("Ignore machine-local usage/impact signals so repeated evaluations rank the shared corpus reproducibly."),
2217
2530
  format: z20.enum(["full", "compact", "actions"]).default("full").describe(
2218
2531
  "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."
2219
2532
  ),
@@ -2242,9 +2555,11 @@ async function getBriefing(input, ctx) {
2242
2555
  let searchMode = "literal";
2243
2556
  let usage = { version: 1, updated_at: "", by_id: {} };
2244
2557
  let byId = /* @__PURE__ */ new Map();
2558
+ const allowedScopes = input.memory_scopes ? new Set(input.memory_scopes) : null;
2559
+ const scopeAllowed = (loaded) => allowedScopes === null || allowedScopes.has(loaded.memory.frontmatter.scope);
2245
2560
  let lastSession;
2246
2561
  if (existsSync22(ctx.paths.memoriesDir)) {
2247
- const allLoaded = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
2562
+ const allLoaded = (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed);
2248
2563
  const recaps = allLoaded.filter(({ memory }) => memory.frontmatter.type === "session_recap").sort(
2249
2564
  (a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
2250
2565
  );
@@ -2277,10 +2592,11 @@ async function getBriefing(input, ctx) {
2277
2592
  if (memoryHasExcludedTag(memory.frontmatter, excludeTags)) return false;
2278
2593
  return true;
2279
2594
  });
2280
- usage = await loadUsageIndex8(ctx.paths);
2595
+ usage = input.deterministic ? { version: 1, updated_at: "", by_id: {} } : await loadUsageIndex8(ctx.paths);
2281
2596
  byId = new Map(allMemories.map((m) => [m.memory.frontmatter.id, m]));
2282
- const semanticHits = input.task && input.semantic ? await trySemanticHits(ctx, input.task, allMemories.length * 2) : null;
2283
- if (input.task && input.semantic) {
2597
+ const semanticEnabled = input.semantic && input.deterministic !== true;
2598
+ const semanticHits = input.task && semanticEnabled ? await trySemanticHits(ctx, input.task, allMemories.length * 2) : null;
2599
+ if (input.task && semanticEnabled) {
2284
2600
  searchMode = semanticHits ? "semantic" : "literal_fallback";
2285
2601
  }
2286
2602
  const seen = /* @__PURE__ */ new Map();
@@ -2616,7 +2932,7 @@ ${m.content}`).join("\n\n---\n\n"),
2616
2932
  }
2617
2933
  }
2618
2934
  if (existsSync22(ctx.paths.memoriesDir)) {
2619
- const allMems = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
2935
+ const allMems = (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed);
2620
2936
  for (const { memory } of allMems) {
2621
2937
  const fm = memory.frontmatter;
2622
2938
  if (!fm.requires_human_approval) continue;
@@ -2673,7 +2989,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
2673
2989
  pcRaw = await readFile7(ctx.paths.projectContext, "utf8");
2674
2990
  } catch {
2675
2991
  }
2676
- const allForBootstrap = existsSync22(ctx.paths.memoriesDir) ? await loadMemoriesFromDir17(ctx.paths.memoriesDir) : [];
2992
+ const allForBootstrap = existsSync22(ctx.paths.memoriesDir) ? (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed) : [];
2677
2993
  const cmForBootstrap = await loadCodeMap(ctx.paths);
2678
2994
  let existingModules = [];
2679
2995
  try {
@@ -2684,7 +3000,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
2684
3000
  const bootstrap = assessBootstrapState({
2685
3001
  projectContextRaw: pcRaw,
2686
3002
  memories: allForBootstrap,
2687
- codeFiles: cmForBootstrap ? Object.keys(cmForBootstrap.files) : [],
3003
+ codeFiles: cmForBootstrap ? Object.keys(cmForBootstrap.files).filter((file) => existsSync22(path13.join(ctx.paths.root, file))) : [],
2688
3004
  existingModules
2689
3005
  });
2690
3006
  if (bootstrap.state !== "ready" && bootstrap.metrics.mainAreas > 0) {
@@ -2807,7 +3123,7 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
2807
3123
  };
2808
3124
  }
2809
3125
  async function detectRunCommands(root) {
2810
- const pkgPath = path12.join(root, "package.json");
3126
+ const pkgPath = path13.join(root, "package.json");
2811
3127
  if (!existsSync22(pkgPath)) return null;
2812
3128
  try {
2813
3129
  const pkg = JSON.parse(await readFile7(pkgPath, "utf8"));
@@ -2875,7 +3191,7 @@ function oneLine(value) {
2875
3191
  return value.replace(/\s+/g, " ").replace(/"/g, '\\"').trim().slice(0, 120);
2876
3192
  }
2877
3193
  function serverVersion() {
2878
- return true ? "0.39.2" : "dev";
3194
+ return true ? "0.43.2" : "dev";
2879
3195
  }
2880
3196
 
2881
3197
  // src/tools/code-map.ts
@@ -4048,6 +4364,7 @@ ${template}\`\`\`
4048
4364
  // src/prompts/bootstrap-repo.ts
4049
4365
  import { readFile as readFile8, readdir as readdir5 } from "fs/promises";
4050
4366
  import { existsSync as existsSync29 } from "fs";
4367
+ import path14 from "path";
4051
4368
  import {
4052
4369
  assessBootstrapState as assessBootstrapState2,
4053
4370
  loadCodeMap as loadCodeMap4,
@@ -4075,7 +4392,7 @@ async function currentAssessment(ctx) {
4075
4392
  return assessBootstrapState2({
4076
4393
  projectContextRaw,
4077
4394
  memories,
4078
- codeFiles: codeMap ? Object.keys(codeMap.files) : [],
4395
+ codeFiles: codeMap ? Object.keys(codeMap.files).filter((file) => existsSync29(path14.join(ctx.paths.root, file))) : [],
4079
4396
  existingModules
4080
4397
  });
4081
4398
  }
@@ -4303,7 +4620,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
4303
4620
  // src/server.ts
4304
4621
  import { hasRecentBriefingMarker, loadConfigSync } from "@hivelore/core";
4305
4622
  var SERVER_NAME = "hivelore";
4306
- var SERVER_VERSION = "0.39.2";
4623
+ var SERVER_VERSION = "0.43.2";
4307
4624
  function jsonResult(data) {
4308
4625
  return {
4309
4626
  content: [