@hivelore/cli 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.
@@ -125,7 +125,7 @@ import {
125
125
  import { z as z14 } from "zod";
126
126
  import { mkdir as mkdir3, writeFile as writeFile9 } from "fs/promises";
127
127
  import { existsSync as existsSync16 } from "fs";
128
- import path6 from "path";
128
+ import path7 from "path";
129
129
  import {
130
130
  buildFrontmatter as buildFrontmatter2,
131
131
  memoryFilePath as memoryFilePath2,
@@ -133,22 +133,26 @@ import {
133
133
  suggestSensorSeed as suggestSensorSeed2
134
134
  } from "@hivelore/core";
135
135
  import { z as z16 } from "zod";
136
- import { execSync } from "child_process";
136
+ import { execFileSync } from "child_process";
137
137
  import { readFile as readFile3, writeFile as writeFile8 } from "fs/promises";
138
- import { existsSync as existsSync15 } from "fs";
139
- import path5 from "path";
138
+ import { existsSync as existsSync15, rmSync, symlinkSync } from "fs";
139
+ import os from "os";
140
+ import path6 from "path";
140
141
  import {
141
142
  extractSensorExamples,
142
143
  extractTestFilePathsFromCommand,
143
144
  hasPendingTestMarker,
144
145
  judgeProposedSensor,
145
146
  loadMemoriesFromDir as loadMemoriesFromDir13,
147
+ scrubbedCommandEnv,
148
+ sensorPatternBrittleness,
146
149
  serializeMemory as serializeMemory7
147
150
  } from "@hivelore/core";
148
151
  import { z as z15 } from "zod";
152
+ import path5 from "path";
149
153
  import { existsSync as existsSync17, statSync } from "fs";
150
154
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile10 } from "fs/promises";
151
- import path7 from "path";
155
+ import path8 from "path";
152
156
  import { z as z17 } from "zod";
153
157
  import {
154
158
  buildProposeCommand,
@@ -160,7 +164,7 @@ import {
160
164
  } from "@hivelore/core";
161
165
  import { existsSync as existsSync18 } from "fs";
162
166
  import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile11 } from "fs/promises";
163
- import path8 from "path";
167
+ import path9 from "path";
164
168
  import {
165
169
  draftsFromFindings,
166
170
  filterNewDrafts,
@@ -172,7 +176,7 @@ import {
172
176
  import { z as z18 } from "zod";
173
177
  import { writeFile as writeFile13, mkdir as mkdir7 } from "fs/promises";
174
178
  import { existsSync as existsSync20 } from "fs";
175
- import path10 from "path";
179
+ import path11 from "path";
176
180
  import {
177
181
  buildFrontmatter as buildFrontmatter3,
178
182
  loadMemoriesFromDir as loadMemoriesFromDir16,
@@ -188,11 +192,11 @@ import {
188
192
  } from "@hivelore/core";
189
193
  import { mkdir as mkdir6, writeFile as writeFile12, rm } from "fs/promises";
190
194
  import { existsSync as existsSync19 } from "fs";
191
- import path9 from "path";
192
- import { execSync as execSync2 } from "child_process";
195
+ import path10 from "path";
196
+ import { execSync } from "child_process";
193
197
  import { readFile as readFile7, writeFile as writeFile14, readdir as readdir4 } from "fs/promises";
194
198
  import { existsSync as existsSync22 } from "fs";
195
- import path12 from "path";
199
+ import path13 from "path";
196
200
  import {
197
201
  allocateBudget,
198
202
  assessBootstrapState,
@@ -237,7 +241,7 @@ import {
237
241
  import { z as z20 } from "zod";
238
242
  import { readdir as readdir3, readFile as readFile6 } from "fs/promises";
239
243
  import { existsSync as existsSync21 } from "fs";
240
- import path11 from "path";
244
+ import path12 from "path";
241
245
  import {
242
246
  classifyMemoryPriority as coreClassifyPriority,
243
247
  isGlobPath,
@@ -301,6 +305,7 @@ import { z as z32 } from "zod";
301
305
  import { z as z33 } from "zod";
302
306
  import { readFile as readFile8, readdir as readdir5 } from "fs/promises";
303
307
  import { existsSync as existsSync29 } from "fs";
308
+ import path14 from "path";
304
309
  import {
305
310
  assessBootstrapState as assessBootstrapState2,
306
311
  loadCodeMap as loadCodeMap4,
@@ -1278,12 +1283,125 @@ async function memApprove(input, ctx) {
1278
1283
  file_path: found.filePath
1279
1284
  };
1280
1285
  }
1286
+ var cachedEngine;
1287
+ var registeredDynamicLanguages = /* @__PURE__ */ new Set();
1288
+ async function loadDynamicLanguages(engine) {
1289
+ const registrations = {};
1290
+ const candidates = [
1291
+ ["python", () => import("@ast-grep/lang-python")],
1292
+ ["go", () => import("@ast-grep/lang-go")],
1293
+ ["rust", () => import("@ast-grep/lang-rust")],
1294
+ ["java", () => import("@ast-grep/lang-java")]
1295
+ ];
1296
+ for (const [name, load] of candidates) {
1297
+ try {
1298
+ const mod = await load();
1299
+ registrations[name] = mod.default;
1300
+ registeredDynamicLanguages.add(name);
1301
+ } catch {
1302
+ }
1303
+ }
1304
+ if (Object.keys(registrations).length > 0) {
1305
+ engine.registerDynamicLanguage(registrations);
1306
+ }
1307
+ }
1308
+ async function loadAstEngine() {
1309
+ if (cachedEngine !== void 0) return cachedEngine;
1310
+ try {
1311
+ cachedEngine = await import("@ast-grep/napi");
1312
+ await loadDynamicLanguages(cachedEngine);
1313
+ } catch {
1314
+ cachedEngine = null;
1315
+ }
1316
+ return cachedEngine;
1317
+ }
1318
+ async function astEngineAvailable() {
1319
+ return await loadAstEngine() !== null;
1320
+ }
1321
+ function astLangForPath(filePath, explicitLanguage) {
1322
+ if (explicitLanguage) {
1323
+ const normalized = explicitLanguage.toLowerCase();
1324
+ if (["typescript", "tsx", "javascript", "html", "css"].includes(normalized)) {
1325
+ return normalized === "typescript" ? "TypeScript" : normalized === "tsx" ? "Tsx" : normalized === "javascript" ? "JavaScript" : normalized[0].toUpperCase() + normalized.slice(1);
1326
+ }
1327
+ return registeredDynamicLanguages.has(normalized) ? normalized : null;
1328
+ }
1329
+ const ext = path5.extname(filePath).toLowerCase();
1330
+ if (ext === ".ts" || ext === ".mts" || ext === ".cts") return "TypeScript";
1331
+ if (ext === ".tsx") return "Tsx";
1332
+ if (ext === ".js" || ext === ".jsx" || ext === ".mjs" || ext === ".cjs") return "JavaScript";
1333
+ if (ext === ".py" && registeredDynamicLanguages.has("python")) return "python";
1334
+ if (ext === ".go" && registeredDynamicLanguages.has("go")) return "go";
1335
+ if (ext === ".rs" && registeredDynamicLanguages.has("rust")) return "rust";
1336
+ if (ext === ".java" && registeredDynamicLanguages.has("java")) return "java";
1337
+ return null;
1338
+ }
1339
+ function absentPresentInNode(node, absent) {
1340
+ try {
1341
+ if (node.find(absent)) return true;
1342
+ } catch {
1343
+ }
1344
+ const text = node.text();
1345
+ try {
1346
+ return new RegExp(absent).test(text);
1347
+ } catch {
1348
+ return text.includes(absent);
1349
+ }
1350
+ }
1351
+ async function runAstPattern(content, filePath, pattern, absent, rule, language) {
1352
+ const engine = await loadAstEngine();
1353
+ if (!engine) return { status: "engine-missing", matches: [] };
1354
+ const langName = astLangForPath(filePath, language);
1355
+ if (!langName) return { status: "unsupported-language", matches: [] };
1356
+ const lang = langName in engine.Lang ? engine.Lang[langName] : langName;
1357
+ let root;
1358
+ try {
1359
+ root = engine.parse(lang, content).root();
1360
+ } catch (err) {
1361
+ return { status: "parse-error", matches: [], detail: String(err).slice(0, 200) };
1362
+ }
1363
+ let nodes;
1364
+ try {
1365
+ const matcher = rule ? { rule: pattern ? { all: [{ pattern }, rule] } : rule } : pattern;
1366
+ if (!matcher) return { status: "invalid-pattern", matches: [], detail: "missing pattern/rule" };
1367
+ nodes = root.findAll(matcher);
1368
+ } catch (err) {
1369
+ return { status: "invalid-pattern", matches: [], detail: String(err).slice(0, 200) };
1370
+ }
1371
+ const matches = [];
1372
+ for (const node of nodes) {
1373
+ if (absent && absentPresentInNode(node, absent)) continue;
1374
+ const range = node.range();
1375
+ matches.push({
1376
+ startLine: range.start.line + 1,
1377
+ endLine: range.end.line + 1,
1378
+ text: node.text().trim().slice(0, 200)
1379
+ });
1380
+ }
1381
+ return { status: "ok", matches };
1382
+ }
1383
+ async function runAstSensorOnContent(input) {
1384
+ const scan = await runAstPattern(input.content, input.filePath, input.pattern, input.absent, input.rule, input.language);
1385
+ if (scan.status !== "ok" || !input.addedLines || input.addedLines.size === 0) return scan;
1386
+ const added = input.addedLines;
1387
+ return {
1388
+ status: "ok",
1389
+ matches: scan.matches.filter((m) => {
1390
+ for (let line = m.startLine; line <= m.endLine; line++) {
1391
+ if (added.has(line)) return true;
1392
+ }
1393
+ return false;
1394
+ })
1395
+ };
1396
+ }
1281
1397
  var ProposeSensorInputSchema = {
1282
1398
  memory_id: z15.string().min(1).describe("Id of the gotcha/attempt memory this sensor protects."),
1283
- kind: z15.enum(["regex", "shell", "test"]).default("regex").describe(
1284
- "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."
1399
+ kind: z15.enum(["regex", "ast", "shell", "test"]).default("regex").describe(
1400
+ "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."
1285
1401
  ),
1286
- pattern: z15.string().optional().describe("kind=regex: regex matching the FAULTY usage (the risky call/token), e.g. 'stripe\\.paymentIntents\\.create'."),
1402
+ pattern: z15.string().optional().describe("kind=regex: regex matching the faulty usage; kind=ast: optional structural pattern (may be combined with `rule`)."),
1403
+ 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."),
1404
+ language: z15.string().optional().describe("kind=ast: explicit built-in/dynamic language name for non-standard file extensions."),
1287
1405
  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."),
1288
1406
  timeout_ms: z15.number().int().positive().optional().describe("kind=shell|test: max runtime before the executor kills the command (default 120000)."),
1289
1407
  absent: z15.string().optional().describe(
@@ -1295,6 +1413,9 @@ var ProposeSensorInputSchema = {
1295
1413
  incident: z15.string().optional().describe(
1296
1414
  "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."
1297
1415
  ),
1416
+ red_ref: z15.string().optional().describe(
1417
+ "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."
1418
+ ),
1298
1419
  flags: z15.string().optional().describe("Optional regex flags (e.g. 'i' for case-insensitive)."),
1299
1420
  paths: z15.array(z15.string()).default([]).describe("Override scope paths. Defaults to the memory's anchor paths.")
1300
1421
  };
@@ -1311,7 +1432,7 @@ async function readPresumedCorrectTargets(root, relPaths) {
1311
1432
  const targets = [];
1312
1433
  for (const rel of relPaths) {
1313
1434
  try {
1314
- const content = execSync(`git show ${JSON.stringify(`HEAD:./${rel}`)}`, {
1435
+ const content = execFileSync("git", ["show", `HEAD:./${rel}`], {
1315
1436
  cwd: root,
1316
1437
  encoding: "utf8",
1317
1438
  maxBuffer: 10 * 1024 * 1024,
@@ -1321,7 +1442,7 @@ async function readPresumedCorrectTargets(root, relPaths) {
1321
1442
  continue;
1322
1443
  } catch {
1323
1444
  }
1324
- const abs = path5.resolve(root, rel);
1445
+ const abs = path6.resolve(root, rel);
1325
1446
  if (!existsSync15(abs)) continue;
1326
1447
  try {
1327
1448
  targets.push({ path: rel, content: await readFile3(abs, "utf8") });
@@ -1332,11 +1453,13 @@ async function readPresumedCorrectTargets(root, relPaths) {
1332
1453
  }
1333
1454
  function runCommandForValidation(command, root, timeoutMs = 12e4) {
1334
1455
  try {
1335
- execSync(`bash -c ${JSON.stringify(command)}`, {
1456
+ execFileSync("bash", ["-c", command], {
1336
1457
  cwd: root,
1337
1458
  timeout: timeoutMs,
1338
1459
  maxBuffer: 8 * 1024 * 1024,
1339
- stdio: ["ignore", "pipe", "pipe"]
1460
+ stdio: ["ignore", "pipe", "pipe"],
1461
+ // Same containment as the gate executor: an oracle gets a test-runner env, not credentials.
1462
+ env: { ...scrubbedCommandEnv(process.env), HIVELORE_SENSOR: "validation" }
1340
1463
  });
1341
1464
  return { status: "passed", detail: "exit 0" };
1342
1465
  } catch (err) {
@@ -1350,6 +1473,48 @@ ${e.stderr?.toString() ?? ""}`.split("\n").filter(Boolean).slice(-8).join("\n");
1350
1473
  return { status: "failed", detail: out || `exit ${e.status ?? "?"}` };
1351
1474
  }
1352
1475
  }
1476
+ function proveRedOnIncident(command, root, redRef, timeoutMs) {
1477
+ const worktree = path6.join(os.tmpdir(), `hivelore-red-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
1478
+ let added = false;
1479
+ try {
1480
+ try {
1481
+ execFileSync("git", ["worktree", "add", "--detach", worktree, redRef], {
1482
+ cwd: root,
1483
+ stdio: ["ignore", "pipe", "pipe"],
1484
+ timeout: 6e4
1485
+ });
1486
+ added = true;
1487
+ } catch (err) {
1488
+ const e = err;
1489
+ return { proven: false, reason: "red-ref-invalid", detail: (e.stderr?.toString() ?? String(err)).slice(0, 300) };
1490
+ }
1491
+ const mainModules = path6.join(root, "node_modules");
1492
+ const wtModules = path6.join(worktree, "node_modules");
1493
+ if (existsSync15(mainModules) && !existsSync15(wtModules)) {
1494
+ try {
1495
+ symlinkSync(mainModules, wtModules, "dir");
1496
+ } catch {
1497
+ }
1498
+ }
1499
+ const run = runCommandForValidation(command, worktree, timeoutMs);
1500
+ if (run.status === "failed") return { proven: true, detail: run.detail };
1501
+ if (run.status === "passed") {
1502
+ return { proven: false, reason: "red-not-proven", detail: "oracle PASSED on the incident state \u2014 it does not catch the incident" };
1503
+ }
1504
+ return { proven: false, reason: "red-unrunnable", detail: run.detail };
1505
+ } finally {
1506
+ if (added) {
1507
+ try {
1508
+ execFileSync("git", ["worktree", "remove", "--force", worktree], { cwd: root, stdio: "ignore", timeout: 6e4 });
1509
+ } catch {
1510
+ try {
1511
+ rmSync(worktree, { recursive: true, force: true });
1512
+ } catch {
1513
+ }
1514
+ }
1515
+ }
1516
+ }
1517
+ }
1353
1518
  async function proposeSensor(input, ctx) {
1354
1519
  if (!existsSync15(ctx.paths.memoriesDir)) {
1355
1520
  throw new Error(`No .ai/memories at ${ctx.paths.root}. Run 'hivelore init' first.`);
@@ -1379,6 +1544,17 @@ async function proposeSensor(input, ctx) {
1379
1544
  self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1380
1545
  };
1381
1546
  }
1547
+ } else if (kind === "ast") {
1548
+ if (!input.pattern?.trim() && !input.rule) {
1549
+ return {
1550
+ accepted: false,
1551
+ memory_id: input.memory_id,
1552
+ severity: input.severity,
1553
+ reason: "invalid-pattern",
1554
+ guidance: "kind=ast requires a structural `pattern` or a full ast-grep `rule` object.",
1555
+ self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1556
+ };
1557
+ }
1382
1558
  } else if (!input.command?.trim()) {
1383
1559
  return {
1384
1560
  accepted: false,
@@ -1395,12 +1571,111 @@ async function proposeSensor(input, ctx) {
1395
1571
  throw new Error(`No memory found with id ${input.memory_id}`);
1396
1572
  }
1397
1573
  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}.` : "";
1398
- if (kind !== "regex") {
1399
- const referencedTests = extractTestFilePathsFromCommand(input.command.trim()).filter((rel) => existsSync15(path5.resolve(ctx.paths.root, rel)));
1574
+ if (kind === "ast") {
1575
+ const pattern = input.pattern?.trim();
1576
+ if (!await astEngineAvailable() && input.severity === "block") {
1577
+ return {
1578
+ accepted: false,
1579
+ memory_id: input.memory_id,
1580
+ severity: input.severity,
1581
+ reason: "ast-engine-missing",
1582
+ 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.",
1583
+ self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1584
+ };
1585
+ }
1586
+ const brittleAst = pattern ? sensorPatternBrittleness(pattern) : null;
1587
+ if (brittleAst && input.severity === "block") {
1588
+ return {
1589
+ accepted: false,
1590
+ memory_id: input.memory_id,
1591
+ severity: input.severity,
1592
+ reason: "brittle",
1593
+ guidance: `The pattern is brittle (${brittleAst}). Use a durable structural pattern, then re-propose.`,
1594
+ self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1595
+ };
1596
+ }
1597
+ const anchorPathsAst = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
1598
+ const currentTargetsAst = await readPresumedCorrectTargets(ctx.paths.root, anchorPathsAst);
1599
+ const firedOnAst = [];
1600
+ for (const target of currentTargetsAst) {
1601
+ const scan = await runAstSensorOnContent({ pattern, rule: input.rule, language: input.language, absent: input.absent, content: target.content, filePath: target.path });
1602
+ if (scan.status === "invalid-pattern") {
1603
+ return {
1604
+ accepted: false,
1605
+ memory_id: input.memory_id,
1606
+ severity: input.severity,
1607
+ reason: "invalid-pattern",
1608
+ guidance: `The ast-grep pattern is invalid: ${scan.detail ?? "unparseable"}. Fix it and re-propose.`,
1609
+ self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1610
+ };
1611
+ }
1612
+ if (scan.status === "ok" && scan.matches.length > 0) firedOnAst.push(target.path);
1613
+ }
1614
+ if (firedOnAst.length > 0 && input.severity === "block") {
1615
+ return {
1616
+ accepted: false,
1617
+ memory_id: input.memory_id,
1618
+ severity: input.severity,
1619
+ reason: "fires-on-current",
1620
+ 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.`,
1621
+ self_check: { silent_on_current: false, fires_on_bad: null, fired_on: firedOnAst }
1622
+ };
1623
+ }
1624
+ const badExamplesAst = [
1625
+ ...input.bad_example ? [input.bad_example] : [],
1626
+ ...extractSensorExamples(found.memory.body)
1627
+ ];
1628
+ let firesOnBadAst = null;
1629
+ if (badExamplesAst.length > 0 && await astEngineAvailable()) {
1630
+ const exampleLang = anchorPathsAst.find((p) => astLangForPath(p, input.language) !== null) ?? "example.tsx";
1631
+ firesOnBadAst = false;
1632
+ for (const example of badExamplesAst) {
1633
+ const scan = await runAstSensorOnContent({ pattern, rule: input.rule, language: input.language, absent: input.absent, content: example, filePath: exampleLang });
1634
+ if (scan.status === "ok" && scan.matches.length > 0) {
1635
+ firesOnBadAst = true;
1636
+ break;
1637
+ }
1638
+ }
1639
+ }
1640
+ if (firesOnBadAst === false && input.severity === "block") {
1641
+ return {
1642
+ accepted: false,
1643
+ memory_id: input.memory_id,
1644
+ severity: input.severity,
1645
+ reason: "missed-bad-example",
1646
+ guidance: "The pattern did not match the bad example structurally, so it won't catch the mistake. Adjust it, then re-propose.",
1647
+ self_check: { silent_on_current: firedOnAst.length === 0, fires_on_bad: false, fired_on: [] }
1648
+ };
1649
+ }
1650
+ const sensorAst = {
1651
+ kind: "ast",
1652
+ ...pattern ? { pattern } : {},
1653
+ ...input.rule ? { rule: input.rule } : {},
1654
+ ...input.language ? { language: input.language } : {},
1655
+ ...input.absent ? { absent: input.absent } : {},
1656
+ paths: anchorPathsAst,
1657
+ message: input.message?.trim() || deriveMessage(found.memory.body, pattern ?? "AST rule", input.absent),
1658
+ ...input.incident?.trim() ? { incident: input.incident.trim() } : {},
1659
+ severity: input.severity,
1660
+ autogen: false,
1661
+ last_fired: null
1662
+ };
1663
+ await writeFile8(found.filePath, serializeMemory7({ frontmatter: { ...found.memory.frontmatter, sensor: sensorAst }, body: found.memory.body }), "utf8");
1664
+ return {
1665
+ accepted: true,
1666
+ memory_id: input.memory_id,
1667
+ severity: input.severity,
1668
+ 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,
1669
+ self_check: { silent_on_current: firedOnAst.length === 0, fires_on_bad: firesOnBadAst, fired_on: firedOnAst },
1670
+ file_path: found.filePath
1671
+ };
1672
+ }
1673
+ if (kind === "shell" || kind === "test") {
1674
+ const referencedTests = extractTestFilePathsFromCommand(input.command.trim()).filter((rel) => existsSync15(path6.resolve(ctx.paths.root, rel)));
1400
1675
  const pendingTests = [];
1401
1676
  for (const rel of referencedTests) {
1402
1677
  try {
1403
- if (hasPendingTestMarker(await readFile3(path5.resolve(ctx.paths.root, rel), "utf8"))) pendingTests.push(rel);
1678
+ if (hasPendingTestMarker(await readFile3(path6.resolve(ctx.paths.root, rel), "utf8"))) pendingTests.push(rel);
1404
1679
  } catch {
1405
1680
  }
1406
1681
  }
@@ -1416,6 +1691,21 @@ async function proposeSensor(input, ctx) {
1416
1691
  }
1417
1692
  const verdictCmd = runCommandForValidation(input.command.trim(), ctx.paths.root, input.timeout_ms);
1418
1693
  const anchorPathsCmd = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
1694
+ let redProven = false;
1695
+ if (input.red_ref?.trim()) {
1696
+ const red = proveRedOnIncident(input.command.trim(), ctx.paths.root, input.red_ref.trim(), input.timeout_ms);
1697
+ if (!red.proven && input.severity === "block") {
1698
+ return {
1699
+ accepted: false,
1700
+ memory_id: input.memory_id,
1701
+ severity: input.severity,
1702
+ reason: red.reason ?? "red-not-proven",
1703
+ 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),
1704
+ self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: false, fired_on: [] }
1705
+ };
1706
+ }
1707
+ redProven = red.proven;
1708
+ }
1419
1709
  if (verdictCmd.status !== "passed" && input.severity === "block") {
1420
1710
  return {
1421
1711
  accepted: false,
@@ -1427,6 +1717,16 @@ ${verdictCmd.detail}`,
1427
1717
  self_check: { silent_on_current: false, fires_on_bad: null, fired_on: anchorPathsCmd }
1428
1718
  };
1429
1719
  }
1720
+ if (input.severity === "block" && !input.red_ref?.trim()) {
1721
+ return {
1722
+ accepted: false,
1723
+ memory_id: input.memory_id,
1724
+ severity: input.severity,
1725
+ reason: "red-required",
1726
+ 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.",
1727
+ self_check: { silent_on_current: true, fires_on_bad: null, fired_on: [] }
1728
+ };
1729
+ }
1430
1730
  const sensorCmd = {
1431
1731
  kind,
1432
1732
  command: input.command.trim(),
@@ -1434,6 +1734,7 @@ ${verdictCmd.detail}`,
1434
1734
  paths: anchorPathsCmd,
1435
1735
  message: input.message?.trim() || deriveMessage(found.memory.body, input.command.trim(), void 0),
1436
1736
  ...input.incident?.trim() ? { incident: input.incident.trim() } : {},
1737
+ ...redProven ? { red_proven: true } : {},
1437
1738
  severity: input.severity,
1438
1739
  autogen: false,
1439
1740
  last_fired: null
@@ -1447,8 +1748,9 @@ ${verdictCmd.detail}`,
1447
1748
  accepted: true,
1448
1749
  memory_id: input.memory_id,
1449
1750
  severity: input.severity,
1450
- 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,
1451
- self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: null, fired_on: [] }
1751
+ 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,
1752
+ self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: redProven ? true : null, fired_on: [] },
1753
+ file_path: found.filePath
1452
1754
  };
1453
1755
  }
1454
1756
  const anchorPaths = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
@@ -1513,14 +1815,17 @@ var MemTriedInputSchema = {
1513
1815
  paths: z16.array(z16.string()).default([]).describe("Anchor file paths this applies to"),
1514
1816
  author: z16.string().optional().describe("Author handle or email"),
1515
1817
  sensor: z16.object({
1516
- kind: z16.enum(["regex", "shell", "test"]).default("regex").describe("regex pattern, or a shell/test COMMAND the gate executes (behaviour bridge)"),
1517
- pattern: z16.string().optional().describe("kind=regex: regex matching the FAULTY usage (added diff lines)"),
1818
+ kind: z16.enum(["regex", "ast", "shell", "test"]).default("regex").describe("regex/AST pattern, or a shell/test command"),
1819
+ pattern: z16.string().optional().describe("kind=regex|ast: pattern matching the faulty usage"),
1820
+ rule: z16.record(z16.unknown()).optional().describe("kind=ast: full ast-grep Rule object"),
1821
+ language: z16.string().optional().describe("kind=ast: explicit built-in/dynamic language"),
1518
1822
  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)"),
1519
1823
  timeout_ms: z16.number().int().positive().optional().describe("kind=shell|test: max runtime (default 120000)"),
1520
1824
  absent: z16.string().optional().describe("kind=regex: regex marking CORRECT usage nearby \u2014 excludes it from firing"),
1521
1825
  severity: z16.enum(["warn", "block"]).default("block").describe("block = deterministic gate refusal"),
1522
1826
  message: z16.string().optional().describe("Self-correction message shown when the sensor fires"),
1523
1827
  incident: z16.string().optional().describe("Provenance: the incident this sensor guards (e.g. 'prod #442') \u2014 surfaced when it fires and in the receipt"),
1828
+ 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)"),
1524
1829
  bad_example: z16.string().optional().describe("kind=regex: code snippet the sensor MUST fire on (validation)")
1525
1830
  }).optional().describe(
1526
1831
  "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."
@@ -1552,7 +1857,7 @@ async function memTried(input, ctx) {
1552
1857
  }
1553
1858
  const body = lines.join("\n") + "\n";
1554
1859
  const file = memoryFilePath2(ctx.paths, frontmatter.scope, frontmatter.id, frontmatter.module);
1555
- await mkdir3(path6.dirname(file), { recursive: true });
1860
+ await mkdir3(path7.dirname(file), { recursive: true });
1556
1861
  if (existsSync16(file)) {
1557
1862
  throw new Error(`Memory already exists at ${file}`);
1558
1863
  }
@@ -1563,12 +1868,15 @@ async function memTried(input, ctx) {
1563
1868
  memory_id: frontmatter.id,
1564
1869
  kind: input.sensor.kind ?? "regex",
1565
1870
  pattern: input.sensor.pattern,
1871
+ rule: input.sensor.rule,
1872
+ language: input.sensor.language,
1566
1873
  command: input.sensor.command,
1567
1874
  timeout_ms: input.sensor.timeout_ms,
1568
1875
  absent: input.sensor.absent,
1569
1876
  severity: input.sensor.severity ?? "block",
1570
1877
  message: input.sensor.message,
1571
1878
  incident: input.sensor.incident,
1879
+ red_ref: input.sensor.red_ref,
1572
1880
  bad_example: input.sensor.bad_example,
1573
1881
  flags: void 0,
1574
1882
  paths: []
@@ -1608,17 +1916,17 @@ async function memTried(input, ctx) {
1608
1916
  }
1609
1917
  var PY_SIGNALS = ["pyproject.toml", "setup.py", "pytest.ini", "requirements.txt", "tox.ini"];
1610
1918
  async function detectForAnchor(root, rel) {
1611
- let dir = path7.resolve(root, rel);
1919
+ let dir = path8.resolve(root, rel);
1612
1920
  try {
1613
- if (!statSync(dir).isDirectory()) dir = path7.dirname(dir);
1921
+ if (!statSync(dir).isDirectory()) dir = path8.dirname(dir);
1614
1922
  } catch {
1615
- if (path7.extname(dir)) dir = path7.dirname(dir);
1923
+ if (path8.extname(dir)) dir = path8.dirname(dir);
1616
1924
  }
1617
1925
  while (dir.startsWith(root)) {
1618
- const pkgJson = path7.join(dir, "package.json");
1926
+ const pkgJson = path8.join(dir, "package.json");
1619
1927
  const hasPkg = existsSync17(pkgJson);
1620
- const goMod = existsSync17(path7.join(dir, "go.mod"));
1621
- const pySignal = PY_SIGNALS.some((s) => existsSync17(path7.join(dir, s)));
1928
+ const goMod = existsSync17(path8.join(dir, "go.mod"));
1929
+ const pySignal = PY_SIGNALS.some((s) => existsSync17(path8.join(dir, s)));
1622
1930
  if (hasPkg || goMod || pySignal) {
1623
1931
  let pkg = null;
1624
1932
  if (hasPkg) {
@@ -1628,10 +1936,10 @@ async function detectForAnchor(root, rel) {
1628
1936
  pkg = null;
1629
1937
  }
1630
1938
  }
1631
- const baseDir = path7.relative(root, dir).split(path7.sep).join("/");
1939
+ const baseDir = path8.relative(root, dir).split(path8.sep).join("/");
1632
1940
  return { framework: pickTestFramework(pkg, { goMod, pySignal }), baseDir };
1633
1941
  }
1634
- const parent = path7.dirname(dir);
1942
+ const parent = path8.dirname(dir);
1635
1943
  if (parent === dir || dir === root) break;
1636
1944
  dir = parent;
1637
1945
  }
@@ -1697,14 +2005,14 @@ async function scaffoldTest(input, ctx) {
1697
2005
  }
1698
2006
  const results = [];
1699
2007
  for (const scaffold of scaffolds) {
1700
- const abs = path7.isAbsolute(scaffold.relPath) ? scaffold.relPath : path7.resolve(ctx.paths.root, scaffold.relPath);
2008
+ const abs = path8.isAbsolute(scaffold.relPath) ? scaffold.relPath : path8.resolve(ctx.paths.root, scaffold.relPath);
1701
2009
  let written = false;
1702
2010
  let alreadyExists = false;
1703
2011
  if (input.write) {
1704
2012
  if (existsSync17(abs)) {
1705
2013
  alreadyExists = true;
1706
2014
  } else {
1707
- await mkdir4(path7.dirname(abs), { recursive: true });
2015
+ await mkdir4(path8.dirname(abs), { recursive: true });
1708
2016
  await writeFile10(abs, scaffold.content, "utf8");
1709
2017
  written = true;
1710
2018
  }
@@ -1755,7 +2063,7 @@ async function ingestFindings(input, ctx) {
1755
2063
  if (input.report && input.report.trim()) {
1756
2064
  raw = input.report;
1757
2065
  } else if (input.report_path) {
1758
- const file = path8.resolve(ctx.paths.root, input.report_path);
2066
+ const file = path9.resolve(ctx.paths.root, input.report_path);
1759
2067
  if (!existsSync18(file)) throw new Error(`Report file not found: ${file}`);
1760
2068
  raw = await readFile5(file, "utf8");
1761
2069
  } else {
@@ -1809,12 +2117,12 @@ async function writeDraft(ctx, draft) {
1809
2117
  draft.frontmatter.id,
1810
2118
  draft.frontmatter.module
1811
2119
  );
1812
- await mkdir5(path8.dirname(file), { recursive: true });
2120
+ await mkdir5(path9.dirname(file), { recursive: true });
1813
2121
  await writeFile11(file, serializeMemory9({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
1814
2122
  return file;
1815
2123
  }
1816
2124
  function pendingDistillPath(ctx) {
1817
- return path9.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
2125
+ return path10.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
1818
2126
  }
1819
2127
  var SessionTracker = class {
1820
2128
  events = [];
@@ -1855,7 +2163,7 @@ var SessionTracker = class {
1855
2163
  }
1856
2164
  let gitDiff;
1857
2165
  try {
1858
- const raw = execSync2("git diff HEAD", {
2166
+ const raw = execSync("git diff HEAD", {
1859
2167
  cwd: this.ctx.paths.root,
1860
2168
  timeout: 5e3,
1861
2169
  encoding: "utf8",
@@ -1887,7 +2195,7 @@ var SessionTracker = class {
1887
2195
  const triedThreads = this.events.filter((e) => e.tool === "mem_tried").map((e) => e.summary ?? "").filter(Boolean);
1888
2196
  let diffStat;
1889
2197
  try {
1890
- diffStat = execSync2("git diff --stat HEAD", {
2198
+ diffStat = execSync("git diff --stat HEAD", {
1891
2199
  cwd: this.ctx.paths.root,
1892
2200
  timeout: 5e3,
1893
2201
  encoding: "utf8",
@@ -1931,7 +2239,7 @@ var SessionTracker = class {
1931
2239
  ...gitDiff ? { git_diff: gitDiff } : {},
1932
2240
  ...recapId ? { recap_id: recapId } : {}
1933
2241
  };
1934
- const cacheDir = path9.join(this.ctx.paths.haiveDir, ".cache");
2242
+ const cacheDir = path10.join(this.ctx.paths.haiveDir, ".cache");
1935
2243
  await mkdir6(cacheDir, { recursive: true });
1936
2244
  await writeFile12(
1937
2245
  pendingDistillPath(this.ctx),
@@ -2011,12 +2319,12 @@ async function memSessionEnd(input, ctx) {
2011
2319
  const body = buildBody(input);
2012
2320
  const topic = recapTopic(input.scope, input.module);
2013
2321
  const normalizedFiles = input.files_touched.map((p) => {
2014
- if (!p || !path10.isAbsolute(p)) return p;
2015
- const rel = path10.relative(ctx.paths.root, p);
2322
+ if (!p || !path11.isAbsolute(p)) return p;
2323
+ const rel = path11.relative(ctx.paths.root, p);
2016
2324
  return rel.startsWith("..") ? p : rel;
2017
2325
  });
2018
2326
  const invalidPaths = normalizedFiles.filter(
2019
- (p) => !existsSync20(path10.resolve(ctx.paths.root, p))
2327
+ (p) => !existsSync20(path11.resolve(ctx.paths.root, p))
2020
2328
  );
2021
2329
  if (invalidPaths.length > 0) {
2022
2330
  console.warn(`[haive] session end: anchor path(s) not found: ${invalidPaths.join(", ")}`);
@@ -2067,7 +2375,7 @@ async function memSessionEnd(input, ctx) {
2067
2375
  frontmatter.id,
2068
2376
  frontmatter.module
2069
2377
  );
2070
- await mkdir7(path10.dirname(file), { recursive: true });
2378
+ await mkdir7(path11.dirname(file), { recursive: true });
2071
2379
  await writeFile13(file, serializeMemory10({ frontmatter, body }), "utf8");
2072
2380
  await clearPendingDistill(ctx);
2073
2381
  return {
@@ -2211,7 +2519,7 @@ async function loadModuleContexts2(ctx, modules) {
2211
2519
  const out = [];
2212
2520
  for (const m of modules) {
2213
2521
  if (!available.has(m)) continue;
2214
- const file = path11.join(ctx.paths.modulesContextDir, m, "context.md");
2522
+ const file = path12.join(ctx.paths.modulesContextDir, m, "context.md");
2215
2523
  if (existsSync21(file)) {
2216
2524
  out.push({ name: m, content: await readFile6(file, "utf8") });
2217
2525
  }
@@ -2237,6 +2545,8 @@ var GetBriefingInputSchema = {
2237
2545
  ),
2238
2546
  include_stale: z20.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
2239
2547
  track: z20.boolean().default(true).describe("Increment read_count on returned memories"),
2548
+ memory_scopes: z20.array(z20.enum(["personal", "team", "module", "shared"])).optional().describe("Restrict the candidate corpus to selected scopes. Omit to include every scope."),
2549
+ deterministic: z20.boolean().optional().describe("Ignore machine-local usage/impact signals so repeated evaluations rank the shared corpus reproducibly."),
2240
2550
  format: z20.enum(["full", "compact", "actions"]).default("full").describe(
2241
2551
  "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."
2242
2552
  ),
@@ -2265,9 +2575,11 @@ async function getBriefing(input, ctx) {
2265
2575
  let searchMode = "literal";
2266
2576
  let usage = { version: 1, updated_at: "", by_id: {} };
2267
2577
  let byId = /* @__PURE__ */ new Map();
2578
+ const allowedScopes = input.memory_scopes ? new Set(input.memory_scopes) : null;
2579
+ const scopeAllowed = (loaded) => allowedScopes === null || allowedScopes.has(loaded.memory.frontmatter.scope);
2268
2580
  let lastSession;
2269
2581
  if (existsSync22(ctx.paths.memoriesDir)) {
2270
- const allLoaded = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
2582
+ const allLoaded = (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed);
2271
2583
  const recaps = allLoaded.filter(({ memory }) => memory.frontmatter.type === "session_recap").sort(
2272
2584
  (a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
2273
2585
  );
@@ -2300,10 +2612,11 @@ async function getBriefing(input, ctx) {
2300
2612
  if (memoryHasExcludedTag(memory.frontmatter, excludeTags)) return false;
2301
2613
  return true;
2302
2614
  });
2303
- usage = await loadUsageIndex8(ctx.paths);
2615
+ usage = input.deterministic ? { version: 1, updated_at: "", by_id: {} } : await loadUsageIndex8(ctx.paths);
2304
2616
  byId = new Map(allMemories.map((m) => [m.memory.frontmatter.id, m]));
2305
- const semanticHits = input.task && input.semantic ? await trySemanticHits(ctx, input.task, allMemories.length * 2) : null;
2306
- if (input.task && input.semantic) {
2617
+ const semanticEnabled = input.semantic && input.deterministic !== true;
2618
+ const semanticHits = input.task && semanticEnabled ? await trySemanticHits(ctx, input.task, allMemories.length * 2) : null;
2619
+ if (input.task && semanticEnabled) {
2307
2620
  searchMode = semanticHits ? "semantic" : "literal_fallback";
2308
2621
  }
2309
2622
  const seen = /* @__PURE__ */ new Map();
@@ -2639,7 +2952,7 @@ ${m.content}`).join("\n\n---\n\n"),
2639
2952
  }
2640
2953
  }
2641
2954
  if (existsSync22(ctx.paths.memoriesDir)) {
2642
- const allMems = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
2955
+ const allMems = (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed);
2643
2956
  for (const { memory } of allMems) {
2644
2957
  const fm = memory.frontmatter;
2645
2958
  if (!fm.requires_human_approval) continue;
@@ -2696,7 +3009,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
2696
3009
  pcRaw = await readFile7(ctx.paths.projectContext, "utf8");
2697
3010
  } catch {
2698
3011
  }
2699
- const allForBootstrap = existsSync22(ctx.paths.memoriesDir) ? await loadMemoriesFromDir17(ctx.paths.memoriesDir) : [];
3012
+ const allForBootstrap = existsSync22(ctx.paths.memoriesDir) ? (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed) : [];
2700
3013
  const cmForBootstrap = await loadCodeMap(ctx.paths);
2701
3014
  let existingModules = [];
2702
3015
  try {
@@ -2707,7 +3020,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
2707
3020
  const bootstrap = assessBootstrapState({
2708
3021
  projectContextRaw: pcRaw,
2709
3022
  memories: allForBootstrap,
2710
- codeFiles: cmForBootstrap ? Object.keys(cmForBootstrap.files) : [],
3023
+ codeFiles: cmForBootstrap ? Object.keys(cmForBootstrap.files).filter((file) => existsSync22(path13.join(ctx.paths.root, file))) : [],
2711
3024
  existingModules
2712
3025
  });
2713
3026
  if (bootstrap.state !== "ready" && bootstrap.metrics.mainAreas > 0) {
@@ -2830,7 +3143,7 @@ Invoke the \`bootstrap_repo\` MCP prompt, or close these gaps directly:
2830
3143
  };
2831
3144
  }
2832
3145
  async function detectRunCommands(root) {
2833
- const pkgPath = path12.join(root, "package.json");
3146
+ const pkgPath = path13.join(root, "package.json");
2834
3147
  if (!existsSync22(pkgPath)) return null;
2835
3148
  try {
2836
3149
  const pkg = JSON.parse(await readFile7(pkgPath, "utf8"));
@@ -2898,7 +3211,7 @@ function oneLine(value) {
2898
3211
  return value.replace(/\s+/g, " ").replace(/"/g, '\\"').trim().slice(0, 120);
2899
3212
  }
2900
3213
  function serverVersion() {
2901
- return true ? "0.39.2" : "dev";
3214
+ return true ? "0.43.2" : "dev";
2902
3215
  }
2903
3216
  var CodeMapInputSchema = {
2904
3217
  file: z21.string().optional().describe("Filter to files whose path contains this substring"),
@@ -4006,7 +4319,7 @@ async function currentAssessment(ctx) {
4006
4319
  return assessBootstrapState2({
4007
4320
  projectContextRaw,
4008
4321
  memories,
4009
- codeFiles: codeMap ? Object.keys(codeMap.files) : [],
4322
+ codeFiles: codeMap ? Object.keys(codeMap.files).filter((file) => existsSync29(path14.join(ctx.paths.root, file))) : [],
4010
4323
  existingModules
4011
4324
  });
4012
4325
  }
@@ -4225,7 +4538,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
4225
4538
  };
4226
4539
  }
4227
4540
  var SERVER_NAME = "hivelore";
4228
- var SERVER_VERSION = "0.39.2";
4541
+ var SERVER_VERSION = "0.43.2";
4229
4542
  function jsonResult(data) {
4230
4543
  return {
4231
4544
  content: [
@@ -5171,6 +5484,10 @@ async function runHaiveMcpStdio(options) {
5171
5484
  }
5172
5485
 
5173
5486
  export {
5487
+ astEngineAvailable,
5488
+ astLangForPath,
5489
+ runAstPattern,
5490
+ runAstSensorOnContent,
5174
5491
  readPresumedCorrectTargets,
5175
5492
  proposeSensor,
5176
5493
  memTried,
@@ -5201,4 +5518,4 @@ export {
5201
5518
  printHaiveMcpVersion,
5202
5519
  runHaiveMcpStdio
5203
5520
  };
5204
- //# sourceMappingURL=chunk-XA5FXG6E.js.map
5521
+ //# sourceMappingURL=chunk-FXDGOBPT.js.map