@hiveai/cli 0.13.7 → 0.13.9

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
@@ -3019,7 +3019,7 @@ ${SEED_FOOTER(stack)}` });
3019
3019
  }
3020
3020
 
3021
3021
  // src/commands/init.ts
3022
- var HAIVE_GITHUB_ACTION_REF = `v${"0.13.7"}`;
3022
+ var HAIVE_GITHUB_ACTION_REF = `v${"0.13.9"}`;
3023
3023
  var PROJECT_CONTEXT_TEMPLATE = `# Project context
3024
3024
 
3025
3025
  > Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
@@ -4133,6 +4133,7 @@ import { z as z25 } from "zod";
4133
4133
  import { existsSync as existsSync25 } from "fs";
4134
4134
  import {
4135
4135
  addedLinesFromDiff,
4136
+ appendPreventionEvent,
4136
4137
  buildDocFrequency,
4137
4138
  CODE_STOPWORDS,
4138
4139
  deriveConfidence as deriveConfidence6,
@@ -4143,7 +4144,9 @@ import {
4143
4144
  loadUsageIndex as loadUsageIndex10,
4144
4145
  literalMatchesAnyToken as literalMatchesAnyToken3,
4145
4146
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths4,
4147
+ recordPrevention,
4146
4148
  runSensors,
4149
+ saveUsageIndex as saveUsageIndex4,
4147
4150
  sensorTargetsFromDiff,
4148
4151
  tokenizeQuery as tokenizeQuery3
4149
4152
  } from "@hiveai/core";
@@ -6669,6 +6672,22 @@ async function antiPatternsCheck(input, ctx) {
6669
6672
  };
6670
6673
  return score(b) - score(a);
6671
6674
  }).slice(0, input.limit);
6675
+ const strongCatches = warnings.filter(
6676
+ (w) => w.reasons.includes("sensor") || w.distinctive_literal === true || w.reasons.includes("anchor") && w.reasons.includes("literal")
6677
+ );
6678
+ if (strongCatches.length > 0) {
6679
+ const recordedIds = [];
6680
+ for (const w of strongCatches) if (recordPrevention(usage, w.id)) recordedIds.push(w.id);
6681
+ if (recordedIds.length > 0) {
6682
+ await saveUsageIndex4(ctx.paths, usage).catch(() => {
6683
+ });
6684
+ const at = (/* @__PURE__ */ new Date()).toISOString();
6685
+ for (const id of recordedIds) {
6686
+ await appendPreventionEvent(ctx.paths, { at, id, source: "anti-pattern" }).catch(() => {
6687
+ });
6688
+ }
6689
+ }
6690
+ }
6672
6691
  return {
6673
6692
  scanned: negative.length,
6674
6693
  warnings
@@ -7919,7 +7938,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
7919
7938
  };
7920
7939
  }
7921
7940
  var SERVER_NAME = "haive";
