@hivelore/cli 0.35.1 → 0.37.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
@@ -10,7 +10,7 @@ import {
10
10
  preCommitCheck,
11
11
  readPresumedCorrectTargets,
12
12
  runHaiveMcpStdio
13
- } from "./chunk-QQANGHJK.js";
13
+ } from "./chunk-VLRQ4MRO.js";
14
14
  import {
15
15
  registerMemoryPending
16
16
  } from "./chunk-OYJKHD22.js";
@@ -3755,7 +3755,7 @@ ${SEED_FOOTER(stack)}` });
3755
3755
 
3756
3756
  // src/commands/init.ts
3757
3757
  var execFileAsync = promisify2(execFile2);
3758
- var HAIVE_GITHUB_ACTION_REF = `v${"0.35.1"}`;
3758
+ var HAIVE_GITHUB_ACTION_REF = `v${"0.37.0"}`;
3759
3759
  var PROJECT_CONTEXT_TEMPLATE = `# Project context
3760
3760
 
3761
3761
  > Generated by \`hivelore init\`. Run \`hivelore init --bootstrap\` to auto-fill from your codebase,
@@ -6090,6 +6090,7 @@ function registerMemoryTried(memory2) {
6090
6090
  } else if (result.hint) {
6091
6091
  ui.warn(result.hint);
6092
6092
  }
6093
+ ui.info(` Prefer a real test? \`hivelore sensors scaffold ${result.id}\` generates a pending test + the wiring command.`);
6093
6094
  });
6094
6095
  }
6095
6096
 
@@ -7325,13 +7326,15 @@ import {
7325
7326
  loadPreventionEvents as loadPreventionEvents2,
7326
7327
  parseSince,
7327
7328
  readUsageEvents,
7329
+ renderPreventionReceiptShare,
7328
7330
  resolveHaivePaths as resolveHaivePaths27,
7329
7331
  usageLogSize
7330
7332
  } from "@hivelore/core";
7331
7333
  function registerStats(program2) {
7332
7334
  const stats = program2.command("stats").description("Show MCP tool-usage stats and prevention receipts.");
7333
- stats.command("receipt").description("Show documented mistakes refused by the gate over a time window").addHelpText("after", "\nParent options also apply: --since <window> (default 7d here), --json, --dir <dir>.").action(async () => {
7335
+ const receiptCmd = stats.command("receipt").description("Show documented mistakes refused by the gate over a time window").option("--share", "emit a Markdown block ready to paste into Slack or a PR (with attribution)", false).addHelpText("after", "\nParent options also apply: --since <window> (default 7d here), --json, --dir <dir>.").action(async () => {
7334
7336
  const opts = stats.opts();
7337
+ const sub = receiptCmd.opts();
7335
7338
  const root = findProjectRoot28(opts.dir);
7336
7339
  const paths = resolveHaivePaths27(root);
7337
7340
  const sinceRaw = stats.getOptionValueSource("since") === "default" ? "7d" : opts.since ?? "7d";
@@ -7342,7 +7345,8 @@ function registerStats(program2) {
7342
7345
  existsSync33(paths.memoriesDir) ? loadMemoriesFromDir11(paths.memoriesDir) : Promise.resolve([])
7343
7346
  ]);
7344
7347
  const receipt = buildPreventionReceipt(events, memories, usage, { since });
7345
- console.log(opts.json ? JSON.stringify(receipt, null, 2) : renderPreventionReceipt(receipt));
7348
+ const output = sub.share ? renderPreventionReceiptShare(receipt) : opts.json ? JSON.stringify(receipt, null, 2) : renderPreventionReceipt(receipt);
7349
+ console.log(output);
7346
7350
  });
7347
7351
  stats.option("--since <window>", "ISO date or relative (e.g. '7d', '24h', '30m')", "30d").option("--json", "emit JSON instead of human-readable output", false).option("--memory-hits", "show top-read memories (which mems are actually being used)", false).option(
7348
7352
  "--export-report <path>",
@@ -8956,7 +8960,7 @@ function registerDoctor(program2) {
8956
8960
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `hivelore init` without --manual)."
8957
8961
  });
8958
8962
  }
