@hivelore/cli 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.
@@ -149,6 +149,7 @@ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile10 } from
149
149
  import path7 from "path";
150
150
  import { z as z17 } from "zod";
151
151
  import {
152
+ buildProposeCommand,
152
153
  loadMemoriesFromDir as loadMemoriesFromDir14,
153
154
  normalizeFramework,
154
155
  parseLessonFields,
@@ -1391,6 +1392,7 @@ async function proposeSensor(input, ctx) {
1391
1392
  if (!found) {
1392
1393
  throw new Error(`No memory found with id ${input.memory_id}`);
1393
1394
  }
1395
+ 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}.` : "";
1394
1396
  if (kind !== "regex") {
1395
1397
  const verdictCmd = runCommandForValidation(input.command.trim(), ctx.paths.root, input.timeout_ms);
1396
1398
  const anchorPathsCmd = input.paths.length > 0 ? input.paths : found.memory.frontmatter.anchor.paths;
@@ -1425,7 +1427,7 @@ ${verdictCmd.detail}`,
1425
1427
  accepted: true,
1426
1428
  memory_id: input.memory_id,
1427
1429
  severity: input.severity,
1428
- 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}).`,
1430
+ 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,
1429
1431
  self_check: { silent_on_current: verdictCmd.status === "passed", fires_on_bad: null, fired_on: [] }
1430
1432
  };
1431
1433
  }
@@ -1474,6 +1476,7 @@ ${verdictCmd.detail}`,
1474
1476
  accepted: true,
1475
1477
  memory_id: input.memory_id,
1476
1478
  severity: input.severity,
1479
+ ...personalScopeNudge ? { guidance: personalScopeNudge.trim() } : {},
1477
1480
  self_check,
1478
1481
  file_path: found.filePath
1479
1482
  };
@@ -1482,7 +1485,9 @@ var MemTriedInputSchema = {
1482
1485
  what: z16.string().min(1).describe("Brief description of the approach that was tried"),
1483
1486
  why_failed: z16.string().min(1).describe("Why it failed or why it should NOT be used"),
1484
1487
  instead: z16.string().optional().describe("What to use or do instead (recommended alternative)"),
1485
- scope: z16.enum(["personal", "team", "module"]).default("personal").describe("Visibility scope"),
1488
+ scope: z16.enum(["personal", "team", "module"]).optional().describe(
1489
+ "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."
1490
+ ),
1486
1491
  module: z16.string().optional().describe("Module name (required when scope=module)"),
1487
1492
  tags: z16.array(z16.string()).default([]).describe("Tags for filtering"),
1488
1493
  paths: z16.array(z16.string()).default([]).describe("Anchor file paths this applies to"),
@@ -1506,10 +1511,11 @@ async function memTried(input, ctx) {
1506
1511
  throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'hivelore init' first.`);
1507
1512
  }
1508
1513
  const slug = input.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 5).join("-");
1514
+ const scope = input.scope ?? (input.sensor ? "team" : "personal");
1509
1515
  const baseFm = buildFrontmatter2({
1510
1516
  type: "attempt",
1511
1517
  slug,
1512
- scope: input.scope,
1518
+ scope,
1513
1519
  module: input.module,
1514
1520
  tags: input.tags,
1515
1521
  paths: input.paths,
@@ -1557,7 +1563,7 @@ async function memTried(input, ctx) {
1557
1563
  ...verdict.reason ? { reason: verdict.reason } : {},
1558
1564
  ...verdict.guidance ? { guidance: verdict.guidance } : {}
1559
1565
  },
1560
- 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.`
1566
+ 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." : "")
1561
1567
  };
1562
1568
  }
1563
1569
  const seed = input.paths.length > 0 ? suggestSensorSeed2(body, input.paths) : null;
@@ -1578,39 +1584,55 @@ async function memTried(input, ctx) {
1578
1584
  };
1579
1585
  }
1580
1586
  var PY_SIGNALS = ["pyproject.toml", "setup.py", "pytest.ini", "requirements.txt", "tox.ini"];