7922
- var SERVER_VERSION = "0.13.7";
7941
+ var SERVER_VERSION = "0.13.9";
7923
7942
  function jsonResult(data) {
7924
7943
  return {
7925
7944
  content: [
@@ -10688,7 +10707,7 @@ import {
10688
10707
  loadUsageIndex as loadUsageIndex18,
10689
10708
  recordRejection as recordRejection3,
10690
10709
  resolveHaivePaths as resolveHaivePaths23,
10691
- saveUsageIndex as saveUsageIndex4,
10710
+ saveUsageIndex as saveUsageIndex5,
10692
10711
  serializeMemory as serializeMemory19
10693
10712
  } from "@hiveai/core";
10694
10713
  function registerMemoryReject(memory2) {
@@ -10721,7 +10740,7 @@ function registerMemoryReject(memory2) {
10721
10740
  );
10722
10741
  const idx = await loadUsageIndex18(paths);
10723
10742
  recordRejection3(idx, id, opts.reason ?? null);
10724
- await saveUsageIndex4(paths, idx);
10743
+ await saveUsageIndex5(paths, idx);
10725
10744
  const u = idx.by_id[id];
10726
10745
  ui.success(
10727
10746
  `Rejected ${id} (status=rejected, ${u.rejected_count} rejection${u.rejected_count === 1 ? "" : "s"})`
@@ -10740,7 +10759,7 @@ import {
10740
10759
  findProjectRoot as findProjectRoot27,
10741
10760
  loadUsageIndex as loadUsageIndex19,
10742
10761
  resolveHaivePaths as resolveHaivePaths24,
10743
- saveUsageIndex as saveUsageIndex5
10762
+ saveUsageIndex as saveUsageIndex6
10744
10763
  } from "@hiveai/core";
10745
10764
  function registerMemoryRm(memory2) {
10746
10765
  memory2.command("delete <id>").alias("rm").description("Delete a memory file (and its usage entry by default). Mirrors MCP mem_delete. Alias: rm").option("-y, --yes", "skip the confirmation prompt").option("--keep-usage", "do not remove the usage.json entry").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
@@ -10774,7 +10793,7 @@ function registerMemoryRm(memory2) {
10774
10793
  const idx = await loadUsageIndex19(paths);
10775
10794
  if (idx.by_id[id]) {
10776
10795
  delete idx.by_id[id];
10777
- await saveUsageIndex5(paths, idx);
10796
+ await saveUsageIndex6(paths, idx);
10778
10797
  ui.info("Removed usage entry");
10779
10798
  }
10780
10799
  }
@@ -10984,7 +11003,7 @@ import {
10984
11003
  recordApplied as recordApplied2,
10985
11004
  recordRejection as recordRejection4,
10986
11005
  resolveHaivePaths as resolveHaivePaths28,
10987
- saveUsageIndex as saveUsageIndex6
11006
+ saveUsageIndex as saveUsageIndex7
10988
11007
  } from "@hiveai/core";
10989
11008
  function registerMemoryFeedback(memory2) {
10990
11009
  memory2.command("feedback <id>").description(
@@ -11013,7 +11032,7 @@ function registerMemoryFeedback(memory2) {
11013
11032
  const outcome = opts.applied ? "applied" : "rejected";
11014
11033
  if (opts.applied) recordApplied2(index, id);
11015
11034
  else recordRejection4(index, id, opts.reason ?? null);
11016
- await saveUsageIndex6(paths, index);
11035
+ await saveUsageIndex7(paths, index);
11017
11036
  const usage = getUsage19(index, id);
11018
11037
  const impact = computeImpact4(target.memory.frontmatter, usage);
11019
11038
  if (opts.json) {
@@ -13488,7 +13507,7 @@ function registerDoctor(program2) {
13488
13507
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
13489
13508
  });
13490
13509
  }
13491
- findings.push(...await collectInstallFindings(root, "0.13.7"));
13510
+ findings.push(...await collectInstallFindings(root, "0.13.9"));
13492
13511
  findings.push(...await collectToolchainFindings(root));
13493
13512
  try {
13494
13513
  const legacyRaw = execSync3("haive-mcp --version", {
@@ -13496,7 +13515,7 @@ function registerDoctor(program2) {
13496
13515
  timeout: 3e3,
13497
13516
  stdio: ["ignore", "pipe", "ignore"]
13498
13517
  }).trim();
13499
- const cliVersion = "0.13.7";
13518
+ const cliVersion = "0.13.9";
13500
13519
  if (legacyRaw && legacyRaw !== cliVersion) {
13501
13520
  findings.push({
13502
13521
  severity: "warn",
@@ -14656,6 +14675,16 @@ function registerEnforce(program2) {
14656
14675
  process.exit(2);
14657
14676
  }
14658
14677
  });
14678
+ enforce.command("commit-msg <msgfile>").description(
14679
+ "git commit-msg hook: block a CI-skip directive in a commit that also changes shippable code (GitHub scans the whole message and would skip CI for the entire push). `.ai/`-only sync commits are allowed."
14680
+ ).option("-d, --dir <dir>", "project root").action(async (msgfile, opts) => {
14681
+ const root = findProjectRoot52(opts.dir);
14682
+ const verdict = await checkCommitMessageSkipCi(root, msgfile);
14683
+ if (verdict.block) {
14684
+ ui.error(verdict.message);
14685
+ process.exit(1);
14686
+ }
14687
+ });
14659
14688
  enforce.command("session-start").description("Claude Code SessionStart hook: inject briefing and write a local briefing marker.").option("-d, --dir <dir>", "project root").option("--task <text>", "task text to rank memories").option("--source <name>", "marker source", "claude-session-start").option("--session-id <id>", "agent session id").action(async (opts) => {
14660
14689
  const payload = await readHookPayload();
14661
14690
  const root = resolveRoot(opts.dir, payload);
@@ -15095,7 +15124,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
15095
15124
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
15096
15125
  });
15097
15126
  }
15098
- findings.push(...await inspectIntegrationVersions(root, "0.13.7"));
15127
+ findings.push(...await inspectIntegrationVersions(root, "0.13.9"));
15099
15128
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
15100
15129
  const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
15101
15130
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
@@ -15589,6 +15618,21 @@ var SHIPPABLE_PATH_PREFIXES = [
15589
15618
  function isShippablePath(file) {
15590
15619
  return SHIPPABLE_PATH_PREFIXES.some((prefix) => file.startsWith(prefix)) || VERSION_FILES.includes(file);
15591
15620
  }
15621
+ var CI_SKIP_DIRECTIVE = /\[skip ci\]|\[ci skip\]|\[no ci\]|\[skip actions\]|\*\*\*NO_CI\*\*\*|skip-checks: *true/i;
15622
+ async function checkCommitMessageSkipCi(root, msgfile) {
15623
+ const file = path51.isAbsolute(msgfile) ? msgfile : path51.join(root, msgfile);
15624
+ const raw = await readFile23(file, "utf8").catch(() => "");
15625
+ const cleaned = raw.split("\n").filter((line) => !line.startsWith("#")).join("\n");
15626
+ if (!CI_SKIP_DIRECTIVE.test(cleaned)) return { block: false, message: "" };
15627
+ const staged = (await runCommand4("git", ["diff", "--cached", "--name-only"], root).catch(() => "")).split("\n").map((s) => s.trim()).filter(Boolean);
15628
+ const shippable = staged.filter(isShippablePath);
15629
+ if (shippable.length === 0) return { block: false, message: "" };
15630
+ return {
15631
+ block: true,
15632
+ message: "This commit message contains a CI-skip directive ([skip ci] / [ci skip] / [no ci]) but the commit changes shippable code:\n" + shippable.slice(0, 6).map((f) => ` - ${f}`).join("\n") + (shippable.length > 6 ? `
15633
+ \u2026and ${shippable.length - 6} more` : "") + "\nGitHub scans the whole commit message and would skip CI for the ENTIRE push \u2014 your code would land untested.\nFix: reword the message so it does not contain the literal directive (e.g. write 'skip-ci'), or move the\nskip-ci sync into a separate `.ai/`-only commit."
15634
+ };
15635
+ }
15592
15636
  async function inspectReleaseVersionState(root, upstream) {
15593
15637
  const localEntries = await Promise.all(VERSION_FILES.map(async (file) => [file, await readPackageVersion(root, file)]));
15594
15638
  const localVersions = new Map(localEntries);
@@ -15812,6 +15856,13 @@ haive enforce check --stage pre-commit --dir . || exit $?
15812
15856
  body: `#!/bin/sh
15813
15857
  ${ENFORCE_HOOK_MARKER}
15814
15858
  haive enforce check --stage pre-push --dir . || exit $?
15859
+ `
15860
+ },
15861
+ {
15862
+ name: "commit-msg",
15863
+ body: `#!/bin/sh
15864
+ ${ENFORCE_HOOK_MARKER}
15865
+ haive enforce commit-msg "$1" --dir . || exit $?
15815
15866
  `
15816
15867
  }
15817
15868
  ];
@@ -15831,7 +15882,7 @@ ${hook.body}`, "utf8");
15831
15882
  }
15832
15883
  await chmod2(file, 493);
15833
15884
  }
15834
- ui.success("Installed blocking git enforcement hooks: pre-commit, pre-push");
15885
+ ui.success("Installed blocking git enforcement hooks: pre-commit, pre-push, commit-msg");
15835
15886
  }
15836
15887
  async function installCiEnforcement(root) {
15837
15888
  const workflowPath = path51.join(root, ".github", "workflows", "haive-enforcement.yml");
@@ -16064,11 +16115,15 @@ import path53 from "path";
16064
16115
  import { promisify as promisify2 } from "util";
16065
16116
  import "commander";
16066
16117
  import {
16118
+ appendPreventionEvent as appendPreventionEvent2,
16067
16119
  findProjectRoot as findProjectRoot53,
16068
16120
  isRetiredMemory as isRetiredMemory3,
16069
16121
  loadMemoriesFromDir as loadMemoriesFromDir39,
16122
+ loadUsageIndex as loadUsageIndex29,
16123
+ recordPrevention as recordPrevention2,
16070
16124
  resolveHaivePaths as resolveHaivePaths49,
16071
16125
  runSensors as runSensors2,
16126
+ saveUsageIndex as saveUsageIndex8,
16072
16127
  sensorTargetsFromDiff as sensorTargetsFromDiff2,
16073
16128
  serializeMemory as serializeMemory27
16074
16129
  } from "@hiveai/core";
@@ -16103,6 +16158,21 @@ function registerSensors(program2) {
16103
16158
  const diff = opts.diffFile ? await readFile24(path53.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
16104
16159
  const targets = sensorTargetsFromDiff2(diff);
16105
16160
  const hits = runSensors2(memories, targets.length > 0 ? targets : [{ path: "", content: diff }]);
16161
+ const firedIds = [...new Set(hits.map((hit) => hit.memory_id))];
16162
+ if (firedIds.length > 0) {
16163
+ const usage = await loadUsageIndex29(paths);
16164
+ const recordedIds = [];
16165
+ for (const id of firedIds) if (recordPrevention2(usage, id)) recordedIds.push(id);
16166
+ if (recordedIds.length > 0) {
16167
+ await saveUsageIndex8(paths, usage).catch(() => {
16168
+ });
16169
+ const at = (/* @__PURE__ */ new Date()).toISOString();
16170
+ for (const id of recordedIds) {
16171
+ await appendPreventionEvent2(paths, { at, id, source: "sensor" }).catch(() => {
16172
+ });
16173
+ }
16174
+ }
16175
+ }
16106
16176
  const output = {
16107
16177
  scanned: memories.length,
16108
16178
  hits: hits.map((hit) => ({
@@ -16446,7 +16516,8 @@ import {
16446
16516
  buildDashboard,
16447
16517
  findProjectRoot as findProjectRoot55,
16448
16518
  loadMemoriesFromDir as loadMemoriesFromDir41,
16449
- loadUsageIndex as loadUsageIndex29,
16519
+ loadPreventionEvents,
16520
+ loadUsageIndex as loadUsageIndex30,
16450
16521
  resolveHaivePaths as resolveHaivePaths51
16451
16522
  } from "@hiveai/core";
16452
16523
  function registerDashboard(program2) {
@@ -16461,11 +16532,13 @@ function registerDashboard(program2) {
16461
16532
  return;
16462
16533
  }
16463
16534
  const memories = existsSync78(paths.memoriesDir) ? await loadMemoriesFromDir41(paths.memoriesDir) : [];
16464
- const usage = await loadUsageIndex29(paths);
16535
+ const usage = await loadUsageIndex30(paths);
16536
+ const preventionEvents = await loadPreventionEvents(paths);
16465
16537
  const top = Math.max(1, Number.parseInt(opts.top ?? "10", 10) || 10);
16466
16538
  const dormantDays = opts.dormantDays ? Number.parseInt(opts.dormantDays, 10) : void 0;
16467
16539
  const report = buildDashboard(memories, usage, {
16468
16540
  top,
16541
+ preventionEvents,
16469
16542
  ...dormantDays !== void 0 && Number.isFinite(dormantDays) ? { dormantDays } : {}
16470
16543
  });
16471
16544
  if (opts.json) {
@@ -16476,7 +16549,7 @@ function registerDashboard(program2) {
16476
16549
  });
16477
16550
  }
16478
16551
  function renderDashboard(r) {
16479
- const { inventory: inv, impact, sensors, health, decay, corpus } = r;
16552
+ const { inventory: inv, impact, sensors, health, decay, corpus, prevention } = r;
16480
16553
  console.log(ui.bold("hAIve dashboard"));
16481
16554
  console.log(
16482
16555
  ` ${ui.dim("corpus:")} ${inv.total} policy memor${inv.total === 1 ? "y" : "ies"} (${inv.active} active, ${inv.retired} retired) \xB7 ${inv.session_recaps} recap(s) \xB7 ~${corpus.est_tokens.toLocaleString()} tokens`
@@ -16506,6 +16579,27 @@ function renderDashboard(r) {
16506
16579
  console.log(` ${marker} ${s.id} ${ui.dim(`last fired ${s.last_fired.slice(0, 10)}`)}`);
16507
16580
  }
16508
16581
  console.log();
16582
+ console.log(ui.bold("Prevention") + ui.dim(" (outcome: sensors that caught a real diff)"));
16583
+ console.log(
16584
+ ` ${prevention.total_events > 0 ? ui.green(`${prevention.total_events} catch event(s)`) : "0 catch events"} \xB7 ${prevention.memories_with_catches} memor${prevention.memories_with_catches === 1 ? "y" : "ies"} with catches`
16585
+ );
16586
+ console.log(
16587
+ ` ${ui.dim("trend:")} ${prevention.trend.last_7d} in 7d \xB7 ${prevention.trend.last_30d} in 30d ${ui.dim("weekly")} [${prevention.trend.weekly.join(" ")}]`
16588
+ );
16589
+ for (const p of prevention.top.slice(0, 5)) {
16590
+ console.log(
16591
+ ` ${ui.green("\u2713")} ${p.prevented_count}\xD7 ${p.id}` + (p.last_prevented_at ? ui.dim(` last ${p.last_prevented_at.slice(0, 10)}`) : "")
16592
+ );
16593
+ }
16594
+ if (prevention.recurrence.recurring_count > 0) {
16595
+ console.log(
16596
+ ` ${ui.yellow("recurrence:")} ${prevention.recurrence.recurring_count} lesson(s) re-introduced after capture ` + ui.dim("(caught on \u22652 distinct days)")
16597
+ );
16598
+ for (const r2 of prevention.recurrence.top.slice(0, 5)) {
16599
+ console.log(` ${ui.yellow("\u21BB")} ${r2.distinct_days} days \xB7 ${r2.catches}\xD7 ${r2.id}`);
16600
+ }
16601
+ }
16602
+ console.log();
16509
16603
  console.log(ui.bold("Health"));
16510
16604
  console.log(
16511
16605
  ` stale ${warnNum(health.stale)} \xB7 anchorless ${warnNum(health.anchorless)} \xB7 pending ${health.pending} \xB7 prune candidates ${warnNum(health.prune_candidates)}`
@@ -16542,7 +16636,7 @@ function warnNum(n) {
16542
16636
 
16543
16637
  // src/index.ts
16544
16638
  var program = new Command58();
16545
- program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.13.7").option("--advanced", "show maintenance and experimental commands in help");
16639
+ program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.13.9").option("--advanced", "show maintenance and experimental commands in help");
16546
16640
  registerInit(program);
16547
16641
  registerWelcome(program);
16548
16642
  registerResolveProject(program);