8959
- findings.push(...await collectInstallFindings(root, "0.35.1"));
8963
+ findings.push(...await collectInstallFindings(root, "0.37.0"));
8960
8964
  findings.push(...await collectToolchainFindings(root));
8961
8965
  try {
8962
8966
  const legacyRaw = execSync("haive-mcp --version", {
@@ -8964,7 +8968,7 @@ function registerDoctor(program2) {
8964
8968
  timeout: 3e3,
8965
8969
  stdio: ["ignore", "pipe", "ignore"]
8966
8970
  }).trim();
8967
- const cliVersion = "0.35.1";
8971
+ const cliVersion = "0.37.0";
8968
8972
  if (legacyRaw && legacyRaw !== cliVersion) {
8969
8973
  findings.push({
8970
8974
  severity: "warn",
@@ -9723,6 +9727,7 @@ import {
9723
9727
  readRecentBriefingMarker,
9724
9728
  recordPreventionHits,
9725
9729
  resolveBriefingBudget as resolveBriefingBudget2,
9730
+ incidentSuffix,
9726
9731
  resolveHaivePaths as resolveHaivePaths35,
9727
9732
  runSensors,
9728
9733
  saveConfig as saveConfig3,
@@ -9872,7 +9877,8 @@ async function executeCommandSensor(spec, root) {
9872
9877
  command: spec.command,
9873
9878
  kind: spec.kind,
9874
9879
  severity: spec.severity,
9875
- message: spec.message
9880
+ message: spec.message,
9881
+ ...spec.incident ? { incident: spec.incident } : {}
9876
9882
  };
9877
9883
  try {
9878
9884
  const { stdout, stderr } = await exec2("bash", ["-c", spec.command], {
@@ -10665,7 +10671,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
10665
10671
  findings: [{ severity: "info", code: "enforcement-off", message: "Hivelore enforcement is disabled." }]
10666
10672
  });
10667
10673
  }
10668
- findings.push(...await inspectIntegrationVersions(root, "0.35.1"));
10674
+ findings.push(...await inspectIntegrationVersions(root, "0.37.0"));
10669
10675
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
10670
10676
  const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
10671
10677
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent Hivelore briefing marker exists." } : {
@@ -11020,7 +11026,7 @@ async function runSensorGate(paths, diff, stage) {
11020
11026
  findings.push({
11021
11027
  severity: "error",
11022
11028
  code: "sensor-block",
11023
- message: `Block sensor fired \u2014 ${hit.memory_id}: ${hit.message}${where}`,
11029
+ message: `Block sensor fired \u2014 ${hit.memory_id}: ${hit.message}${where}${incidentSuffix(hit.sensor.incident)}`,
11024
11030
  fix: "Remove the flagged pattern, or run `hivelore sensors check` to inspect the match.",
11025
11031
  impact: 45,
11026
11032
  memory_ids: [hit.memory_id]
@@ -11029,7 +11035,7 @@ async function runSensorGate(paths, diff, stage) {
11029
11035
  findings.push({
11030
11036
  severity: "warn",
11031
11037
  code: "sensor-warn",
11032
- message: `Sensor flagged ${hit.memory_id}: ${hit.message}${where}`,
11038
+ message: `Sensor flagged ${hit.memory_id}: ${hit.message}${where}${incidentSuffix(hit.sensor.incident)}`,
11033
11039
  fix: "Review the flagged line; `hivelore sensors check` shows the matched code.",
11034
11040
  impact: 5,
11035
11041
  memory_ids: [hit.memory_id]
@@ -11091,7 +11097,7 @@ ${run.output_tail}` : "";
11091
11097
  findings.push({
11092
11098
  severity: "error",
11093
11099
  code: "sensor-block",
11094
- message: `Block ${run.kind} sensor fired \u2014 ${run.memory_id}: ${run.message}
11100
+ message: `Block ${run.kind} sensor fired \u2014 ${run.memory_id}: ${run.message}${incidentSuffix(run.incident)}
11095
11101
  command: ${run.command} (exit ${run.exit_code}, ${run.duration_ms}ms)${outputBlock}`,
11096
11102
  fix: "Fix the behaviour the command checks, or run `hivelore sensors check --commands` to inspect it.",
11097
11103
  impact: 45,
@@ -11101,7 +11107,7 @@ command: ${run.command} (exit ${run.exit_code}, ${run.duration_ms}ms)${outputBlo
11101
11107
  findings.push({
11102
11108
  severity: "warn",
11103
11109
  code: "sensor-warn",
11104
- message: `${run.kind} sensor flagged ${run.memory_id}: ${run.message} (exit ${run.exit_code})${outputBlock}`,
11110
+ message: `${run.kind} sensor flagged ${run.memory_id}: ${run.message}${incidentSuffix(run.incident)} (exit ${run.exit_code})${outputBlock}`,
11105
11111
  fix: "Review the failing command; `hivelore sensors check --commands` re-runs it.",
11106
11112
  impact: 5,
11107
11113
  memory_ids: [run.memory_id]
@@ -11795,7 +11801,10 @@ jobs:
11795
11801
  "
11796
11802
 
11797
11803
  Weekly total: **" + ($receipt.total|tostring) + "** refused; previous window: **" +
11798
- ($receipt.previous_total|tostring) + "**."
11804
+ ($receipt.previous_total|tostring) + "**." +
11805
+ "
11806
+
11807
+ <sub>\u{1F6E1}\uFE0F Generated by [Hivelore](https://github.com/Doucs91/hivelore) \u2014 the deterministic policy gate for agent-written code.</sub>"
11799
11808
  ')" || exit 0
11800
11809
  comments="$(gh api "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" --paginate 2>/dev/null)" || exit 0
11801
11810
  comment_id="$(printf '%s' "$comments" | jq -r '.[] | select(.body | contains("<!-- haive:prevention-receipt -->")) | .id' | head -1)"
@@ -11810,6 +11819,30 @@ Weekly total: **" + ($receipt.total|tostring) + "** refused; previous window: **
11810
11819
  # haive:enforcement-workflow:end
11811
11820
  `;
11812
11821
  }
11822
+ var CONTENT_CATCH_CODES = /* @__PURE__ */ new Set(["sensor-block", "precommit-policy-block"]);
11823
+ var SETUP_GATE_CODES = /* @__PURE__ */ new Set([
11824
+ "briefing-missing",
11825
+ "session-recap-missing",
11826
+ "decision-coverage-missing",
11827
+ "bootstrap-incomplete",
11828
+ "enforcement-score-below-threshold"
11829
+ ]);
11830
+ function printBlockHeadline(report) {
11831
+ const blocking = report.categories?.blocking ?? report.findings.filter((f) => f.severity === "error");
11832
+ if (blocking.length === 0) return;
11833
+ const catches = blocking.filter((f) => CONTENT_CATCH_CODES.has(f.code));
11834
+ console.log();
11835
+ if (catches.length > 0) {
11836
+ console.log(ui.red(ui.bold("\u{1F6E1}\uFE0F A documented lesson refused this commit \u2014 about the change you just made:")));
11837
+ for (const c of catches) {
11838
+ const id = c.memory_ids?.[0] ?? c.code;
11839
+ console.log(` ${ui.red("\u2022")} ${ui.bold(id)} ${c.message.split("\n")[0]}`);
11840
+ }
11841
+ } else if (blocking.every((f) => SETUP_GATE_CODES.has(f.code))) {
11842
+ console.log(ui.yellow(ui.bold("\u2699 Setup gate \u2014 about your repo's baseline, not the change you just made.")));
11843
+ console.log(ui.dim(" Fill the knowledge layer once (bootstrap / load a briefing); later commits pass silently."));
11844
+ }
11845
+ }
11813
11846
  function printReport(report, json, explain = false) {
11814
11847
  if (json) {
11815
11848
  console.log(JSON.stringify(report, null, 2));
@@ -11818,6 +11851,7 @@ function printReport(report, json, explain = false) {
11818
11851
  console.log(ui.bold(`Hivelore enforcement \u2014 ${report.mode}${report.actor ? ` \xB7 ${report.actor}` : ""}`));
11819
11852
  console.log(ui.dim(` root: ${report.root}`));
11820
11853
  console.log(ui.dim(` score: ${report.score.score}% / threshold ${report.score.threshold}%`));
11854
+ if (report.should_block) printBlockHeadline(report);
11821
11855
  if (explain) {
11822
11856
  printFindingGroup("Blocking", report.categories.blocking, "error");
11823
11857
  printFindingGroup("Review", report.categories.review, "warn");
@@ -12126,10 +12160,13 @@ import {
12126
12160
  loadConfig as loadConfig13,
12127
12161
  loadSensorLedger as loadSensorLedger4,
12128
12162
  loadMemoriesFromDir as loadMemoriesFromDir15,
12163
+ parseLessonFields,
12129
12164
  recordPreventionHits as recordPreventionHits2,
12130
12165
  resolveHaivePaths as resolveHaivePaths36,
12131
12166
  runSensors as runSensors2,
12167
+ scaffoldPostIncidentTest,
12132
12168
  selectCommandSensors as selectCommandSensors2,
12169
+ TEST_FRAMEWORKS,
12133
12170
  sensorPatternBrittleness as sensorPatternBrittleness2,
12134
12171
  sensorSelfCheck as sensorSelfCheck2,
12135
12172
  sensorAppliesToPath as sensorAppliesToPath3,
@@ -12364,7 +12401,7 @@ function registerSensors(program2) {
12364
12401
  });
12365
12402
  sensors.command("propose").description(
12366
12403
  "Propose a discriminating sensor for a memory \u2014 you write the pattern, Hivelore validates it before\n trusting it to block. Mirrors the MCP `propose_sensor` tool (the agent-authored path).\n\n A `block` proposal is accepted ONLY if it is not brittle, stays SILENT on the current code,\n and FIRES on the bad example. Rejected proposals are not written \u2014 fix and re-run.\n\n Example:\n hivelore sensors propose <memory-id> \\\n --pattern 'stripe\\.paymentIntents\\.create' --absent 'idempotencyKey' \\\n --bad-example 'stripe.paymentIntents.create({ amount })'"
12367
- ).argument("<memory-id>", "memory id to attach the sensor to").option("--kind <kind>", "regex (default) | shell | test \u2014 command kinds route the team's own oracle to this lesson", "regex").option("--pattern <regex>", "kind=regex: regex matching the FAULTY usage").option("--command <cmd>", "kind=shell|test: command the gate runs when the diff touches the sensor's paths").option("--timeout <ms>", "kind=shell|test: max runtime in ms (default 120000)").option("--absent <regex>", "regex for the CORRECT-usage marker (makes it discriminate)").option("--bad-example <code>", "a snippet that SHOULD match (else examples are read from the lesson)").option("--severity <severity>", "block | warn", "block").option("--message <text>", "fix message shown when it fires").option("--flags <flags>", "regex flags (e.g. i)").option("--paths <csv>", "override scope paths (defaults to the memory anchors)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
12404
+ ).argument("<memory-id>", "memory id to attach the sensor to").option("--kind <kind>", "regex (default) | shell | test \u2014 command kinds route the team's own oracle to this lesson", "regex").option("--pattern <regex>", "kind=regex: regex matching the FAULTY usage").option("--command <cmd>", "kind=shell|test: command the gate runs when the diff touches the sensor's paths").option("--timeout <ms>", "kind=shell|test: max runtime in ms (default 120000)").option("--absent <regex>", "regex for the CORRECT-usage marker (makes it discriminate)").option("--bad-example <code>", "a snippet that SHOULD match (else examples are read from the lesson)").option("--severity <severity>", "block | warn", "block").option("--message <text>", "fix message shown when it fires").option("--incident <ref>", "provenance: the incident this sensor guards (e.g. 'prod #442') \u2014 shown when it fires and in the receipt").option("--flags <flags>", "regex flags (e.g. i)").option("--paths <csv>", "override scope paths (defaults to the memory anchors)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
12368
12405
  if (opts.kind === "shell" || opts.kind === "test") {
12369
12406
  if (!opts.command?.trim()) {
12370
12407
  ui.error("--kind shell|test requires --command.");
@@ -12372,7 +12409,7 @@ function registerSensors(program2) {
12372
12409
  return;
12373
12410
  }
12374
12411
  const root2 = findProjectRoot39(opts.dir);
12375
- const { proposeSensor } = await import("./server-W5Q35K7Q.js");
12412
+ const { proposeSensor } = await import("./server-J6TDFG2C.js");
12376
12413
  const out = await proposeSensor(
12377
12414
  {
12378
12415
  memory_id: id,
@@ -12384,6 +12421,7 @@ function registerSensors(program2) {
12384
12421
  bad_example: void 0,
12385
12422
  severity: opts.severity === "warn" ? "warn" : "block",
12386
12423
  message: opts.message,
12424
+ incident: opts.incident,
12387
12425
  flags: void 0,
12388
12426
  paths: opts.paths ? opts.paths.split(",").map((p) => p.trim()).filter(Boolean) : []
12389
12427
  },
@@ -12435,6 +12473,7 @@ function registerSensors(program2) {
12435
12473
  ...opts.flags ? { flags: opts.flags } : {},
12436
12474
  paths: anchorPaths,
12437
12475
  message: opts.message?.trim() || deriveProposedMessage(found.memory.body, opts.pattern, opts.absent),
12476
+ ...opts.incident?.trim() ? { incident: opts.incident.trim() } : {},
12438
12477
  severity,
12439
12478
  autogen: false,
12440
12479
  last_fired: null
@@ -12459,6 +12498,56 @@ function registerSensors(program2) {
12459
12498
  `self-check: silent on current=${verdict.self_check.silent_on_current}` + (verdict.self_check.fires_on_bad === null ? "; no bad example tested" : `; fires on bad=${verdict.self_check.fires_on_bad}`)
12460
12499
  );
12461
12500
  });
12501
+ sensors.command("scaffold").description(
12502
+ "Generate a PENDING post-incident test from a lesson (mem_tried/attempt/gotcha) \u2014 the on-ramp to\n a command sensor. Writes a test stub carrying the incident's provenance, then prints the exact\n `sensors propose --kind test` line to arm it once you've written the assertion. It never arms a\n sensor itself \u2014 propose_sensor stays the sole validated writer.\n\n Example:\n hivelore sensors scaffold 2026-07-03-attempt-refund-exceeds-capture --framework vitest"
12503
+ ).argument("<memory-id>", "lesson id to scaffold a test from").option("--framework <fw>", `test framework: ${TEST_FRAMEWORKS.join(" | ")} (auto-detected when omitted)`).option("--out <path>", "override the generated test file path (project-relative)").option("--stdout", "print the test to stdout instead of writing a file", false).option("--force", "overwrite an existing file at the target path", false).option("-d, --dir <dir>", "project root").action(async (id, opts) => {
12504
+ const root = findProjectRoot39(opts.dir);
12505
+ const paths = resolveHaivePaths36(root);
12506
+ const loaded = existsSync43(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
12507
+ const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
12508
+ if (!found) {
12509
+ ui.error(`No memory found with id ${id}`);
12510
+ process.exitCode = 1;
12511
+ return;
12512
+ }
12513
+ const framework = opts.framework ? normalizeFramework(opts.framework) : await detectTestFramework(root);
12514
+ if (!framework) {
12515
+ ui.error(`Unknown --framework "${opts.framework}". Use one of: ${TEST_FRAMEWORKS.join(", ")}.`);
12516
+ process.exitCode = 1;
12517
+ return;
12518
+ }
12519
+ const fields = parseLessonFields(found.memory.body);
12520
+ const fm = found.memory.frontmatter;
12521
+ const scaffold = scaffoldPostIncidentTest(
12522
+ {
12523
+ memoryId: id,
12524
+ title: fields.title || id,
12525
+ whyFailed: fields.whyFailed,
12526
+ instead: fields.instead,
12527
+ incident: fm.sensor?.incident,
12528
+ paths: fm.anchor.paths
12529
+ },
12530
+ { framework, outPath: opts.out }
12531
+ );
12532
+ if (opts.stdout) {
12533
+ console.log(scaffold.content);
12534
+ ui.info(`Arm it once written: ${scaffold.proposeCommand}`);
12535
+ return;
12536
+ }
12537
+ const abs = path41.isAbsolute(scaffold.relPath) ? scaffold.relPath : path41.resolve(root, scaffold.relPath);
12538
+ if (existsSync43(abs) && !opts.force) {
12539
+ ui.error(`${scaffold.relPath} already exists \u2014 pass --force to overwrite, or --out <path> for a different file.`);
12540
+ process.exitCode = 1;
12541
+ return;
12542
+ }
12543
+ await mkdir17(path41.dirname(abs), { recursive: true });
12544
+ await writeFile27(abs, scaffold.content, "utf8");
12545
+ ui.success(`Wrote ${framework} post-incident test \u2192 ${scaffold.relPath}`);
12546
+ ui.info(" 1. Fill in the assertion (RED on the incident, GREEN once fixed).");
12547
+ ui.info(` 2. Run it: ${scaffold.runCommand}`);
12548
+ ui.info(" 3. Arm it as a deterministic gate:");
12549
+ console.log(` ${scaffold.proposeCommand}`);
12550
+ });
12462
12551
  sensors.command("export").description("Export regex sensors into .ai/generated for external toolchains").option("--format <format>", "grep | eslint", "grep").option("--out-dir <dir>", "output directory", ".ai/generated").option("-d, --dir <dir>", "project root").action(async (opts) => {
12463
12552
  const format = opts.format ?? "grep";
12464
12553
  if (format !== "grep" && format !== "eslint") {
@@ -12548,6 +12637,31 @@ function renderGrepScript(rows) {
12548
12637
  function shellQuote(value) {
12549
12638
  return `'${value.replace(/'/g, "'\\''")}'`;
12550
12639
  }
12640
+ function normalizeFramework(input) {
12641
+ const v = input.trim().toLowerCase();
12642
+ if (v === "vitest") return "vitest";
12643
+ if (v === "jest") return "jest";
12644
+ if (v === "pytest" || v === "py" || v === "python") return "pytest";
12645
+ if (v === "go" || v === "gotest" || v === "go-test") return "gotest";
12646
+ return null;
12647
+ }
12648
+ async function detectTestFramework(root) {
12649
+ try {
12650
+ const pkgPath = path41.join(root, "package.json");
12651
+ if (existsSync43(pkgPath)) {
12652
+ const pkg = JSON.parse(await readFile20(pkgPath, "utf8"));
12653
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
12654
+ if (deps.vitest) return "vitest";
12655
+ if (deps.jest || deps["ts-jest"]) return "jest";
12656
+ }
12657
+ } catch {
12658
+ }
12659
+ if (existsSync43(path41.join(root, "go.mod"))) return "gotest";
12660
+ for (const signal of ["pyproject.toml", "setup.py", "pytest.ini", "requirements.txt", "tox.ini"]) {
12661
+ if (existsSync43(path41.join(root, signal))) return "pytest";
12662
+ }
12663
+ return "vitest";
12664
+ }
12551
12665
 
12552
12666
  // src/commands/ingest.ts
12553
12667
  import { existsSync as existsSync44 } from "fs";
@@ -13209,7 +13323,7 @@ function registerBridges(program2) {
13209
13323
 
13210
13324
  // src/index.ts
13211
13325
  var program = new Command48();
13212
- program.name("hivelore").description("Hivelore - repo-native memory and context policy for coding-agent harnesses").version("0.35.1").option("--advanced", "show maintenance and experimental commands in help").showSuggestionAfterError(true);
13326
+ program.name("hivelore").description("Hivelore - the deterministic policy gate for agent-written code (rules live as repo-native team memory)").version("0.37.0").option("--advanced", "show maintenance and experimental commands in help").showSuggestionAfterError(true);
13213
13327
  registerInit(program);
13214
13328
  registerResolveProject(program);
13215
13329
  registerEnforce(program);