@hivelore/cli 0.35.0 → 0.36.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-ZR7UPTRR.js";
13
+ } from "./chunk-EWJQ3YE7.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.0"}`;
3758
+ var HAIVE_GITHUB_ACTION_REF = `v${"0.36.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,
@@ -4715,6 +4715,7 @@ import "commander";
4715
4715
  import {
4716
4716
  DEFAULT_AUTO_PROMOTE_RULE,
4717
4717
  assessSensorHealth,
4718
+ sensorPromotedAtMap,
4718
4719
  buildFrontmatter as buildFrontmatter3,
4719
4720
  existingGateMissShas,
4720
4721
  findProjectRoot as findProjectRoot9,
@@ -4773,10 +4774,12 @@ function registerSync(program2) {
4773
4774
  let promoted = 0;
4774
4775
  let autoApproved = 0;
4775
4776
  let quarantined = 0;
4777
+ const quarantineCandidates = await loadMemoriesFromDir7(paths.memoriesDir);
4776
4778
  const sensorHealth = new Map(
4777
- assessSensorHealth(await loadSensorLedger(paths)).map((health) => [health.memory_id, health])
4779
+ assessSensorHealth(await loadSensorLedger(paths), /* @__PURE__ */ new Date(), {
4780
+ promotedAt: sensorPromotedAtMap(quarantineCandidates.map((m) => m.memory.frontmatter))
4781
+ }).map((health) => [health.memory_id, health])
4778
4782
  );
4779
- const quarantineCandidates = await loadMemoriesFromDir7(paths.memoriesDir);
4780
4783
  for (const { memory: memory2, filePath } of quarantineCandidates) {
4781
4784
  const sensor = memory2.frontmatter.sensor;
4782
4785
  const health = sensorHealth.get(memory2.frontmatter.id);
@@ -5261,7 +5264,8 @@ async function processGateMissWatch(root, paths, dryRun) {
5261
5264
  const proposals = proposeGateMissDrafts(
5262
5265
  readGitCommitsRange(root, plan.range),
5263
5266
  existingGateMissShas(await loadMemoriesFromDir7(paths.memoriesDir)),
5264
- gatePassedShas(await loadSensorLedger(paths))
5267
+ gatePassedShas(await loadSensorLedger(paths)),
5268
+ { pathExists: (rel) => existsSync14(path15.join(root, rel)) }
5265
5269
  );
5266
5270
  const ids = [];
5267
5271
  for (const proposal of proposals) {
@@ -7321,13 +7325,15 @@ import {
7321
7325
  loadPreventionEvents as loadPreventionEvents2,
7322
7326
  parseSince,
7323
7327
  readUsageEvents,
7328
+ renderPreventionReceiptShare,
7324
7329
  resolveHaivePaths as resolveHaivePaths27,
7325
7330
  usageLogSize
7326
7331
  } from "@hivelore/core";
7327
7332
  function registerStats(program2) {
7328
7333
  const stats = program2.command("stats").description("Show MCP tool-usage stats and prevention receipts.");
7329
- 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 () => {
7334
+ 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 () => {
7330
7335
  const opts = stats.opts();
7336
+ const sub = receiptCmd.opts();
7331
7337
  const root = findProjectRoot28(opts.dir);
7332
7338
  const paths = resolveHaivePaths27(root);
7333
7339
  const sinceRaw = stats.getOptionValueSource("since") === "default" ? "7d" : opts.since ?? "7d";
@@ -7338,7 +7344,8 @@ function registerStats(program2) {
7338
7344
  existsSync33(paths.memoriesDir) ? loadMemoriesFromDir11(paths.memoriesDir) : Promise.resolve([])
7339
7345
  ]);
7340
7346
  const receipt = buildPreventionReceipt(events, memories, usage, { since });
7341
- console.log(opts.json ? JSON.stringify(receipt, null, 2) : renderPreventionReceipt(receipt));
7347
+ const output = sub.share ? renderPreventionReceiptShare(receipt) : opts.json ? JSON.stringify(receipt, null, 2) : renderPreventionReceipt(receipt);
7348
+ console.log(output);
7342
7349
  });
7343
7350
  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(
7344
7351
  "--export-report <path>",
@@ -8574,6 +8581,7 @@ import "commander";
8574
8581
  import {
8575
8582
  codeMapPath as codeMapPath2,
8576
8583
  assessSensorHealth as assessSensorHealth2,
8584
+ sensorPromotedAtMap as sensorPromotedAtMap2,
8577
8585
  countSourceFilesOnDisk,
8578
8586
  extractReferencedPaths,
8579
8587
  sensorPatternBrittleness,
@@ -8832,7 +8840,9 @@ function registerDoctor(program2) {
8832
8840
  fix: "Make the pattern discriminating (add an 'absent' companion), or demote: `hivelore sensors promote <id> --severity warn`"
8833
8841
  });
8834
8842
  }
8835
- const sensorHealth = assessSensorHealth2(await loadSensorLedger2(paths));
8843
+ const sensorHealth = assessSensorHealth2(await loadSensorLedger2(paths), /* @__PURE__ */ new Date(), {
8844
+ promotedAt: sensorPromotedAtMap2(memories.map((m) => m.memory.frontmatter))
8845
+ });
8836
8846
  for (const health of sensorHealth.filter((h) => h.quarantine_pending)) {
8837
8847
  const last = health.flaps.at(-1);
8838
8848
  findings.push({
@@ -8949,7 +8959,7 @@ function registerDoctor(program2) {
8949
8959
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `hivelore init` without --manual)."
8950
8960
  });
8951
8961
  }
8952
- findings.push(...await collectInstallFindings(root, "0.35.0"));
8962
+ findings.push(...await collectInstallFindings(root, "0.36.0"));
8953
8963
  findings.push(...await collectToolchainFindings(root));
8954
8964
  try {
8955
8965
  const legacyRaw = execSync("haive-mcp --version", {
@@ -8957,7 +8967,7 @@ function registerDoctor(program2) {
8957
8967
  timeout: 3e3,
8958
8968
  stdio: ["ignore", "pipe", "ignore"]
8959
8969
  }).trim();
8960
- const cliVersion = "0.35.0";
8970
+ const cliVersion = "0.36.0";
8961
8971
  if (legacyRaw && legacyRaw !== cliVersion) {
8962
8972
  findings.push({
8963
8973
  severity: "warn",
@@ -9697,6 +9707,7 @@ import {
9697
9707
  antiPatternGateParams as antiPatternGateParams2,
9698
9708
  appendSensorEvaluations,
9699
9709
  assessSensorHealth as assessSensorHealth3,
9710
+ sensorPromotedAtMap as sensorPromotedAtMap3,
9700
9711
  assessBootstrapState,
9701
9712
  isSensorScannablePath,
9702
9713
  findProjectRoot as findProjectRoot37,
@@ -9715,6 +9726,7 @@ import {
9715
9726
  readRecentBriefingMarker,
9716
9727
  recordPreventionHits,
9717
9728
  resolveBriefingBudget as resolveBriefingBudget2,
9729
+ incidentSuffix,
9718
9730
  resolveHaivePaths as resolveHaivePaths35,
9719
9731
  runSensors,
9720
9732
  saveConfig as saveConfig3,
@@ -9864,7 +9876,8 @@ async function executeCommandSensor(spec, root) {
9864
9876
  command: spec.command,
9865
9877
  kind: spec.kind,
9866
9878
  severity: spec.severity,
9867
- message: spec.message
9879
+ message: spec.message,
9880
+ ...spec.incident ? { incident: spec.incident } : {}
9868
9881
  };
9869
9882
  try {
9870
9883
  const { stdout, stderr } = await exec2("bash", ["-c", spec.command], {
@@ -10657,7 +10670,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
10657
10670
  findings: [{ severity: "info", code: "enforcement-off", message: "Hivelore enforcement is disabled." }]
10658
10671
  });
10659
10672
  }
10660
- findings.push(...await inspectIntegrationVersions(root, "0.35.0"));
10673
+ findings.push(...await inspectIntegrationVersions(root, "0.36.0"));
10661
10674
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
10662
10675
  const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
10663
10676
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent Hivelore briefing marker exists." } : {
@@ -11012,7 +11025,7 @@ async function runSensorGate(paths, diff, stage) {
11012
11025
  findings.push({
11013
11026
  severity: "error",
11014
11027
  code: "sensor-block",
11015
- message: `Block sensor fired \u2014 ${hit.memory_id}: ${hit.message}${where}`,
11028
+ message: `Block sensor fired \u2014 ${hit.memory_id}: ${hit.message}${where}${incidentSuffix(hit.sensor.incident)}`,
11016
11029
  fix: "Remove the flagged pattern, or run `hivelore sensors check` to inspect the match.",
11017
11030
  impact: 45,
11018
11031
  memory_ids: [hit.memory_id]
@@ -11021,7 +11034,7 @@ async function runSensorGate(paths, diff, stage) {
11021
11034
  findings.push({
11022
11035
  severity: "warn",
11023
11036
  code: "sensor-warn",
11024
- message: `Sensor flagged ${hit.memory_id}: ${hit.message}${where}`,
11037
+ message: `Sensor flagged ${hit.memory_id}: ${hit.message}${where}${incidentSuffix(hit.sensor.incident)}`,
11025
11038
  fix: "Review the flagged line; `hivelore sensors check` shows the matched code.",
11026
11039
  impact: 5,
11027
11040
  memory_ids: [hit.memory_id]
@@ -11045,7 +11058,10 @@ async function runSensorGate(paths, diff, stage) {
11045
11058
  }, { exit_code: run.exit_code, duration_ms: run.duration_ms }));
11046
11059
  }
11047
11060
  const prior = await loadSensorLedger3(paths);
11048
- const health = new Map(assessSensorHealth3([...prior, ...ledgerRows]).map((h) => [h.memory_id, h]));
11061
+ const promotedAt = sensorPromotedAtMap3(scannable.map((m) => m.frontmatter));
11062
+ const health = new Map(
11063
+ assessSensorHealth3([...prior, ...ledgerRows], /* @__PURE__ */ new Date(), { promotedAt }).map((h) => [h.memory_id, h])
11064
+ );
11049
11065
  for (const run of runs) {
11050
11066
  const sensorHealth = health.get(run.memory_id);
11051
11067
  const quarantined = sensorHealth?.quarantine_pending === true;
@@ -11080,7 +11096,7 @@ ${run.output_tail}` : "";
11080
11096
  findings.push({
11081
11097
  severity: "error",
11082
11098
  code: "sensor-block",
11083
- message: `Block ${run.kind} sensor fired \u2014 ${run.memory_id}: ${run.message}
11099
+ message: `Block ${run.kind} sensor fired \u2014 ${run.memory_id}: ${run.message}${incidentSuffix(run.incident)}
11084
11100
  command: ${run.command} (exit ${run.exit_code}, ${run.duration_ms}ms)${outputBlock}`,
11085
11101
  fix: "Fix the behaviour the command checks, or run `hivelore sensors check --commands` to inspect it.",
11086
11102
  impact: 45,
@@ -11090,7 +11106,7 @@ command: ${run.command} (exit ${run.exit_code}, ${run.duration_ms}ms)${outputBlo
11090
11106
  findings.push({
11091
11107
  severity: "warn",
11092
11108
  code: "sensor-warn",
11093
- message: `${run.kind} sensor flagged ${run.memory_id}: ${run.message} (exit ${run.exit_code})${outputBlock}`,
11109
+ message: `${run.kind} sensor flagged ${run.memory_id}: ${run.message}${incidentSuffix(run.incident)} (exit ${run.exit_code})${outputBlock}`,
11094
11110
  fix: "Review the failing command; `hivelore sensors check --commands` re-runs it.",
11095
11111
  impact: 5,
11096
11112
  memory_ids: [run.memory_id]
@@ -11784,7 +11800,10 @@ jobs:
11784
11800
  "
11785
11801
 
11786
11802
  Weekly total: **" + ($receipt.total|tostring) + "** refused; previous window: **" +
11787
- ($receipt.previous_total|tostring) + "**."
11803
+ ($receipt.previous_total|tostring) + "**." +
11804
+ "
11805
+
11806
+ <sub>\u{1F6E1}\uFE0F Generated by [Hivelore](https://github.com/Doucs91/hivelore) \u2014 the deterministic policy gate for agent-written code.</sub>"
11788
11807
  ')" || exit 0
11789
11808
  comments="$(gh api "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" --paginate 2>/dev/null)" || exit 0
11790
11809
  comment_id="$(printf '%s' "$comments" | jq -r '.[] | select(.body | contains("<!-- haive:prevention-receipt -->")) | .id' | head -1)"
@@ -11799,6 +11818,30 @@ Weekly total: **" + ($receipt.total|tostring) + "** refused; previous window: **
11799
11818
  # haive:enforcement-workflow:end
11800
11819
  `;
11801
11820
  }
11821
+ var CONTENT_CATCH_CODES = /* @__PURE__ */ new Set(["sensor-block", "precommit-policy-block"]);
11822
+ var SETUP_GATE_CODES = /* @__PURE__ */ new Set([
11823
+ "briefing-missing",
11824
+ "session-recap-missing",
11825
+ "decision-coverage-missing",
11826
+ "bootstrap-incomplete",
11827
+ "enforcement-score-below-threshold"
11828
+ ]);
11829
+ function printBlockHeadline(report) {
11830
+ const blocking = report.categories?.blocking ?? report.findings.filter((f) => f.severity === "error");
11831
+ if (blocking.length === 0) return;
11832
+ const catches = blocking.filter((f) => CONTENT_CATCH_CODES.has(f.code));
11833
+ console.log();
11834
+ if (catches.length > 0) {
11835
+ console.log(ui.red(ui.bold("\u{1F6E1}\uFE0F A documented lesson refused this commit \u2014 about the change you just made:")));
11836
+ for (const c of catches) {
11837
+ const id = c.memory_ids?.[0] ?? c.code;
11838
+ console.log(` ${ui.red("\u2022")} ${ui.bold(id)} ${c.message.split("\n")[0]}`);
11839
+ }
11840
+ } else if (blocking.every((f) => SETUP_GATE_CODES.has(f.code))) {
11841
+ console.log(ui.yellow(ui.bold("\u2699 Setup gate \u2014 about your repo's baseline, not the change you just made.")));
11842
+ console.log(ui.dim(" Fill the knowledge layer once (bootstrap / load a briefing); later commits pass silently."));
11843
+ }
11844
+ }
11802
11845
  function printReport(report, json, explain = false) {
11803
11846
  if (json) {
11804
11847
  console.log(JSON.stringify(report, null, 2));
@@ -11807,6 +11850,7 @@ function printReport(report, json, explain = false) {
11807
11850
  console.log(ui.bold(`Hivelore enforcement \u2014 ${report.mode}${report.actor ? ` \xB7 ${report.actor}` : ""}`));
11808
11851
  console.log(ui.dim(` root: ${report.root}`));
11809
11852
  console.log(ui.dim(` score: ${report.score.score}% / threshold ${report.score.threshold}%`));
11853
+ if (report.should_block) printBlockHeadline(report);
11810
11854
  if (explain) {
11811
11855
  printFindingGroup("Blocking", report.categories.blocking, "error");
11812
11856
  printFindingGroup("Review", report.categories.review, "warn");
@@ -12108,6 +12152,7 @@ import {
12108
12152
  extractSensorExamples,
12109
12153
  appendSensorEvaluations as appendSensorEvaluations2,
12110
12154
  assessSensorHealth as assessSensorHealth4,
12155
+ sensorPromotedAtMap as sensorPromotedAtMap4,
12111
12156
  findProjectRoot as findProjectRoot39,
12112
12157
  isRetiredMemory as isRetiredMemory3,
12113
12158
  judgeProposedSensor,
@@ -12203,7 +12248,10 @@ function registerSensors(program2) {
12203
12248
  }, { exit_code: run.exit_code, duration_ms: run.duration_ms }));
12204
12249
  }
12205
12250
  const prior = await loadSensorLedger4(paths);
12206
- const health = new Map(assessSensorHealth4([...prior, ...ledgerRows]).map((h) => [h.memory_id, h]));
12251
+ const promotedAt = sensorPromotedAtMap4(allSensorMemories.map((m) => m.frontmatter));
12252
+ const health = new Map(
12253
+ assessSensorHealth4([...prior, ...ledgerRows], /* @__PURE__ */ new Date(), { promotedAt }).map((h) => [h.memory_id, h])
12254
+ );
12207
12255
  for (const run of runs) {
12208
12256
  const quarantined = health.get(run.memory_id)?.quarantine_pending === true;
12209
12257
  if (run.status === "failed") {
@@ -12336,7 +12384,9 @@ function registerSensors(program2) {
12336
12384
  const next = {
12337
12385
  frontmatter: {
12338
12386
  ...found.memory.frontmatter,
12339
- sensor: { ...sensor, severity }
12387
+ // promoted_at makes health assessment ignore pre-promotion ledger rows: promoting is the
12388
+ // human's assertion the oracle was fixed, so old flaps must not re-quarantine it.
12389
+ sensor: severity === "block" ? { ...sensor, severity, promoted_at: (/* @__PURE__ */ new Date()).toISOString() } : { ...sensor, severity }
12340
12390
  },
12341
12391
  body: severity === "block" ? withoutQuarantineNote(found.memory.body) : found.memory.body
12342
12392
  };
@@ -12347,7 +12397,7 @@ function registerSensors(program2) {
12347
12397
  });
12348
12398
  sensors.command("propose").description(
12349
12399
  "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 })'"
12350
- ).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) => {
12400
+ ).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) => {
12351
12401
  if (opts.kind === "shell" || opts.kind === "test") {
12352
12402
  if (!opts.command?.trim()) {
12353
12403
  ui.error("--kind shell|test requires --command.");
@@ -12355,7 +12405,7 @@ function registerSensors(program2) {
12355
12405
  return;
12356
12406
  }
12357
12407
  const root2 = findProjectRoot39(opts.dir);
12358
- const { proposeSensor } = await import("./server-2FUYM6KI.js");
12408
+ const { proposeSensor } = await import("./server-JFLUYWUB.js");
12359
12409
  const out = await proposeSensor(
12360
12410
  {
12361
12411
  memory_id: id,
@@ -12367,6 +12417,7 @@ function registerSensors(program2) {
12367
12417
  bad_example: void 0,
12368
12418
  severity: opts.severity === "warn" ? "warn" : "block",
12369
12419
  message: opts.message,
12420
+ incident: opts.incident,
12370
12421
  flags: void 0,
12371
12422
  paths: opts.paths ? opts.paths.split(",").map((p) => p.trim()).filter(Boolean) : []
12372
12423
  },
@@ -12418,6 +12469,7 @@ function registerSensors(program2) {
12418
12469
  ...opts.flags ? { flags: opts.flags } : {},
12419
12470
  paths: anchorPaths,
12420
12471
  message: opts.message?.trim() || deriveProposedMessage(found.memory.body, opts.pattern, opts.absent),
12472
+ ...opts.incident?.trim() ? { incident: opts.incident.trim() } : {},
12421
12473
  severity,
12422
12474
  autogen: false,
12423
12475
  last_fired: null
@@ -13192,7 +13244,7 @@ function registerBridges(program2) {
13192
13244
 
13193
13245
  // src/index.ts
13194
13246
  var program = new Command48();
13195
- program.name("hivelore").description("Hivelore - repo-native memory and context policy for coding-agent harnesses").version("0.35.0").option("--advanced", "show maintenance and experimental commands in help").showSuggestionAfterError(true);
13247
+ program.name("hivelore").description("Hivelore - the deterministic policy gate for agent-written code (rules live as repo-native team memory)").version("0.36.0").option("--advanced", "show maintenance and experimental commands in help").showSuggestionAfterError(true);
13196
13248
  registerInit(program);
13197
13249
  registerResolveProject(program);
13198
13250
  registerEnforce(program);