@hivelore/mcp 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/dist/server.d.ts CHANGED
@@ -192,6 +192,8 @@ declare const GetBriefingZod: z.ZodObject<{
192
192
  semantic: z.ZodDefault<z.ZodBoolean>;
193
193
  include_stale: z.ZodDefault<z.ZodBoolean>;
194
194
  track: z.ZodDefault<z.ZodBoolean>;
195
+ memory_scopes: z.ZodOptional<z.ZodArray<z.ZodEnum<["personal", "team", "module", "shared"]>, "many">>;
196
+ deterministic: z.ZodOptional<z.ZodBoolean>;
195
197
  format: z.ZodDefault<z.ZodEnum<["full", "compact", "actions"]>>;
196
198
  symbols: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
197
199
  min_semantic_score: z.ZodDefault<z.ZodNumber>;
@@ -210,6 +212,8 @@ declare const GetBriefingZod: z.ZodObject<{
210
212
  min_semantic_score: number;
211
213
  task?: string | undefined;
212
214
  dedupe_project_context?: boolean | undefined;
215
+ memory_scopes?: ("personal" | "team" | "module" | "shared")[] | undefined;
216
+ deterministic?: boolean | undefined;
213
217
  budget_preset?: "quick" | "balanced" | "deep" | undefined;
214
218
  }, {
215
219
  symbols?: string[] | undefined;
@@ -224,6 +228,8 @@ declare const GetBriefingZod: z.ZodObject<{
224
228
  semantic?: boolean | undefined;
225
229
  include_stale?: boolean | undefined;
226
230
  track?: boolean | undefined;
231
+ memory_scopes?: ("personal" | "team" | "module" | "shared")[] | undefined;
232
+ deterministic?: boolean | undefined;
227
233
  min_semantic_score?: number | undefined;
228
234
  budget_preset?: "quick" | "balanced" | "deep" | undefined;
229
235
  }>;
@@ -359,6 +365,7 @@ interface AntiPatternsCheckInput {
359
365
  limit: number;
360
366
  semantic: boolean;
361
367
  min_semantic_score?: number;
368
+ track?: boolean;
362
369
  }
363
370
  interface AntiPatternsWarning {
364
371
  id: string;
@@ -509,6 +516,8 @@ declare const ProposeSensorInputSchema: {
509
516
  memory_id: z.ZodString;
510
517
  kind: z.ZodDefault<z.ZodEnum<["regex", "ast", "shell", "test"]>>;
511
518
  pattern: z.ZodOptional<z.ZodString>;
519
+ rule: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
520
+ language: z.ZodOptional<z.ZodString>;
512
521
  command: z.ZodOptional<z.ZodString>;
513
522
  timeout_ms: z.ZodOptional<z.ZodNumber>;
514
523
  absent: z.ZodOptional<z.ZodString>;
@@ -561,22 +570,25 @@ interface AstScanResult {
561
570
  matches: AstMatch[];
562
571
  detail?: string;
563
572
  }
573
+ type AstRule = Record<string, unknown>;
564
574
  declare function astEngineAvailable(): Promise<boolean>;
565
575
  /** Map a file extension to a built-in ast-grep language. Unknown → null (unsupported, warn only). */
566
- declare function astLangForPath(filePath: string): "TypeScript" | "Tsx" | "JavaScript" | null;
576
+ declare function astLangForPath(filePath: string, explicitLanguage?: string): string | null;
567
577
  /**
568
578
  * Run one AST pattern (with optional `absent` sub-pattern) over a file's FULL content.
569
579
  * A match is suppressed when `absent` matches INSIDE the matched node — the structural version of
570
580
  * the regex `absent` window: the required companion lives in the call's own arguments.
571
581
  */
572
- declare function runAstPattern(content: string, filePath: string, pattern: string, absent?: string): Promise<AstScanResult>;
582
+ declare function runAstPattern(content: string, filePath: string, pattern?: string, absent?: string, rule?: AstRule, language?: string): Promise<AstScanResult>;
573
583
  /**
574
584
  * Gate-side evaluation: matches on the full content, FIRES only when a match intersects the added
575
585
  * lines (introduction, not mere presence). `addedLines` empty/undefined = validation mode (any
576
586
  * match counts — used for silent-on-current / fires-on-bad checks).
577
587
  */
578
588
  declare function runAstSensorOnContent(input: {
579
- pattern: string;
589
+ pattern?: string;
590
+ rule?: AstRule;
591
+ language?: string;
580
592
  absent?: string;
581
593
  content: string;
582
594
  filePath: string;
@@ -593,8 +605,10 @@ declare const MemTriedInputSchema: {
593
605
  paths: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
594
606
  author: z.ZodOptional<z.ZodString>;
595
607
  sensor: z.ZodOptional<z.ZodObject<{
596
- kind: z.ZodDefault<z.ZodEnum<["regex", "shell", "test"]>>;
608
+ kind: z.ZodDefault<z.ZodEnum<["regex", "ast", "shell", "test"]>>;
597
609
  pattern: z.ZodOptional<z.ZodString>;
610
+ rule: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
611
+ language: z.ZodOptional<z.ZodString>;
598
612
  command: z.ZodOptional<z.ZodString>;
599
613
  timeout_ms: z.ZodOptional<z.ZodNumber>;
600
614
  absent: z.ZodOptional<z.ZodString>;
@@ -604,10 +618,12 @@ declare const MemTriedInputSchema: {
604
618
  red_ref: z.ZodOptional<z.ZodString>;
605
619
  bad_example: z.ZodOptional<z.ZodString>;
606
620
  }, "strip", z.ZodTypeAny, {
607
- kind: "regex" | "shell" | "test";
621
+ kind: "regex" | "ast" | "shell" | "test";
608
622
  severity: "warn" | "block";
609
623
  pattern?: string | undefined;
610
624
  message?: string | undefined;
625
+ rule?: Record<string, unknown> | undefined;
626
+ language?: string | undefined;
611
627
  command?: string | undefined;
612
628
  timeout_ms?: number | undefined;
613
629
  absent?: string | undefined;
@@ -617,7 +633,9 @@ declare const MemTriedInputSchema: {
617
633
  }, {
618
634
  pattern?: string | undefined;
619
635
  message?: string | undefined;
620
- kind?: "regex" | "shell" | "test" | undefined;
636
+ kind?: "regex" | "ast" | "shell" | "test" | undefined;
637
+ rule?: Record<string, unknown> | undefined;
638
+ language?: string | undefined;
621
639
  command?: string | undefined;
622
640
  timeout_ms?: number | undefined;
623
641
  absent?: string | undefined;
@@ -817,5 +835,6 @@ declare function printHaiveMcpVersion(): void;
817
835
  declare function runHaiveMcpStdio(options: {
818
836
  root?: string;
819
837
  }): Promise<void>;
838
+ declare function writeMcpRuntimeMarker(context: HaiveContext): Promise<void>;
820
839
 
821
- export { type AnchorFrameworkGroup, type AntiPatternsCheckInput, type AntiPatternsCheckOutput, type AstMatch, type AstScanResult, type BriefingOutput, type CodeMapInput, type CodeMapToolOutput, type CodeSearchInput, type CodeSearchOutput, ENFORCEMENT_PROFILE_TOOLS, EXPERIMENTAL_PROFILE_TOOLS, type GetBriefingInput, type GetRecapInput, type GetRecapOutput, MAINTENANCE_PROFILE_TOOLS, type MemConflictCandidatesInput, type MemDistillInput, type MemDistillOutput, type MemRelevantToInput, type MemRelevantToOutput, type MemResolveProjectInput, type MemSuggestTopicInput, type MemTimelineInput, type MemTriedOutput, type PreCommitCheckInput, type PreCommitCheckOutput, type ProposeSensorOutput, SERVER_NAME, SERVER_VERSION, type ScaffoldTestOutput, TOOL_PROFILES, type ToolProfile, antiPatternsCheck, astEngineAvailable, astLangForPath, codeMapTool, codeSearch, createHaiveServer, detectTestFrameworkForPaths, detectTestFrameworksForAnchors, getAllowedToolsForProfile, getBriefing, getRecap, memConflictCandidates, memDistill, memRelevantTo, memResolveProject, memSuggestTopic, memTimeline, memTried, parseMcpCliArgs, preCommitCheck, printHaiveMcpVersion, proposeSensor, readPresumedCorrectTargets, runAstPattern, runAstSensorOnContent, runHaiveMcpStdio, scaffoldTest };
840
+ export { type AnchorFrameworkGroup, type AntiPatternsCheckInput, type AntiPatternsCheckOutput, type AstMatch, type AstScanResult, type BriefingOutput, type CodeMapInput, type CodeMapToolOutput, type CodeSearchInput, type CodeSearchOutput, ENFORCEMENT_PROFILE_TOOLS, EXPERIMENTAL_PROFILE_TOOLS, type GetBriefingInput, type GetRecapInput, type GetRecapOutput, MAINTENANCE_PROFILE_TOOLS, type MemConflictCandidatesInput, type MemDistillInput, type MemDistillOutput, type MemRelevantToInput, type MemRelevantToOutput, type MemResolveProjectInput, type MemSuggestTopicInput, type MemTimelineInput, type MemTriedOutput, type PreCommitCheckInput, type PreCommitCheckOutput, type ProposeSensorOutput, SERVER_NAME, SERVER_VERSION, type ScaffoldTestOutput, TOOL_PROFILES, type ToolProfile, antiPatternsCheck, astEngineAvailable, astLangForPath, codeMapTool, codeSearch, createHaiveServer, detectTestFrameworkForPaths, detectTestFrameworksForAnchors, getAllowedToolsForProfile, getBriefing, getRecap, memConflictCandidates, memDistill, memRelevantTo, memResolveProject, memSuggestTopic, memTimeline, memTried, parseMcpCliArgs, preCommitCheck, printHaiveMcpVersion, proposeSensor, readPresumedCorrectTargets, runAstPattern, runAstSensorOnContent, runHaiveMcpStdio, scaffoldTest, writeMcpRuntimeMarker };
package/dist/server.js CHANGED
@@ -1,6 +1,8 @@
1
1
  // src/server.ts
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { mkdir as mkdir8, writeFile as writeFile15 } from "fs/promises";
5
+ import path15 from "path";
4
6
 
5
7
  // src/context.ts
6
8
  import { findProjectRoot, resolveHaivePaths } from "@hivelore/core";
@@ -1132,7 +1134,7 @@ import {
1132
1134
  import { z as z16 } from "zod";
1133
1135
 
1134
1136
  // src/tools/propose-sensor.ts
1135
- import { execSync } from "child_process";
1137
+ import { execFileSync } from "child_process";
1136
1138
  import { readFile as readFile3, writeFile as writeFile8 } from "fs/promises";
1137
1139
  import { existsSync as existsSync15, rmSync, symlinkSync } from "fs";
1138
1140
  import os from "os";
@@ -1152,10 +1154,32 @@ import { z as z15 } from "zod";
1152
1154
  // src/ast-sensors.ts
1153
1155
  import path5 from "path";
1154
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
+ }
1155
1178
  async function loadAstEngine() {
1156
1179
  if (cachedEngine !== void 0) return cachedEngine;
1157
1180
  try {
1158
1181
  cachedEngine = await import("@ast-grep/napi");
1182
+ await loadDynamicLanguages(cachedEngine);
1159
1183
  } catch {
1160
1184
  cachedEngine = null;
1161
1185
  }
@@ -1164,11 +1188,22 @@ async function loadAstEngine() {
1164
1188
  async function astEngineAvailable() {
1165
1189
  return await loadAstEngine() !== null;
1166
1190
  }
1167
- function astLangForPath(filePath) {
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
+ }
1168
1199
  const ext = path5.extname(filePath).toLowerCase();
1169
1200
  if (ext === ".ts" || ext === ".mts" || ext === ".cts") return "TypeScript";
1170
1201
  if (ext === ".tsx") return "Tsx";
1171
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";
1172
1207
  return null;
1173
1208
  }
1174
1209
  function absentPresentInNode(node, absent) {
@@ -1183,12 +1218,12 @@ function absentPresentInNode(node, absent) {
1183
1218
  return text.includes(absent);
1184
1219
  }
1185
1220
  }
1186
- async function runAstPattern(content, filePath, pattern, absent) {
1221
+ async function runAstPattern(content, filePath, pattern, absent, rule, language) {
1187
1222
  const engine = await loadAstEngine();
1188
1223
  if (!engine) return { status: "engine-missing", matches: [] };
1189
- const langName = astLangForPath(filePath);
1224
+ const langName = astLangForPath(filePath, language);
1190
1225
  if (!langName) return { status: "unsupported-language", matches: [] };
1191
- const lang = engine.Lang[langName];
1226
+ const lang = langName in engine.Lang ? engine.Lang[langName] : langName;
1192
1227
  let root;
1193
1228
  try {
1194
1229
  root = engine.parse(lang, content).root();
@@ -1197,7 +1232,9 @@ async function runAstPattern(content, filePath, pattern, absent) {
1197
1232
  }
1198
1233
  let nodes;
1199
1234
  try {
1200
- nodes = root.findAll(pattern);
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);
1201
1238
  } catch (err) {
1202
1239
  return { status: "invalid-pattern", matches: [], detail: String(err).slice(0, 200) };
1203
1240
  }
@@ -1214,7 +1251,7 @@ async function runAstPattern(content, filePath, pattern, absent) {
1214
1251
  return { status: "ok", matches };
1215
1252
  }
1216
1253
  async function runAstSensorOnContent(input) {
1217
- const scan = await runAstPattern(input.content, input.filePath, input.pattern, input.absent);
1254
+ const scan = await runAstPattern(input.content, input.filePath, input.pattern, input.absent, input.rule, input.language);
1218
1255
  if (scan.status !== "ok" || !input.addedLines || input.addedLines.size === 0) return scan;
1219
1256
  const added = input.addedLines;
1220
1257
  return {
@@ -1234,7 +1271,9 @@ var ProposeSensorInputSchema = {
1234
1271
  kind: z15.enum(["regex", "ast", "shell", "test"]).default("regex").describe(
1235
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."
1236
1273
  ),
1237
- 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."),
1238
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."),
1239
1278
  timeout_ms: z15.number().int().positive().optional().describe("kind=shell|test: max runtime before the executor kills the command (default 120000)."),
1240
1279
  absent: z15.string().optional().describe(
@@ -1265,7 +1304,7 @@ async function readPresumedCorrectTargets(root, relPaths) {
1265
1304
  const targets = [];
1266
1305
  for (const rel of relPaths) {
1267
1306
  try {
1268
- const content = execSync(`git show ${JSON.stringify(`HEAD:./${rel}`)}`, {
1307
+ const content = execFileSync("git", ["show", `HEAD:./${rel}`], {
1269
1308
  cwd: root,
1270
1309
  encoding: "utf8",
1271
1310
  maxBuffer: 10 * 1024 * 1024,
@@ -1286,7 +1325,7 @@ async function readPresumedCorrectTargets(root, relPaths) {
1286
1325
  }
1287
1326
  function runCommandForValidation(command, root, timeoutMs = 12e4) {
1288
1327
  try {
1289
- execSync(`bash -c ${JSON.stringify(command)}`, {
1328
+ execFileSync("bash", ["-c", command], {
1290
1329
  cwd: root,
1291
1330
  timeout: timeoutMs,
1292
1331
  maxBuffer: 8 * 1024 * 1024,
@@ -1311,7 +1350,7 @@ function proveRedOnIncident(command, root, redRef, timeoutMs) {
1311
1350
  let added = false;
1312
1351
  try {
1313
1352
  try {
1314
- execSync(`git worktree add --detach ${JSON.stringify(worktree)} ${JSON.stringify(redRef)}`, {
1353
+ execFileSync("git", ["worktree", "add", "--detach", worktree, redRef], {
1315
1354
  cwd: root,
1316
1355
  stdio: ["ignore", "pipe", "pipe"],
1317
1356
  timeout: 6e4
@@ -1338,7 +1377,7 @@ function proveRedOnIncident(command, root, redRef, timeoutMs) {
1338
1377
  } finally {
1339
1378
  if (added) {
1340
1379
  try {
1341
- execSync(`git worktree remove --force ${JSON.stringify(worktree)}`, { cwd: root, stdio: "ignore", timeout: 6e4 });
1380
+ execFileSync("git", ["worktree", "remove", "--force", worktree], { cwd: root, stdio: "ignore", timeout: 6e4 });
1342
1381
  } catch {
1343
1382
  try {
1344
1383
  rmSync(worktree, { recursive: true, force: true });
@@ -1378,13 +1417,13 @@ async function proposeSensor(input, ctx) {
1378
1417
  };
1379
1418
  }
1380
1419
  } else if (kind === "ast") {
1381
- if (!input.pattern?.trim()) {
1420
+ if (!input.pattern?.trim() && !input.rule) {
1382
1421
  return {
1383
1422
  accepted: false,
1384
1423
  memory_id: input.memory_id,
1385
1424
  severity: input.severity,
1386
1425
  reason: "invalid-pattern",
1387
- guidance: "kind=ast requires a `pattern` (an ast-grep structural pattern).",
1426
+ guidance: "kind=ast requires a structural `pattern` or a full ast-grep `rule` object.",
1388
1427
  self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1389
1428
  };
1390
1429
  }
@@ -1405,7 +1444,7 @@ async function proposeSensor(input, ctx) {
1405
1444
  }
1406
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}.` : "";
1407
1446
  if (kind === "ast") {
1408
- const pattern = input.pattern.trim();
1447
+ const pattern = input.pattern?.trim();
1409
1448
  if (!await astEngineAvailable() && input.severity === "block") {
1410
1449
  return {
1411
1450
  accepted: false,
@@ -1416,7 +1455,7 @@ async function proposeSensor(input, ctx) {
1416
1455
  self_check: { silent_on_current: false, fires_on_bad: null, fired_on: [] }
1417
1456
  };
1418
1457
  }
1419
- const brittleAst = sensorPatternBrittleness(pattern);
1458
+ const brittleAst = pattern ? sensorPatternBrittleness(pattern) : null;
1420
1459
  if (brittleAst && input.severity === "block") {
1421
1460
  return {
1422
1461
  accepted: false,
@@ -1431,7 +1470,7 @@ async function proposeSensor(input, ctx) {
1431
1470
  const currentTargetsAst = await readPresumedCorrectTargets(ctx.paths.root, anchorPathsAst);
1432
1471
  const firedOnAst = [];
1433
1472
  for (const target of currentTargetsAst) {
1434
- const scan = await runAstSensorOnContent({ pattern, absent: input.absent, content: target.content, filePath: target.path });
1473
+ const scan = await runAstSensorOnContent({ pattern, rule: input.rule, language: input.language, absent: input.absent, content: target.content, filePath: target.path });
1435
1474
  if (scan.status === "invalid-pattern") {
1436
1475
  return {
1437
1476
  accepted: false,
@@ -1460,10 +1499,10 @@ async function proposeSensor(input, ctx) {
1460
1499
  ];
1461
1500
  let firesOnBadAst = null;
1462
1501
  if (badExamplesAst.length > 0 && await astEngineAvailable()) {
1463
- const exampleLang = anchorPathsAst.find((p) => astLangForPath(p) !== null) ?? "example.tsx";
1502
+ const exampleLang = anchorPathsAst.find((p) => astLangForPath(p, input.language) !== null) ?? "example.tsx";
1464
1503
  firesOnBadAst = false;
1465
1504
  for (const example of badExamplesAst) {
1466
- const scan = await runAstSensorOnContent({ pattern, absent: input.absent, content: example, filePath: exampleLang });
1505
+ const scan = await runAstSensorOnContent({ pattern, rule: input.rule, language: input.language, absent: input.absent, content: example, filePath: exampleLang });
1467
1506
  if (scan.status === "ok" && scan.matches.length > 0) {
1468
1507
  firesOnBadAst = true;
1469
1508
  break;
@@ -1482,10 +1521,12 @@ async function proposeSensor(input, ctx) {
1482
1521
  }
1483
1522
  const sensorAst = {
1484
1523
  kind: "ast",
1485
- pattern,
1524
+ ...pattern ? { pattern } : {},
1525
+ ...input.rule ? { rule: input.rule } : {},
1526
+ ...input.language ? { language: input.language } : {},
1486
1527
  ...input.absent ? { absent: input.absent } : {},
1487
1528
  paths: anchorPathsAst,
1488
- message: input.message?.trim() || deriveMessage(found.memory.body, pattern, input.absent),
1529
+ message: input.message?.trim() || deriveMessage(found.memory.body, pattern ?? "AST rule", input.absent),
1489
1530
  ...input.incident?.trim() ? { incident: input.incident.trim() } : {},
1490
1531
  severity: input.severity,
1491
1532
  autogen: false,
@@ -1548,6 +1589,16 @@ ${verdictCmd.detail}`,
1548
1589
  self_check: { silent_on_current: false, fires_on_bad: null, fired_on: anchorPathsCmd }
1549
1590
  };
1550
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
+ }
1551
1602
  const sensorCmd = {
1552
1603
  kind,
1553
1604
  command: input.command.trim(),
@@ -1569,7 +1620,7 @@ ${verdictCmd.detail}`,
1569
1620
  accepted: true,
1570
1621
  memory_id: input.memory_id,
1571
1622
  severity: input.severity,
1572
- guidance: (verdictCmd.status === "passed" ? "Command oracle passes on the current tree; the gate now runs it when the diff touches the sensor's paths (requires enforcement.runCommandSensors=true)." : `Accepted at warn severity, but note: ${verdictCmd.status} on the current tree (${verdictCmd.detail}).`) + (redProven ? " RED proven: the oracle demonstrably FAILS on the incident state (red_ref) \u2014 recorded as red_proven." : " Tip: pass red_ref (the pre-fix commit) to PROVE the oracle catches the incident, not merely that it passes today.") + (pendingTests.length > 0 ? ` Note: the routed test is still a PENDING stub (${pendingTests.join(", ")}) \u2014 it passes on anything; write the assertion to make this oracle real.` : "") + personalScopeNudge,
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,
1573
1624
  self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: redProven ? true : null, fired_on: [] },
1574
1625
  file_path: found.filePath
1575
1626
  };
@@ -1638,8 +1689,10 @@ var MemTriedInputSchema = {
1638
1689
  paths: z16.array(z16.string()).default([]).describe("Anchor file paths this applies to"),
1639
1690
  author: z16.string().optional().describe("Author handle or email"),
1640
1691
  sensor: z16.object({
1641
- kind: z16.enum(["regex", "shell", "test"]).default("regex").describe("regex pattern, or a shell/test COMMAND the gate executes (behaviour bridge)"),
1642
- pattern: z16.string().optional().describe("kind=regex: regex matching the FAULTY usage (added diff lines)"),
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"),
1643
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)"),
1644
1697
  timeout_ms: z16.number().int().positive().optional().describe("kind=shell|test: max runtime (default 120000)"),
1645
1698
  absent: z16.string().optional().describe("kind=regex: regex marking CORRECT usage nearby \u2014 excludes it from firing"),
@@ -1689,6 +1742,8 @@ async function memTried(input, ctx) {
1689
1742
  memory_id: frontmatter.id,
1690
1743
  kind: input.sensor.kind ?? "regex",
1691
1744
  pattern: input.sensor.pattern,
1745
+ rule: input.sensor.rule,
1746
+ language: input.sensor.language,
1692
1747
  command: input.sensor.command,
1693
1748
  timeout_ms: input.sensor.timeout_ms,
1694
1749
  absent: input.sensor.absent,
@@ -1991,7 +2046,7 @@ import {
1991
2046
  import { mkdir as mkdir6, writeFile as writeFile12, rm } from "fs/promises";
1992
2047
  import { existsSync as existsSync19 } from "fs";
1993
2048
  import path10 from "path";
1994
- import { execSync as execSync2 } from "child_process";
2049
+ import { execSync } from "child_process";
1995
2050
  function pendingDistillPath(ctx) {
1996
2051
  return path10.join(ctx.paths.haiveDir, ".cache", "pending-distill.json");
1997
2052
  }
@@ -2034,7 +2089,7 @@ var SessionTracker = class {
2034
2089
  }
2035
2090
  let gitDiff;
2036
2091
  try {
2037
- const raw = execSync2("git diff HEAD", {
2092
+ const raw = execSync("git diff HEAD", {
2038
2093
  cwd: this.ctx.paths.root,
2039
2094
  timeout: 5e3,
2040
2095
  encoding: "utf8",
@@ -2066,7 +2121,7 @@ var SessionTracker = class {
2066
2121
  const triedThreads = this.events.filter((e) => e.tool === "mem_tried").map((e) => e.summary ?? "").filter(Boolean);
2067
2122
  let diffStat;
2068
2123
  try {
2069
- diffStat = execSync2("git diff --stat HEAD", {
2124
+ diffStat = execSync("git diff --stat HEAD", {
2070
2125
  cwd: this.ctx.paths.root,
2071
2126
  timeout: 5e3,
2072
2127
  encoding: "utf8",
@@ -2478,6 +2533,8 @@ var GetBriefingInputSchema = {
2478
2533
  ),
2479
2534
  include_stale: z20.boolean().default(false).describe("Include stale memories (excluded by default \u2014 they may be outdated)"),
2480
2535
  track: z20.boolean().default(true).describe("Increment read_count on returned memories"),
2536
+ memory_scopes: z20.array(z20.enum(["personal", "team", "module", "shared"])).optional().describe("Restrict the candidate corpus to selected scopes. Omit to include every scope."),
2537
+ deterministic: z20.boolean().optional().describe("Ignore machine-local usage/impact signals so repeated evaluations rank the shared corpus reproducibly."),
2481
2538
  format: z20.enum(["full", "compact", "actions"]).default("full").describe(
2482
2539
  "Output format: 'full' returns memory bodies (honors token budget via truncation); 'compact' returns a 1-line summary per memory (call mem_get for detail); 'actions' squeezes bodies to actionable bullet lines \u2014 fewer tokens vs full."
2483
2540
  ),
@@ -2506,9 +2563,11 @@ async function getBriefing(input, ctx) {
2506
2563
  let searchMode = "literal";
2507
2564
  let usage = { version: 1, updated_at: "", by_id: {} };
2508
2565
  let byId = /* @__PURE__ */ new Map();
2566
+ const allowedScopes = input.memory_scopes ? new Set(input.memory_scopes) : null;
2567
+ const scopeAllowed = (loaded) => allowedScopes === null || allowedScopes.has(loaded.memory.frontmatter.scope);
2509
2568
  let lastSession;
2510
2569
  if (existsSync22(ctx.paths.memoriesDir)) {
2511
- const allLoaded = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
2570
+ const allLoaded = (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed);
2512
2571
  const recaps = allLoaded.filter(({ memory }) => memory.frontmatter.type === "session_recap").sort(
2513
2572
  (a, b) => new Date(b.memory.frontmatter.created_at).getTime() - new Date(a.memory.frontmatter.created_at).getTime()
2514
2573
  );
@@ -2541,10 +2600,11 @@ async function getBriefing(input, ctx) {
2541
2600
  if (memoryHasExcludedTag(memory.frontmatter, excludeTags)) return false;
2542
2601
  return true;
2543
2602
  });
2544
- usage = await loadUsageIndex8(ctx.paths);
2603
+ usage = input.deterministic ? { version: 1, updated_at: "", by_id: {} } : await loadUsageIndex8(ctx.paths);
2545
2604
  byId = new Map(allMemories.map((m) => [m.memory.frontmatter.id, m]));
2546
- const semanticHits = input.task && input.semantic ? await trySemanticHits(ctx, input.task, allMemories.length * 2) : null;
2547
- if (input.task && input.semantic) {
2605
+ const semanticEnabled = input.semantic && input.deterministic !== true;
2606
+ const semanticHits = input.task && semanticEnabled ? await trySemanticHits(ctx, input.task, allMemories.length * 2) : null;
2607
+ if (input.task && semanticEnabled) {
2548
2608
  searchMode = semanticHits ? "semantic" : "literal_fallback";
2549
2609
  }
2550
2610
  const seen = /* @__PURE__ */ new Map();
@@ -2880,7 +2940,7 @@ ${m.content}`).join("\n\n---\n\n"),
2880
2940
  }
2881
2941
  }
2882
2942
  if (existsSync22(ctx.paths.memoriesDir)) {
2883
- const allMems = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
2943
+ const allMems = (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed);
2884
2944
  for (const { memory } of allMems) {
2885
2945
  const fm = memory.frontmatter;
2886
2946
  if (!fm.requires_human_approval) continue;
@@ -2937,7 +2997,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
2937
2997
  pcRaw = await readFile7(ctx.paths.projectContext, "utf8");
2938
2998
  } catch {
2939
2999
  }
2940
- const allForBootstrap = existsSync22(ctx.paths.memoriesDir) ? await loadMemoriesFromDir17(ctx.paths.memoriesDir) : [];
3000
+ const allForBootstrap = existsSync22(ctx.paths.memoriesDir) ? (await loadMemoriesFromDir17(ctx.paths.memoriesDir)).filter(scopeAllowed) : [];
2941
3001
  const cmForBootstrap = await loadCodeMap(ctx.paths);
2942
3002
  let existingModules = [];
2943
3003
  try {
@@ -2948,7 +3008,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
2948
3008
  const bootstrap = assessBootstrapState({
2949
3009
  projectContextRaw: pcRaw,
2950
3010
  memories: allForBootstrap,
2951
- codeFiles: cmForBootstrap ? Object.keys(cmForBootstrap.files) : [],
3011
+ codeFiles: cmForBootstrap ? Object.keys(cmForBootstrap.files).filter((file) => existsSync22(path13.join(ctx.paths.root, file))) : [],
2952
3012
  existingModules
2953
3013
  });
2954
3014
  if (bootstrap.state !== "ready" && bootstrap.metrics.mainAreas > 0) {
@@ -3139,7 +3199,7 @@ function oneLine(value) {
3139
3199
  return value.replace(/\s+/g, " ").replace(/"/g, '\\"').trim().slice(0, 120);
3140
3200
  }
3141
3201
  function serverVersion() {
3142
- return true ? "0.42.1" : "dev";
3202
+ return true ? "0.44.0" : "dev";
3143
3203
  }
3144
3204
 
3145
3205
  // src/tools/code-map.ts
@@ -3425,7 +3485,8 @@ var AntiPatternsCheckInputSchema = {
3425
3485
  ),
3426
3486
  min_semantic_score: z26.number().min(0).max(1).default(0.45).describe(
3427
3487
  "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."
3428
- )
3488
+ ),
3489
+ track: z26.boolean().default(true).describe("Record real prevention outcomes. Set false for eval/selftest probes so synthetic cases never inflate ROI.")
3429
3490
  };
3430
3491
  function tokenizeDiffForLiteral(diff) {
3431
3492
  const lines = diff.split("\n");
@@ -3618,7 +3679,9 @@ async function antiPatternsCheck(input, ctx) {
3618
3679
  }).slice(0, input.limit);
3619
3680
  const isHardBlockCatch = (w) => w.reasons.includes("sensor");
3620
3681
  const strongCatches = warnings.filter(isHardBlockCatch);
3621
- await recordPreventionHits(ctx.paths, strongCatches.map((w) => w.id), "anti-pattern");
3682
+ if (input.track !== false) {
3683
+ await recordPreventionHits(ctx.paths, strongCatches.map((w) => w.id), "anti-pattern");
3684
+ }
3622
3685
  return {
3623
3686
  scanned: negative.length,
3624
3687
  warnings
@@ -4312,6 +4375,7 @@ ${template}\`\`\`
4312
4375
  // src/prompts/bootstrap-repo.ts
4313
4376
  import { readFile as readFile8, readdir as readdir5 } from "fs/promises";
4314
4377
  import { existsSync as existsSync29 } from "fs";
4378
+ import path14 from "path";
4315
4379
  import {
4316
4380
  assessBootstrapState as assessBootstrapState2,
4317
4381
  loadCodeMap as loadCodeMap4,
@@ -4339,7 +4403,7 @@ async function currentAssessment(ctx) {
4339
4403
  return assessBootstrapState2({
4340
4404
  projectContextRaw,
4341
4405
  memories,
4342
- codeFiles: codeMap ? Object.keys(codeMap.files) : [],
4406
+ codeFiles: codeMap ? Object.keys(codeMap.files).filter((file) => existsSync29(path14.join(ctx.paths.root, file))) : [],
4343
4407
  existingModules
4344
4408
  });
4345
4409
  }
@@ -4399,13 +4463,14 @@ Main code areas detected: ${areas}
4399
4463
  import { z as z35 } from "zod";
4400
4464
  var PostTaskArgsSchema = {
4401
4465
  task_summary: z35.string().optional().describe("One sentence describing what you just did"),
4402
- files_touched: z35.array(z35.string()).optional().describe("Files you created or modified during the task")
4466
+ files_touched: z35.string().optional().describe("Files you created or modified during the task, as CSV or a JSON array string")
4403
4467
  };
4404
4468
  function postTaskPrompt(args, ctx) {
4405
4469
  const taskLine = args.task_summary ? `
4406
4470
  Task just completed: **${args.task_summary}**` : "";
4407
- const filesLine = args.files_touched && args.files_touched.length > 0 ? `
4408
- Files touched: ${args.files_touched.map((f) => `\`${f}\``).join(", ")}` : "";
4471
+ const filesTouched = parsePromptFilesTouched(args.files_touched);
4472
+ const filesLine = filesTouched.length > 0 ? `
4473
+ Files touched: ${filesTouched.map((f) => `\`${f}\``).join(", ")}` : "";
4409
4474
  const text = `You have just finished a task. Before closing this session, take 60 seconds to capture what you learned.
4410
4475
  ${taskLine}${filesLine}
4411
4476
 
@@ -4494,6 +4559,20 @@ When done, respond with a brief summary: "Saved N memories: [list of IDs]. Sessi
4494
4559
  ]
4495
4560
  };
4496
4561
  }
4562
+ function parsePromptFilesTouched(input) {
4563
+ const raw = input?.trim();
4564
+ if (!raw) return [];
4565
+ if (raw.startsWith("[")) {
4566
+ try {
4567
+ const parsed = JSON.parse(raw);
4568
+ if (Array.isArray(parsed)) {
4569
+ return parsed.filter((value) => typeof value === "string").map((value) => value.trim()).filter(Boolean);
4570
+ }
4571
+ } catch {
4572
+ }
4573
+ }
4574
+ return raw.split(",").map((value) => value.trim()).filter(Boolean);
4575
+ }
4497
4576
 
4498
4577
  // src/prompts/import-docs.ts
4499
4578
  import { z as z36 } from "zod";
@@ -4567,7 +4646,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
4567
4646
  // src/server.ts
4568
4647
  import { hasRecentBriefingMarker, loadConfigSync } from "@hivelore/core";
4569
4648
  var SERVER_NAME = "hivelore";
4570
- var SERVER_VERSION = "0.42.1";
4649
+ var SERVER_VERSION = "0.44.0";
4571
4650
  function jsonResult(data) {
4572
4651
  return {
4573
4652
  content: [
@@ -5506,11 +5585,22 @@ function printHaiveMcpVersion() {
5506
5585
  }
5507
5586
  async function runHaiveMcpStdio(options) {
5508
5587
  const { server, context } = createHaiveServer({ root: options.root, env: process.env });
5588
+ await writeMcpRuntimeMarker(context).catch(() => {
5589
+ });
5509
5590
  console.error(
5510
5591
  `[haive-mcp] starting server v${SERVER_VERSION} (project root: ${context.paths.root})`
5511
5592
  );
5512
5593
  await server.connect(new StdioServerTransport());
5513
5594
  }
5595
+ async function writeMcpRuntimeMarker(context) {
5596
+ await mkdir8(context.paths.runtimeDir, { recursive: true });
5597
+ await writeFile15(path15.join(context.paths.runtimeDir, "mcp-server.json"), JSON.stringify({
5598
+ version: SERVER_VERSION,
5599
+ pid: process.pid,
5600
+ started_at: (/* @__PURE__ */ new Date()).toISOString(),
5601
+ command: "hivelore mcp --stdio"
5602
+ }, null, 2), "utf8");
5603
+ }
5514
5604
  export {
5515
5605
  ENFORCEMENT_PROFILE_TOOLS,
5516
5606
  EXPERIMENTAL_PROFILE_TOOLS,
@@ -5544,6 +5634,7 @@ export {
5544
5634
  runAstPattern,
5545
5635
  runAstSensorOnContent,
5546
5636
  runHaiveMcpStdio,
5547
- scaffoldTest
5637
+ scaffoldTest,
5638
+ writeMcpRuntimeMarker
5548
5639
  };
5549
5640
  //# sourceMappingURL=server.js.map