@hivelore/mcp 0.38.0 → 0.39.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/index.js CHANGED
@@ -1261,6 +1261,7 @@ async function proposeSensor(input, ctx) {
1261
1261
  if (!found) {
1262
1262
  throw new Error(`No memory found with id ${input.memory_id}`);
1263
1263
  }
1264
+ 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}.` : "";
1264
1265
  if (kind !== "regex") {
1265
1266
  const verdictCmd = runCommandForValidation(input.command.trim(), ctx.paths.root, input.timeout_ms);
1266
1267
  const anchorPathsCmd = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
@@ -1295,7 +1296,7 @@ ${verdictCmd.detail}`,
1295
1296
  accepted: true,
1296
1297
  memory_id: input.memory_id,
1297
1298
  severity: input.severity,
1298
- 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}).`,
1299
+ 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}).`) + personalScopeNudge,
1299
1300
  self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: null, fired_on: [] }
1300
1301
  };
1301
1302
  }
@@ -1344,6 +1345,7 @@ ${verdictCmd.detail}`,
1344
1345
  accepted: true,
1345
1346
  memory_id: input.memory_id,
1346
1347
  severity: input.severity,
1348
+ ...personalScopeNudge ? { guidance: personalScopeNudge.trim() } : {},
1347
1349
  self_check,
1348
1350
  file_path: found.filePath
1349
1351
  };
@@ -1354,7 +1356,9 @@ var MemTriedInputSchema = {
1354
1356
  what: z16.string().min(1).describe("Brief description of the approach that was tried"),
1355
1357
  why_failed: z16.string().min(1).describe("Why it failed or why it should NOT be used"),
1356
1358
  instead: z16.string().optional().describe("What to use or do instead (recommended alternative)"),
1357
- scope: z16.enum(["personal", "team", "module"]).default("personal").describe("Visibility scope"),
1359
+ scope: z16.enum(["personal", "team", "module"]).optional().describe(
1360
+ "Visibility scope. Defaults to personal \u2014 EXCEPT when a one-shot `sensor` is attached: an enforced lesson is team truth (the sensor must travel to every machine and CI), so it defaults to team. Pass scope explicitly to override."
1361
+ ),
1358
1362
  module: z16.string().optional().describe("Module name (required when scope=module)"),
1359
1363
  tags: z16.array(z16.string()).default([]).describe("Tags for filtering"),
1360
1364
  paths: z16.array(z16.string()).default([]).describe("Anchor file paths this applies to"),
@@ -1378,10 +1382,11 @@ async function memTried(input, ctx) {
1378
1382
  throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
1379
1383
  }
1380
1384
  const slug = input.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 5).join("-");
1385
+ const scope = input.scope ?? (input.sensor ? "team" : "personal");
1381
1386
  const baseFm = buildFrontmatter2({
1382
1387
  type: "attempt",
1383
1388
  slug,
1384
- scope: input.scope,
1389
+ scope,
1385
1390
  module: input.module,
1386
1391
  tags: input.tags,
1387
1392
  paths: input.paths,
@@ -1429,7 +1434,7 @@ async function memTried(input, ctx) {
1429
1434
  ...verdict.reason ? { reason: verdict.reason } : {},
1430
1435
  ...verdict.guidance ? { guidance: verdict.guidance } : {}
1431
1436
  },
1432
- hint: verdict.accepted ? "Loop closed: the attempt is saved AND enforced \u2014 the gate now refuses a repeat deterministically." : `Attempt saved, but the sensor was rejected (${verdict.reason}). Revise per the guidance and re-propose with propose_sensor.`
1437
+ hint: (verdict.accepted ? "Loop closed: the attempt is saved AND enforced \u2014 the gate now refuses a repeat deterministically." : `Attempt saved, but the sensor was rejected (${verdict.reason}). Revise per the guidance and re-propose with propose_sensor.`) + (input.scope === void 0 ? " Saved team-scoped (an enforced lesson must travel with the repo) \u2014 pass scope:'personal' to keep it private." : "")
1433
1438
  };
1434
1439
  }
1435
1440
  const seed = input.paths.length > 0 ? suggestSensorSeed2(body, input.paths) : null;
@@ -1456,6 +1461,7 @@ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile10 } from
1456
1461
  import path7 from "path";
1457
1462
  import { z as z17 } from "zod";
1458
1463
  import {
1464
+ buildProposeCommand,
1459
1465
  loadMemoriesFromDir as loadMemoriesFromDir14,
1460
1466
  normalizeFramework,
1461
1467
  parseLessonFields,
@@ -1463,38 +1469,46 @@ import {
1463
1469
  scaffoldPostIncidentTest
1464
1470
  } from "@hivelore/core";
1465
1471
  var PY_SIGNALS = ["pyproject.toml", "setup.py", "pytest.ini", "requirements.txt", "tox.ini"];
1466
- async function detectTestFrameworkForPaths(root, anchorPaths) {
1467
- const starts = anchorPaths.length > 0 ? anchorPaths : ["."];
1468
- for (const rel of starts) {
1469
- let dir = path7.resolve(root, rel);
1470
- try {
1471
- if (!statSync(dir).isDirectory()) dir = path7.dirname(dir);
1472
- } catch {
1473
- if (path7.extname(dir)) dir = path7.dirname(dir);
1474
- }
1475
- while (dir.startsWith(root)) {
1476
- const pkgJson = path7.join(dir, "package.json");
1477
- const hasPkg = existsSync17(pkgJson);
1478
- const goMod = existsSync17(path7.join(dir, "go.mod"));
1479
- const pySignal = PY_SIGNALS.some((s) => existsSync17(path7.join(dir, s)));
1480
- if (hasPkg || goMod || pySignal) {
1481
- let pkg = null;
1482
- if (hasPkg) {
1483
- try {
1484
- pkg = JSON.parse(await readFile4(pkgJson, "utf8"));
1485
- } catch {
1486
- pkg = null;
1487
- }
1472
+ async function detectForAnchor(root, rel) {
1473
+ let dir = path7.resolve(root, rel);
1474
+ try {
1475
+ if (!statSync(dir).isDirectory()) dir = path7.dirname(dir);
1476
+ } catch {
1477
+ if (path7.extname(dir)) dir = path7.dirname(dir);
1478
+ }
1479
+ while (dir.startsWith(root)) {
1480
+ const pkgJson = path7.join(dir, "package.json");
1481
+ const hasPkg = existsSync17(pkgJson);
1482
+ const goMod = existsSync17(path7.join(dir, "go.mod"));
1483
+ const pySignal = PY_SIGNALS.some((s) => existsSync17(path7.join(dir, s)));
1484
+ if (hasPkg || goMod || pySignal) {
1485
+ let pkg = null;
1486
+ if (hasPkg) {
1487
+ try {
1488
+ pkg = JSON.parse(await readFile4(pkgJson, "utf8"));
1489
+ } catch {
1490
+ pkg = null;
1488
1491
  }
1489
- const baseDir = path7.relative(root, dir).split(path7.sep).join("/");
1490
- return { framework: pickTestFramework(pkg, { goMod, pySignal }), baseDir };
1491
1492
  }
1492
- const parent = path7.dirname(dir);
1493
- if (parent === dir || dir === root) break;
1494
- dir = parent;
1493
+ const baseDir = path7.relative(root, dir).split(path7.sep).join("/");
1494
+ return { framework: pickTestFramework(pkg, { goMod, pySignal }), baseDir };
1495
1495
  }
1496
+ const parent = path7.dirname(dir);
1497
+ if (parent === dir || dir === root) break;
1498
+ dir = parent;
1496
1499
  }
1497
- return { framework: "vitest", baseDir: "" };
1500
+ return null;
1501
+ }
1502
+ async function detectTestFrameworksForAnchors(root, anchorPaths) {
1503
+ const starts = anchorPaths.length > 0 ? anchorPaths : ["."];
1504
+ const groups = /* @__PURE__ */ new Map();
1505
+ for (const rel of starts) {
1506
+ const found = await detectForAnchor(root, rel) ?? { framework: "vitest", baseDir: "" };
1507
+ const existing = groups.get(found.baseDir);
1508
+ if (existing) existing.anchors.push(rel);
1509
+ else groups.set(found.baseDir, { ...found, anchors: [rel] });
1510
+ }
1511
+ return [...groups.values()];
1498
1512
  }
1499
1513
  var ScaffoldTestInputSchema = {
1500
1514
  memory_id: z17.string().min(1).describe("Id of the attempt/gotcha lesson to scaffold a post-incident test from."),
@@ -1509,43 +1523,69 @@ async function scaffoldTest(input, ctx) {
1509
1523
  return { ok: false, error: `No memory found with id ${input.memory_id}`, memory_id: input.memory_id };
1510
1524
  }
1511
1525
  const anchorPaths = found.memory.frontmatter.anchor.paths ?? [];
1512
- const detected = await detectTestFrameworkForPaths(ctx.paths.root, anchorPaths);
1513
- const framework = input.framework ? normalizeFramework(input.framework) ?? detected.framework : detected.framework;
1526
+ const allGroups = await detectTestFrameworksForAnchors(ctx.paths.root, anchorPaths);
1527
+ const groups = input.out_path ? allGroups.slice(0, 1) : allGroups;
1528
+ const frameworkFor = (detected) => input.framework ? normalizeFramework(input.framework) ?? detected : detected;
1514
1529
  const fields = parseLessonFields(found.memory.body);
1515
- const scaffold = scaffoldPostIncidentTest(
1516
- {
1517
- memoryId: input.memory_id,
1518
- title: fields.title || input.memory_id,
1519
- whyFailed: fields.whyFailed,
1520
- instead: fields.instead,
1521
- incident: found.memory.frontmatter.sensor?.incident,
1522
- paths: anchorPaths
1523
- },
1524
- { framework, outPath: input.out_path, baseDir: detected.baseDir }
1530
+ const lesson = {
1531
+ memoryId: input.memory_id,
1532
+ title: fields.title || input.memory_id,
1533
+ whyFailed: fields.whyFailed,
1534
+ instead: fields.instead,
1535
+ incident: found.memory.frontmatter.sensor?.incident,
1536
+ paths: anchorPaths
1537
+ };
1538
+ let scaffolds = groups.map(
1539
+ (g) => scaffoldPostIncidentTest(lesson, { framework: frameworkFor(g.framework), outPath: input.out_path, baseDir: g.baseDir })
1525
1540
  );
1526
- const abs = path7.isAbsolute(scaffold.relPath) ? scaffold.relPath : path7.resolve(ctx.paths.root, scaffold.relPath);
1527
- let written = false;
1528
- let alreadyExists = false;
1529
- if (input.write) {
1530
- if (existsSync17(abs)) {
1531
- alreadyExists = true;
1532
- } else {
1533
- await mkdir4(path7.dirname(abs), { recursive: true });
1534
- await writeFile10(abs, scaffold.content, "utf8");
1535
- written = true;
1541
+ let proposeCommand = scaffolds[0].proposeCommand;
1542
+ if (scaffolds.length > 1) {
1543
+ proposeCommand = buildProposeCommand(lesson, scaffolds.map((s) => s.runCommand).join(" && "));
1544
+ scaffolds = groups.map(
1545
+ (g) => scaffoldPostIncidentTest(lesson, {
1546
+ framework: frameworkFor(g.framework),
1547
+ baseDir: g.baseDir,
1548
+ proposeCommandOverride: proposeCommand
1549
+ })
1550
+ );
1551
+ }
1552
+ const results = [];
1553
+ for (const scaffold of scaffolds) {
1554
+ const abs = path7.isAbsolute(scaffold.relPath) ? scaffold.relPath : path7.resolve(ctx.paths.root, scaffold.relPath);
1555
+ let written = false;
1556
+ let alreadyExists = false;
1557
+ if (input.write) {
1558
+ if (existsSync17(abs)) {
1559
+ alreadyExists = true;
1560
+ } else {
1561
+ await mkdir4(path7.dirname(abs), { recursive: true });
1562
+ await writeFile10(abs, scaffold.content, "utf8");
1563
+ written = true;
1564
+ }
1536
1565
  }
1566
+ results.push({
1567
+ framework: scaffold.framework,
1568
+ path: scaffold.relPath,
1569
+ run_command: scaffold.runCommand,
1570
+ content: scaffold.content,
1571
+ written,
1572
+ already_exists: alreadyExists
1573
+ });
1537
1574
  }
1575
+ const first = results[0];
1576
+ const anyExisting = results.some((r) => r.already_exists);
1538
1577
  return {
1539
1578
  ok: true,
1540
1579
  memory_id: input.memory_id,
1541
- framework,
1542
- path: scaffold.relPath,
1543
- run_command: scaffold.runCommand,
1544
- propose_command: scaffold.proposeCommand,
1545
- content: scaffold.content,
1546
- written,
1547
- already_exists: alreadyExists,
1548
- notice: alreadyExists ? "File already exists \u2014 not overwritten. Delete it or pass out_path to write elsewhere." : "PENDING test scaffolded. Fill in the assertion (RED on the incident, GREEN once fixed), run it, then arm it with propose_command \u2014 propose_sensor stays the sole validated writer."
1580
+ framework: first.framework,
1581
+ path: first.path,
1582
+ run_command: first.run_command,
1583
+ propose_command: proposeCommand,
1584
+ content: first.content,
1585
+ written: first.written,
1586
+ already_exists: first.already_exists,
1587
+ ...results.length > 1 ? { scaffolds: results } : {},
1588
+ notice: (results.length > 1 ? `Lesson spans ${results.length} packages \u2014 one pending test per owning package; ONE propose_command arms them all (chained oracle). ` : "") + (anyExisting ? "Some file(s) already exist \u2014 not overwritten. Delete them or pass out_path to write elsewhere." : "PENDING test scaffolded. Fill in the assertion (RED on the incident, GREEN once fixed), run it, then arm it with propose_command \u2014 propose_sensor stays the sole validated writer.")
1549
1589
  };
1550
1590
  }
1551
1591
 
@@ -2812,7 +2852,7 @@ function oneLine(value) {
2812
2852
  return value.replace(/\s+/g, " ").replace(/"/g, '\\"').trim().slice(0, 120);
2813
2853
  }
2814
2854
  function serverVersion() {
2815
- return true ? "0.38.0" : "dev";
2855
+ return true ? "0.39.0" : "dev";
2816
2856
  }
2817
2857
 
2818
2858
  // src/tools/code-map.ts
@@ -4240,7 +4280,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
4240
4280
  // src/server.ts
4241
4281
  import { hasRecentBriefingMarker, loadConfigSync } from "@hivelore/core";
4242
4282
  var SERVER_NAME = "hivelore";
4243
- var SERVER_VERSION = "0.38.0";
4283
+ var SERVER_VERSION = "0.39.0";
4244
4284
  function jsonResult(data) {
4245
4285
  return {
4246
4286
  content: [