1581
- async function detectTestFrameworkForPaths(root, anchorPaths) {
1582
- const starts = anchorPaths.length > 0 ? anchorPaths : ["."];
1583
- for (const rel of starts) {
1584
- let dir = path7.resolve(root, rel);
1585
- try {
1586
- if (!statSync(dir).isDirectory()) dir = path7.dirname(dir);
1587
- } catch {
1588
- if (path7.extname(dir)) dir = path7.dirname(dir);
1589
- }
1590
- while (dir.startsWith(root)) {
1591
- const pkgJson = path7.join(dir, "package.json");
1592
- const hasPkg = existsSync17(pkgJson);
1593
- const goMod = existsSync17(path7.join(dir, "go.mod"));
1594
- const pySignal = PY_SIGNALS.some((s) => existsSync17(path7.join(dir, s)));
1595
- if (hasPkg || goMod || pySignal) {
1596
- let pkg = null;
1597
- if (hasPkg) {
1598
- try {
1599
- pkg = JSON.parse(await readFile4(pkgJson, "utf8"));
1600
- } catch {
1601
- pkg = null;
1602
- }
1587
+ async function detectForAnchor(root, rel) {
1588
+ let dir = path7.resolve(root, rel);
1589
+ try {
1590
+ if (!statSync(dir).isDirectory()) dir = path7.dirname(dir);
1591
+ } catch {
1592
+ if (path7.extname(dir)) dir = path7.dirname(dir);
1593
+ }
1594
+ while (dir.startsWith(root)) {
1595
+ const pkgJson = path7.join(dir, "package.json");
1596
+ const hasPkg = existsSync17(pkgJson);
1597
+ const goMod = existsSync17(path7.join(dir, "go.mod"));
1598
+ const pySignal = PY_SIGNALS.some((s) => existsSync17(path7.join(dir, s)));
1599
+ if (hasPkg || goMod || pySignal) {
1600
+ let pkg = null;
1601
+ if (hasPkg) {
1602
+ try {
1603
+ pkg = JSON.parse(await readFile4(pkgJson, "utf8"));
1604
+ } catch {
1605
+ pkg = null;
1603
1606
  }
1604
- const baseDir = path7.relative(root, dir).split(path7.sep).join("/");
1605
- return { framework: pickTestFramework(pkg, { goMod, pySignal }), baseDir };
1606
1607
  }
1607
- const parent = path7.dirname(dir);
1608
- if (parent === dir || dir === root) break;
1609
- dir = parent;
1608
+ const baseDir = path7.relative(root, dir).split(path7.sep).join("/");
1609
+ return { framework: pickTestFramework(pkg, { goMod, pySignal }), baseDir };
1610
1610
  }
1611
+ const parent = path7.dirname(dir);
1612
+ if (parent === dir || dir === root) break;
1613
+ dir = parent;
1614
+ }
1615
+ return null;
1616
+ }
1617
+ async function detectTestFrameworkForPaths(root, anchorPaths) {
1618
+ const starts = anchorPaths.length > 0 ? anchorPaths : ["."];
1619
+ for (const rel of starts) {
1620
+ const found = await detectForAnchor(root, rel);
1621
+ if (found) return found;
1611
1622
  }
1612
1623
  return { framework: "vitest", baseDir: "" };
1613
1624
  }
1625
+ async function detectTestFrameworksForAnchors(root, anchorPaths) {
1626
+ const starts = anchorPaths.length > 0 ? anchorPaths : ["."];
1627
+ const groups = /* @__PURE__ */ new Map();
1628
+ for (const rel of starts) {
1629
+ const found = await detectForAnchor(root, rel) ?? { framework: "vitest", baseDir: "" };
1630
+ const existing = groups.get(found.baseDir);
1631
+ if (existing) existing.anchors.push(rel);
1632
+ else groups.set(found.baseDir, { ...found, anchors: [rel] });
1633
+ }
1634
+ return [...groups.values()];
1635
+ }
1614
1636
  var ScaffoldTestInputSchema = {
1615
1637
  memory_id: z17.string().min(1).describe("Id of the attempt/gotcha lesson to scaffold a post-incident test from."),
1616
1638
  framework: z17.enum(["vitest", "jest", "pytest", "gotest"]).optional().describe("Test framework. Auto-detected from the package that owns the lesson's anchor paths when omitted."),
@@ -1624,43 +1646,69 @@ async function scaffoldTest(input, ctx) {
1624
1646
  return { ok: false, error: `No memory found with id ${input.memory_id}`, memory_id: input.memory_id };
1625
1647
  }
1626
1648
  const anchorPaths = found.memory.frontmatter.anchor.paths ?? [];
1627
- const detected = await detectTestFrameworkForPaths(ctx.paths.root, anchorPaths);
1628
- const framework = input.framework ? normalizeFramework(input.framework) ?? detected.framework : detected.framework;
1649
+ const allGroups = await detectTestFrameworksForAnchors(ctx.paths.root, anchorPaths);
1650
+ const groups = input.out_path ? allGroups.slice(0, 1) : allGroups;
1651
+ const frameworkFor = (detected) => input.framework ? normalizeFramework(input.framework) ?? detected : detected;
1629
1652
  const fields = parseLessonFields(found.memory.body);
1630
- const scaffold = scaffoldPostIncidentTest(
1631
- {
1632
- memoryId: input.memory_id,
1633
- title: fields.title || input.memory_id,
1634
- whyFailed: fields.whyFailed,
1635
- instead: fields.instead,
1636
- incident: found.memory.frontmatter.sensor?.incident,
1637
- paths: anchorPaths
1638
- },
1639
- { framework, outPath: input.out_path, baseDir: detected.baseDir }
1653
+ const lesson = {
1654
+ memoryId: input.memory_id,
1655
+ title: fields.title || input.memory_id,
1656
+ whyFailed: fields.whyFailed,
1657
+ instead: fields.instead,
1658
+ incident: found.memory.frontmatter.sensor?.incident,
1659
+ paths: anchorPaths
1660
+ };
1661
+ let scaffolds = groups.map(
1662
+ (g) => scaffoldPostIncidentTest(lesson, { framework: frameworkFor(g.framework), outPath: input.out_path, baseDir: g.baseDir })
1640
1663
  );
1641
- const abs = path7.isAbsolute(scaffold.relPath) ? scaffold.relPath : path7.resolve(ctx.paths.root, scaffold.relPath);
1642
- let written = false;
1643
- let alreadyExists = false;
1644
- if (input.write) {
1645
- if (existsSync17(abs)) {
1646
- alreadyExists = true;
1647
- } else {
1648
- await mkdir4(path7.dirname(abs), { recursive: true });
1649
- await writeFile10(abs, scaffold.content, "utf8");
1650
- written = true;
1664
+ let proposeCommand = scaffolds[0].proposeCommand;
1665
+ if (scaffolds.length > 1) {
1666
+ proposeCommand = buildProposeCommand(lesson, scaffolds.map((s) => s.runCommand).join(" && "));
1667
+ scaffolds = groups.map(
1668
+ (g) => scaffoldPostIncidentTest(lesson, {
1669
+ framework: frameworkFor(g.framework),
1670
+ baseDir: g.baseDir,
1671
+ proposeCommandOverride: proposeCommand
1672
+ })
1673
+ );
1674
+ }
1675
+ const results = [];
1676
+ for (const scaffold of scaffolds) {
1677
+ const abs = path7.isAbsolute(scaffold.relPath) ? scaffold.relPath : path7.resolve(ctx.paths.root, scaffold.relPath);
1678
+ let written = false;
1679
+ let alreadyExists = false;
1680
+ if (input.write) {
1681
+ if (existsSync17(abs)) {
1682
+ alreadyExists = true;
1683
+ } else {
1684
+ await mkdir4(path7.dirname(abs), { recursive: true });
1685
+ await writeFile10(abs, scaffold.content, "utf8");
1686
+ written = true;
1687
+ }
1651
1688
  }
1689
+ results.push({
1690
+ framework: scaffold.framework,
1691
+ path: scaffold.relPath,
1692
+ run_command: scaffold.runCommand,
1693
+ content: scaffold.content,
1694
+ written,
1695
+ already_exists: alreadyExists
1696
+ });
1652
1697
  }
1698
+ const first = results[0];
1699
+ const anyExisting = results.some((r) => r.already_exists);
1653
1700
  return {
1654
1701
  ok: true,
1655
1702
  memory_id: input.memory_id,
1656
- framework,
1657
- path: scaffold.relPath,
1658
- run_command: scaffold.runCommand,
1659
- propose_command: scaffold.proposeCommand,
1660
- content: scaffold.content,
1661
- written,
1662
- already_exists: alreadyExists,
1663
- 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."
1703
+ framework: first.framework,
1704
+ path: first.path,
1705
+ run_command: first.run_command,
1706
+ propose_command: proposeCommand,
1707
+ content: first.content,
1708
+ written: first.written,
1709
+ already_exists: first.already_exists,
1710
+ ...results.length > 1 ? { scaffolds: results } : {},
1711
+ 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.")
1664
1712
  };
1665
1713
  }
1666
1714
  var IngestFindingsInputSchema = {
@@ -2827,7 +2875,7 @@ function oneLine(value) {
2827
2875
  return value.replace(/\s+/g, " ").replace(/"/g, '\\"').trim().slice(0, 120);
2828
2876
  }
2829
2877
  function serverVersion() {
2830
- return true ? "0.38.0" : "dev";
2878
+ return true ? "0.39.0" : "dev";
2831
2879
  }
2832
2880
  var CodeMapInputSchema = {
2833
2881
  file: z21.string().optional().describe("Filter to files whose path contains this substring"),
@@ -4154,7 +4202,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
4154
4202
  };
4155
4203
  }
4156
4204
  var SERVER_NAME = "hivelore";
4157
- var SERVER_VERSION = "0.38.0";
4205
+ var SERVER_VERSION = "0.39.0";
4158
4206
  function jsonResult(data) {
4159
4207
  return {
4160
4208
  content: [
@@ -5104,6 +5152,7 @@ export {
5104
5152
  proposeSensor,
5105
5153
  memTried,
5106
5154
  detectTestFrameworkForPaths,
5155
+ detectTestFrameworksForAnchors,
5107
5156
  scaffoldTest,
5108
5157
  getBriefing,
5109
5158
  codeMapTool,
@@ -5129,4 +5178,4 @@ export {
5129
5178
  printHaiveMcpVersion,
5130
5179
  runHaiveMcpStdio
5131
5180
  };
5132
- //# sourceMappingURL=chunk-UOMGIXZN.js.map
5181
+ //# sourceMappingURL=chunk-I4VELI5K.js.map