@hivelore/cli 0.42.1 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -233,6 +233,7 @@ Run the repeatable quality gate for Hivelore itself or for a project using Hivel
233
233
  ```bash
234
234
  hivelore eval
235
235
  hivelore eval --semantic-only
236
+ hivelore eval --semantic-ranking # require and exercise the real embeddings-backed ranker
236
237
  hivelore eval --spec .ai/eval/spec.json --fail-under 80
237
238
  ```
238
239
 
@@ -533,14 +534,14 @@ hivelore install-hooks --dir /path/to/project
533
534
 
534
535
  ---
535
536
 
536
- ### `hivelore embeddings`
537
+ ### `hivelore index`
537
538
 
538
539
  Manage the local semantic search index (requires `@hivelore/embeddings` to be installed).
539
540
 
540
541
  ```bash
541
- hivelore embeddings index # Build or refresh the embeddings index
542
- hivelore embeddings status # Show index stats (count, last updated, model)
543
- hivelore embeddings query "how do we handle retries on payment failures"
542
+ hivelore index memories # Build or refresh the embeddings index
543
+ hivelore index status # Show index stats (count, last updated, model)
544
+ hivelore index query "how do we handle retries on payment failures"
544
545
  ```
545
546
 
546
547
  The model (`bge-small-en-v1.5`, ~110MB) is downloaded on first use and cached locally. **No data leaves your machine.**
@@ -655,8 +656,8 @@ Install `@hivelore/embeddings` for similarity-based memory retrieval:
655
656
 
656
657
  ```bash
657
658
  npm install -g @hivelore/embeddings
658
- hivelore embeddings index # First run downloads the model (~110MB)
659
- hivelore embeddings query "payment retry logic"
659
+ hivelore index memories # First run downloads the model (~110MB)
660
+ hivelore index query "payment retry logic"
660
661
  ```
661
662
 
662
663
  From MCP: set `semantic: true` on `mem_search` or `get_briefing`.
@@ -3,6 +3,8 @@
3
3
  // ../mcp/dist/server.js
4
4
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import { mkdir as mkdir8, writeFile as writeFile15 } from "fs/promises";
7
+ import path15 from "path";
6
8
  import { findProjectRoot, resolveHaivePaths } from "@hivelore/core";
7
9
  import { mkdir, writeFile } from "fs/promises";
8
10
  import { existsSync } from "fs";
@@ -133,7 +135,7 @@ import {
133
135
  suggestSensorSeed as suggestSensorSeed2
134
136
  } from "@hivelore/core";
135
137
  import { z as z16 } from "zod";
136
- import { execSync } from "child_process";
138
+ import { execFileSync } from "child_process";
137
139
  import { readFile as readFile3, writeFile as writeFile8 } from "fs/promises";
138
140
  import { existsSync as existsSync15, rmSync, symlinkSync } from "fs";
139
141
  import os from "os";
@@ -193,7 +195,7 @@ import {
193
195
  import { mkdir as mkdir6, writeFile as writeFile12, rm } from "fs/promises";
194
196
  import { existsSync as existsSync19 } from "fs";
195
197
  import path10 from "path";
196
- import { execSync as execSync2 } from "child_process";
198
+ import { execSync } from "child_process";
197
199
  import { readFile as readFile7, writeFile as writeFile14, readdir as readdir4 } from "fs/promises";
198
200
  import { existsSync as existsSync22 } from "fs";
199
201
  import path13 from "path";
@@ -305,6 +307,7 @@ import { z as z32 } from "zod";
305
307
  import { z as z33 } from "zod";
306
308
  import { readFile as readFile8, readdir as readdir5 } from "fs/promises";
307
309
  import { existsSync as existsSync29 } from "fs";
310
+ import path14 from "path";
308
311
  import {
309
312
  assessBootstrapState as assessBootstrapState2,
310
313
  loadCodeMap as loadCodeMap4,
@@ -1283,10 +1286,32 @@ async function memApprove(input, ctx) {
1283
1286
  };
1284
1287
  }
1285
1288
  var cachedEngine;
1289
+ var registeredDynamicLanguages = /* @__PURE__ */ new Set();
1290
+ async function loadDynamicLanguages(engine) {
1291
+ const registrations = {};
1292
+ const candidates = [
1293
+ ["python", () => import("@ast-grep/lang-python")],
1294
+ ["go", () => import("@ast-grep/lang-go")],
1295
+ ["rust", () => import("@ast-grep/lang-rust")],
1296
+ ["java", () => import("@ast-grep/lang-java")]
1297
+ ];
1298
+ for (const [name, load] of candidates) {
1299
+ try {
1300
+ const mod = await load();
1301
+ registrations[name] = mod.default;
1302
+ registeredDynamicLanguages.add(name);
1303
+ } catch {
1304
+ }
1305
+ }
1306
+ if (Object.keys(registrations).length > 0) {
1307
+ engine.registerDynamicLanguage(registrations);
1308
+ }
1309
+ }
1286
1310
  async function loadAstEngine() {
1287
1311
  if (cachedEngine !== void 0) return cachedEngine;
1288
1312
  try {
1289
1313
  cachedEngine = await import("@ast-grep/napi");
1314
+ await loadDynamicLanguages(cachedEngine);
1290
1315
  } catch {
1291
1316
  cachedEngine = null;
1292
1317
  }
@@ -1295,11 +1320,22 @@ async function loadAstEngine() {
1295
1320
  async function astEngineAvailable() {
1296
1321
  return await loadAstEngine() !== null;
1297
1322
  }
1298
- function astLangForPath(filePath) {
1323
+ function astLangForPath(filePath, explicitLanguage) {
1324
+ if (explicitLanguage) {
1325
+ const normalized = explicitLanguage.toLowerCase();
1326
+ if (["typescript", "tsx", "javascript", "html", "css"].includes(normalized)) {
1327
+ return normalized === "typescript" ? "TypeScript" : normalized === "tsx" ? "Tsx" : normalized === "javascript" ? "JavaScript" : normalized[0].toUpperCase() + normalized.slice(1);
1328
+ }
1329
+ return registeredDynamicLanguages.has(normalized) ? normalized : null;
1330
+ }
1299
1331
  const ext = path5.extname(filePath).toLowerCase();
1300
1332
  if (ext === ".ts" || ext === ".mts" || ext === ".cts") return "TypeScript";
1301
1333
  if (ext === ".tsx") return "Tsx";
1302
1334
  if (ext === ".js" || ext === ".jsx" || ext === ".mjs" || ext === ".cjs") return "JavaScript";
1335
+ if (ext === ".py" && registeredDynamicLanguages.has("python")) return "python";
1336
+ if (ext === ".go" && registeredDynamicLanguages.has("go")) return "go";
1337
+ if (ext === ".rs" && registeredDynamicLanguages.has("rust")) return "rust";
1338
+ if (ext === ".java" && registeredDynamicLanguages.has("java")) return "java";
1303
1339
  return null;
1304
1340
  }
1305
1341
  function absentPresentInNode(node, absent) {
@@ -1314,12 +1350,12 @@ function absentPresentInNode(node, absent) {
1314
1350
  return text.includes(absent);
1315
1351
  }
1316
1352
  }
1317
- async function runAstPattern(content, filePath, pattern, absent) {
1353
+ async function runAstPattern(content, filePath, pattern, absent, rule, language) {
1318
1354
  const engine = await loadAstEngine();
1319
1355
  if (!engine) return { status: "engine-missing", matches: [] };
1320
- const langName = astLangForPath(filePath);
1356
+ const langName = astLangForPath(filePath, language);
1321
1357
  if (!langName) return { status: "unsupported-language", matches: [] };
1322
- const lang = engine.Lang[langName];
1358
+ const lang = langName in engine.Lang ? engine.Lang[langName] : langName;
1323
1359
  let root;
1324
1360
  try {
1325
1361
  root = engine.parse(lang, content).root();
@@ -1328,7 +1364,9 @@ async function runAstPattern(content, filePath, pattern, absent) {
1328
1364
  }
1329
1365
  let nodes;
1330
1366
  try {
1331
- nodes = root.findAll(pattern);
1367
+ const matcher = rule ? { rule: pattern ? { all: [{ pattern }, rule] } : rule } : pattern;
1368
+ if (!matcher) return { status: "invalid-pattern", matches: [], detail: "missing pattern/rule" };
1369
+ nodes = root.findAll(matcher);
1332
1370
  } catch (err) {
1333
1371
  return { status: "invalid-pattern", matches: [], detail: String(err).slice(0, 200) };
1334
1372
  }
@@ -1345,7 +1383,7 @@ async function runAstPattern(content, filePath, pattern, absent) {
1345
1383
  return { status: "ok", matches };
1346
1384
  }
1347
1385
  async function runAstSensorOnContent(input) {
1348
- const scan = await runAstPattern(input.content, input.filePath, input.pattern, input.absent);
1386
+ const scan = await runAstPattern(input.content, input.filePath, input.pattern, input.absent, input.rule, input.language);
1349
1387
  if (scan.status !== "ok" || !input.addedLines || input.addedLines.size === 0) return scan;
1350
1388
  const added = input.addedLines;
1351
1389
  return {
@@ -1363,7 +1401,9 @@ var ProposeSensorInputSchema = {
1363
1401
  kind: z15.enum(["regex", "ast", "shell", "test"]).default("regex").describe(
1364
1402
  "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."
1365
1403
  ),
1366
- pattern: z15.string().optional().describe("kind=regex: regex matching the FAULTY usage (the risky call/token), e.g. 'stripe\\.paymentIntents\\.create'."),
1404
+ pattern: z15.string().optional().describe("kind=regex: regex matching the faulty usage; kind=ast: optional structural pattern (may be combined with `rule`)."),
1405
+ 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."),
1406
+ language: z15.string().optional().describe("kind=ast: explicit built-in/dynamic language name for non-standard file extensions."),
1367
1407
  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."),
1368
1408
  timeout_ms: z15.number().int().positive().optional().describe("kind=shell|test: max runtime before the executor kills the command (default 120000)."),
1369
1409
  absent: z15.string().optional().describe(
@@ -1394,7 +1434,7 @@ async function readPresumedCorrectTargets(root, relPaths) {
1394
1434
  const targets = [];
1395
1435
  for (const rel of relPaths) {
1396
1436
  try {
1397
- const content = execSync(`git show ${JSON.stringify(`HEAD:./${rel}`)}`, {
1437
+ const content = execFileSync("git", ["show", `HEAD:./${rel}`], {
1398
1438
  cwd: root,
1399
1439
  encoding: "utf8",
1400
1440
  maxBuffer: 10 * 1024 * 1024,
@@ -1415,7 +1455,7 @@ async function readPresumedCorrectTargets(root, relPaths) {
1415
1455
  }
1416
1456
  function runCommandForValidation(command, root, timeoutMs = 12e4) {
1417
1457
  try {
1418
- execSync(`bash -c ${JSON.stringify(command)}`, {
1458
+ execFileSync("bash", ["-c", command], {
1419
1459
  cwd: root,
1420
1460
  timeout: timeoutMs,
1421
1461
  maxBuffer: 8 * 1024 * 1024,
@@ -1440,7 +1480,7 @@ function proveRedOnIncident(command, root, redRef, timeoutMs) {
1440
1480
  let added = false;
1441
1481
  try {
1442
1482
  try {
1443
- execSync(`git worktree add --detach ${JSON.stringify(worktree)} ${JSON.stringify(redRef)}`, {
1483
+ execFileSync("git", ["worktree", "add", "--detach", worktree, redRef], {
1444
1484
  cwd: root,
1445
1485
  stdio: ["ignore", "pipe", "pipe"],
1446
1486
  timeout: 6e4
@@ -1467,7 +1507,7 @@ function proveRedOnIncident(command, root, redRef, timeoutMs) {
1467
1507
  } finally {
1468
1508
  if (added) {
1469
1509
  try {
1470
- execSync(`git worktree remove --force ${JSON.stringify(worktree)}`, { cwd: root, stdio: "ignore", timeout: 6e4 });
1510
+ execFileSync("git", ["worktree", "remove", "--force", worktree], { cwd: root, stdio: "ignore", timeout: 6e4 });
1471
1511
  } catch {
1472
1512
  try {
1473
1513
  rmSync(worktree, { recursive: true, force: true });
@@ -1507,13 +1547,13 @@ async function proposeSensor(input, ctx) {
1507
1547
  };
1508
1548
  }
1509
1549
  } else if (kind === "ast") {
1510
- if (!input.pattern?.trim()) {
1550
+ if (!input.pattern?.trim() && !input.rule) {
1511
1551
  return {
1512
1552
  accepted: false,
1513
1553
  memory_id: input.memory_id,
1514
1554
  severity: input.severity,
1515
1555
  reason: "invalid-pattern",
1516
- guidance: "kind=ast requires a `pattern` (an ast-grep structural pattern).",
1556
+ guidance: "kind=ast requires a structural `pattern` or a full ast-grep `rule` object.",
1517
1557
  self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1518
1558
  };
1519
1559
  }
@@ -1534,7 +1574,7 @@ async function proposeSensor(input, ctx) {
1534
1574
  }
1535
1575
  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}.` : "";
1536
1576
  if (kind === "ast") {
1537
- const pattern = input.pattern.trim();
1577
+ const pattern = input.pattern?.trim();
1538
1578
  if (!await astEngineAvailable() && input.severity === "block") {
1539
1579
  return {
1540
1580
  accepted: false,
@@ -1545,7 +1585,7 @@ async function proposeSensor(input, ctx) {
1545
1585
  self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1546
1586
  };
1547
1587
  }
1548
- const brittleAst = sensorPatternBrittleness(pattern);
1588
+ const brittleAst = pattern ? sensorPatternBrittleness(pattern) : null;
1549
1589
  if (brittleAst && input.severity === "block") {
1550
1590
  return {
1551
1591
  accepted: false,
@@ -1560,7 +1600,7 @@ async function proposeSensor(input, ctx) {
1560
1600
  const currentTargetsAst = await readPresumedCorrectTargets(ctx.paths.root, anchorPathsAst);
1561
1601
  const firedOnAst = [];
1562
1602
  for (const target of currentTargetsAst) {
1563
- const scan = await runAstSensorOnContent({ pattern, absent: input.absent, content: target.content, filePath: target.path });
1603
+ const scan = await runAstSensorOnContent({ pattern, rule: input.rule, language: input.language, absent: input.absent, content: target.content, filePath: target.path });
1564
1604
  if (scan.status === "invalid-pattern") {
1565
1605
  return {
1566
1606
  accepted: false,
@@ -1589,10 +1629,10 @@ async function proposeSensor(input, ctx) {
1589
1629
  ];
1590
1630
  let firesOnBadAst = null;
1591
1631
  if (badExamplesAst.length > 0 && await astEngineAvailable()) {
1592
- const exampleLang = anchorPathsAst.find((p) => astLangForPath(p) !== null) ?? "example.tsx";
1632
+ const exampleLang = anchorPathsAst.find((p) => astLangForPath(p, input.language) !== null) ?? "example.tsx";
1593
1633
  firesOnBadAst = false;
1594
1634
  for (const example of badExamplesAst) {
1595
- const scan = await runAstSensorOnContent({ pattern, absent: input.absent, content: example, filePath: exampleLang });
1635
+ const scan = await runAstSensorOnContent({ pattern, rule: input.rule, language: input.language, absent: input.absent, content: example, filePath: exampleLang });
1596
1636
  if (scan.status === "ok" && scan.matches.length > 0) {
1597
1637
  firesOnBadAst = true;
1598
1638
  break;
@@ -1611,10 +1651,12 @@ async function proposeSensor(input, ctx) {
1611
1651
  }
1612
1652
  const sensorAst = {
1613
1653
  kind: "ast",
1614
- pattern,
1654
+ ...pattern ? { pattern } : {},
1655
+ ...input.rule ? { rule: input.rule } : {},
1656
+ ...input.language ? { language: input.language } : {},
1615
1657
  ...input.absent ? { absent: input.absent } : {},
1616
1658
  paths: anchorPathsAst,
1617
- message: input.message?.trim() || deriveMessage(found.memory.body, pattern, input.absent),
1659
+ message: input.message?.trim() || deriveMessage(found.memory.body, pattern ?? "AST rule", input.absent),
1618
1660
  ...input.incident?.trim() ? { incident: input.incident.trim() } : {},
1619
1661
  severity: input.severity,
1620
1662
  autogen: false,
@@ -1677,6 +1719,16 @@ ${verdictCmd.detail}`,
1677
1719
  self_check: { silent_on_current: false, fires_on_bad: null, fired_on: anchorPathsCmd }
1678
1720
  };
1679
1721
  }
1722
+ if (input.severity === "block" && !input.red_ref?.trim()) {
1723
+ return {
1724
+ accepted: false,
1725
+ memory_id: input.memory_id,
1726
+ severity: input.severity,
1727
+ reason: "red-required",
1728
+ 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.",
1729
+ self_check: { silent_on_current: true, fires_on_bad: null, fired_on: [] }
1730
+ };
1731
+ }
1680
1732
  const sensorCmd = {
1681
1733
  kind,
1682
1734
  command: input.command.trim(),
@@ -1698,7 +1750,7 @@ ${verdictCmd.detail}`,
1698
1750
  accepted: true,
1699
1751
  memory_id: input.memory_id,
1700
1752
  severity: input.severity,
1701
- guidance: (verdictCmd.status === "passed" ? "Command oracle passes on the current tree; the gate now runs it when the diff touches the sensor's paths (requires enforcement.runCommandSensors=true)." : `Accepted at warn severity, but note: ${verdictCmd.status} on the current tree (${verdictCmd.detail}).`) + (redProven ? " RED proven: the oracle demonstrably FAILS on the incident state (red_ref) \u2014 recorded as red_proven." : " Tip: pass red_ref (the pre-fix commit) to PROVE the oracle catches the incident, not merely that it passes today.") + (pendingTests.length > 0 ? ` Note: the routed test is still a PENDING stub (${pendingTests.join(", ")}) \u2014 it passes on anything; write the assertion to make this oracle real.` : "") + personalScopeNudge,
1753
+ 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,
1702
1754
  self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: redProven ? true : null, fired_on: [] },
1703
1755
  file_path: found.filePath
1704
1756
  };
@@ -1765,8 +1817,10 @@ var MemTriedInputSchema = {
1765
1817
  paths: z16.array(z16.string()).default([]).describe("Anchor file paths this applies to"),
1766
1818
  author: z16.string().optional().describe("Author handle or email"),
1767
1819
  sensor: z16.object({
1768
- kind: z16.enum(["regex", "shell", "test"]).default("regex").describe("regex pattern, or a shell/test COMMAND the gate executes (behaviour bridge)"),
1769
- pattern: z16.string().optional().describe("kind=regex: regex matching the FAULTY usage (added diff lines)"),
1820
+ kind: z16.enum(["regex", "ast", "shell", "test"]).default("regex").describe("regex/AST pattern, or a shell/test command"),
1821
+ pattern: z16.string().optional().describe("kind=regex|ast: pattern matching the faulty usage"),
1822
+ rule: z16.record(z16.unknown()).optional().describe("kind=ast: full ast-grep Rule object"),
1823
+ language: z16.string().optional().describe("kind=ast: explicit built-in/dynamic language"),
1770
1824
  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)"),
1771
1825
  timeout_ms: z16.number().int().positive().optional().describe("kind=shell|test: max runtime (default 120000)"),
1772
1826
  absent: z16.string().optional().describe("kind=regex: regex marking CORRECT usage nearby \u2014 excludes it from firing"),
@@ -1816,6 +1870,8 @@ async function memTried(input, ctx) {
1816
1870
  memory_id: frontmatter.id,
1817
1871
  kind: input.sensor.kind ?? "regex",
1818
1872
  pattern: input.sensor.pattern,
1873
+ rule: input.sensor.rule,
1874
+ language: input.sensor.language,
1819
1875
  command: input.sensor.command,
1820
1876
  timeout_ms: input.sensor.timeout_ms,
1821
1877
  absent: input.sensor.absent,
@@ -2109,7 +2165,7 @@ var SessionTracker = class {
2109
2165
  }
2110
2166
  let gitDiff;
2111
2167
  try {
2112
- const raw = execSync2("git diff HEAD", {
2168
+ const raw = execSync("git diff HEAD", {
2113
2169
  cwd: this.ctx.paths.root,
2114
2170
  timeout: 5e3,
2115
2171
  encoding: "utf8",
@@ -2141,7 +2197,7 @@ var SessionTracker = class {
2141
2197
  const triedThreads = this.events.filter((e) => e.tool === "mem_tried").map((e) => e.summary ?? "").filter(Boolean);
2142
2198
  let diffStat;
2143
2199
  try {
2144
- diffStat = execSync2("git diff --stat HEAD", {
2200
+ diffStat = execSync("git diff --stat HEAD", {
2145
2201
  cwd: this.ctx.paths.root,
2146
2202
  timeout: 5e3,
2147
2203
  encoding: "utf8",
@@ -2491,6 +2547,8 @@ var GetBriefingInputSchema = {
2491
2547
  ),
2492
2548
  include_stale: z20.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
2493
2549
  track: z20.boolean().default(true).describe("Increment read_count on returned memories"),
2550
+ memory_scopes: z20.array(z20.enum(["personal", "team", "module", "shared"])).optional().describe("Restrict the candidate corpus to selected scopes. Omit to include every scope."),
2551
+ deterministic: z20.boolean().optional().describe("Ignore machine-local usage/impact signals so repeated evaluations rank the shared corpus reproducibly."),
2494
2552
  format: z20.enum(["full", "compact", "actions"]).default("full").describe(
2495
2553
  "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."
2496
2554
  ),
@@ -2519,9 +2577,11 @@ async function getBriefing(input, ctx) {
2519
2577
  let searchMode = "literal";
2520
2578
  let usage = { version: 1, updated_at: "", by_id: {} };
2521
2579
  let byId = /* @__PURE__ */ new Map();
2580
+ const allowedScopes = input.memory_scopes ? new Set(input.memory_scopes) : null;
2581
+ const scopeAllowed = (loaded) => allowedScopes === null || allowedScopes.has(loaded.memory.frontmatter.scope);
2522
2582
  let lastSession;
2523
2583
  if (existsSync22(ctx.paths.memoriesDir)) {
2524
- const allLoaded = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
2584
+ const allLoaded = (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed);
2525
2585
  const recaps = allLoaded.filter(({ memory }) => memory.frontmatter.type === "session_recap").sort(
2526
2586
  (a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
2527
2587
  );
@@ -2554,10 +2614,11 @@ async function getBriefing(input, ctx) {
2554
2614
  if (memoryHasExcludedTag(memory.frontmatter, excludeTags)) return false;
2555
2615
  return true;
2556
2616
  });
2557
- usage = await loadUsageIndex8(ctx.paths);
2617
+ usage = input.deterministic ? { version: 1, updated_at: "", by_id: {} } : await loadUsageIndex8(ctx.paths);
2558
2618
  byId = new Map(allMemories.map((m) => [m.memory.frontmatter.id, m]));
2559
- const semanticHits = input.task && input.semantic ? await trySemanticHits(ctx, input.task, allMemories.length * 2) : null;
2560
- if (input.task && input.semantic) {
2619
+ const semanticEnabled = input.semantic && input.deterministic !== true;
2620
+ const semanticHits = input.task && semanticEnabled ? await trySemanticHits(ctx, input.task, allMemories.length * 2) : null;
2621
+ if (input.task && semanticEnabled) {
2561
2622
  searchMode = semanticHits ? "semantic" : "literal_fallback";
2562
2623
  }
2563
2624
  const seen = /* @__PURE__ */ new Map();
@@ -2893,7 +2954,7 @@ ${m.content}`).join("\n\n---\n\n"),
2893
2954
  }
2894
2955
  }
2895
2956
  if (existsSync22(ctx.paths.memoriesDir)) {
2896
- const allMems = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
2957
+ const allMems = (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed);
2897
2958
  for (const { memory } of allMems) {
2898
2959
  const fm = memory.frontmatter;
2899
2960
  if (!fm.requires_human_approval) continue;
@@ -2950,7 +3011,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
2950
3011
  pcRaw = await readFile7(ctx.paths.projectContext, "utf8");
2951
3012
  } catch {
2952
3013
  }
2953
- const allForBootstrap = existsSync22(ctx.paths.memoriesDir) ? await loadMemoriesFromDir17(ctx.paths.memoriesDir) : [];
3014
+ const allForBootstrap = existsSync22(ctx.paths.memoriesDir) ? (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed) : [];
2954
3015
  const cmForBootstrap = await loadCodeMap(ctx.paths);
2955
3016
  let existingModules = [];
2956
3017
  try {
@@ -2961,7 +3022,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
2961
3022
  const bootstrap = assessBootstrapState({
2962
3023
  projectContextRaw: pcRaw,
2963
3024
  memories: allForBootstrap,
2964
- codeFiles: cmForBootstrap ? Object.keys(cmForBootstrap.files) : [],
3025
+ codeFiles: cmForBootstrap ? Object.keys(cmForBootstrap.files).filter((file) => existsSync22(path13.join(ctx.paths.root, file))) : [],
2965
3026
  existingModules
2966
3027
  });
2967
3028
  if (bootstrap.state !== "ready" && bootstrap.metrics.mainAreas > 0) {
@@ -3152,7 +3213,7 @@ function oneLine(value) {
3152
3213
  return value.replace(/\s+/g, " ").replace(/"/g, '\\"').trim().slice(0, 120);
3153
3214
  }
3154
3215
  function serverVersion() {
3155
- return true ? "0.42.1" : "dev";
3216
+ return true ? "0.44.0" : "dev";
3156
3217
  }
3157
3218
  var CodeMapInputSchema = {
3158
3219
  file: z21.string().optional().describe("Filter to files whose path contains this substring"),
@@ -3395,7 +3456,8 @@ var AntiPatternsCheckInputSchema = {
3395
3456
  ),
3396
3457
  min_semantic_score: z26.number().min(0).max(1).default(0.45).describe(
3397
3458
  "Minimum cosine score for semantic-only anti-pattern hits. Anchor/literal matches still surface. Default 0.45 keeps broad, weakly-related memories out of review noise."
3398
- )
3459
+ ),
3460
+ track: z26.boolean().default(true).describe("Record real prevention outcomes. Set false for eval/selftest probes so synthetic cases never inflate ROI.")
3399
3461
  };
3400
3462
  function tokenizeDiffForLiteral(diff) {
3401
3463
  const lines = diff.split("\n");
@@ -3588,7 +3650,9 @@ async function antiPatternsCheck(input, ctx) {
3588
3650
  }).slice(0, input.limit);
3589
3651
  const isHardBlockCatch = (w) => w.reasons.includes("sensor");
3590
3652
  const strongCatches = warnings.filter(isHardBlockCatch);
3591
- await recordPreventionHits(ctx.paths, strongCatches.map((w) => w.id), "anti-pattern");
3653
+ if (input.track !== false) {
3654
+ await recordPreventionHits(ctx.paths, strongCatches.map((w) => w.id), "anti-pattern");
3655
+ }
3592
3656
  return {
3593
3657
  scanned: negative.length,
3594
3658
  warnings
@@ -4260,7 +4324,7 @@ async function currentAssessment(ctx) {
4260
4324
  return assessBootstrapState2({
4261
4325
  projectContextRaw,
4262
4326
  memories,
4263
- codeFiles: codeMap ? Object.keys(codeMap.files) : [],
4327
+ codeFiles: codeMap ? Object.keys(codeMap.files).filter((file) => existsSync29(path14.join(ctx.paths.root, file))) : [],
4264
4328
  existingModules
4265
4329
  });
4266
4330
  }
@@ -4317,13 +4381,14 @@ Main code areas detected: ${areas}
4317
4381
  }
4318
4382
  var PostTaskArgsSchema = {
4319
4383
  task_summary: z35.string().optional().describe("One sentence describing what you just did"),
4320
- files_touched: z35.array(z35.string()).optional().describe("Files you created or modified during the task")
4384
+ files_touched: z35.string().optional().describe("Files you created or modified during the task, as CSV or a JSON array string")
4321
4385
  };
4322
4386
  function postTaskPrompt(args, ctx) {
4323
4387
  const taskLine = args.task_summary ? `
4324
4388
  Task just completed: **${args.task_summary}**` : "";
4325
- const filesLine = args.files_touched && args.files_touched.length > 0 ? `
4326
- Files touched: ${args.files_touched.map((f) => `\`${f}\``).join(", ")}` : "";
4389
+ const filesTouched = parsePromptFilesTouched(args.files_touched);
4390
+ const filesLine = filesTouched.length > 0 ? `
4391
+ Files touched: ${filesTouched.map((f) => `\`${f}\``).join(", ")}` : "";
4327
4392
  const text = `You have just finished a task. Before closing this session, take 60 seconds to capture what you learned.
4328
4393
  ${taskLine}${filesLine}
4329
4394
 
@@ -4412,6 +4477,20 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
4412
4477
  ]
4413
4478
  };
4414
4479
  }
4480
+ function parsePromptFilesTouched(input) {
4481
+ const raw = input?.trim();
4482
+ if (!raw) return [];
4483
+ if (raw.startsWith("[")) {
4484
+ try {
4485
+ const parsed = JSON.parse(raw);
4486
+ if (Array.isArray(parsed)) {
4487
+ return parsed.filter((value) => typeof value === "string").map((value) => value.trim()).filter(Boolean);
4488
+ }
4489
+ } catch {
4490
+ }
4491
+ }
4492
+ return raw.split(",").map((value) => value.trim()).filter(Boolean);
4493
+ }
4415
4494
  var ImportDocsArgsSchema = {
4416
4495
  content: z36.string().describe("The documentation content to analyze and import as memories (Markdown, README, ADR, etc.)"),
4417
4496
  source: z36.string().optional().describe("Origin of the content (file path, URL, or document title) \u2014 used to anchor memories"),
@@ -4479,7 +4558,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
4479
4558
  };
4480
4559
  }
4481
4560
  var SERVER_NAME = "hivelore";
4482
- var SERVER_VERSION = "0.42.1";
4561
+ var SERVER_VERSION = "0.44.0";
4483
4562
  function jsonResult(data) {
4484
4563
  return {
4485
4564
  content: [
@@ -5418,11 +5497,22 @@ function printHaiveMcpVersion() {
5418
5497
  }
5419
5498
  async function runHaiveMcpStdio(options) {
5420
5499
  const { server, context } = createHaiveServer({ root: options.root, env: process.env });
5500
+ await writeMcpRuntimeMarker(context).catch(() => {
5501
+ });
5421
5502
  console.error(
5422
5503
  `[haive-mcp] starting server v${SERVER_VERSION} (project root: ${context.paths.root})`
5423
5504
  );
5424
5505
  await server.connect(new StdioServerTransport());
5425
5506
  }
5507
+ async function writeMcpRuntimeMarker(context) {
5508
+ await mkdir8(context.paths.runtimeDir, { recursive: true });
5509
+ await writeFile15(path15.join(context.paths.runtimeDir, "mcp-server.json"), JSON.stringify({
5510
+ version: SERVER_VERSION,
5511
+ pid: process.pid,
5512
+ started_at: (/* @__PURE__ */ new Date()).toISOString(),
5513
+ command: "hivelore mcp --stdio"
5514
+ }, null, 2), "utf8");
5515
+ }
5426
5516
 
5427
5517
  export {
5428
5518
  astEngineAvailable,
@@ -5457,6 +5547,7 @@ export {
5457
5547
  createHaiveServer,
5458
5548
  parseMcpCliArgs,
5459
5549
  printHaiveMcpVersion,
5460
- runHaiveMcpStdio
5550
+ runHaiveMcpStdio,
5551
+ writeMcpRuntimeMarker
5461
5552
  };
5462
- //# sourceMappingURL=chunk-EJ7A4IKD.js.map
5553
+ //# sourceMappingURL=chunk-6KRXMDLC.js.map