@hiveai/cli 0.13.7 → 0.13.8

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.8"}`;
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,
@@ -7919,7 +7919,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
7919
7919
  };
7920
7920
  }
7921
7921
  var SERVER_NAME = "haive";
7922
- var SERVER_VERSION = "0.13.7";
7922
+ var SERVER_VERSION = "0.13.8";
7923
7923
  function jsonResult(data) {
7924
7924
  return {
7925
7925
  content: [
@@ -13488,7 +13488,7 @@ function registerDoctor(program2) {
13488
13488
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
13489
13489
  });
13490
13490
  }
13491
- findings.push(...await collectInstallFindings(root, "0.13.7"));
13491
+ findings.push(...await collectInstallFindings(root, "0.13.8"));
13492
13492
  findings.push(...await collectToolchainFindings(root));
13493
13493
  try {
13494
13494
  const legacyRaw = execSync3("haive-mcp --version", {
@@ -13496,7 +13496,7 @@ function registerDoctor(program2) {
13496
13496
  timeout: 3e3,
13497
13497
  stdio: ["ignore", "pipe", "ignore"]
13498
13498
  }).trim();
13499
- const cliVersion = "0.13.7";
13499
+ const cliVersion = "0.13.8";
13500
13500
  if (legacyRaw && legacyRaw !== cliVersion) {
13501
13501
  findings.push({
13502
13502
  severity: "warn",
@@ -14656,6 +14656,16 @@ function registerEnforce(program2) {
14656
14656
  process.exit(2);
14657
14657
  }
14658
14658
  });
14659
+ enforce.command("commit-msg <msgfile>").description(
14660
+ "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."
14661
+ ).option("-d, --dir <dir>", "project root").action(async (msgfile, opts) => {
14662
+ const root = findProjectRoot52(opts.dir);
14663
+ const verdict = await checkCommitMessageSkipCi(root, msgfile);
14664
+ if (verdict.block) {
14665
+ ui.error(verdict.message);
14666
+ process.exit(1);
14667
+ }
14668
+ });
14659
14669
  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
14670
  const payload = await readHookPayload();
14661
14671
  const root = resolveRoot(opts.dir, payload);
@@ -15095,7 +15105,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
15095
15105
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
15096
15106
  });
15097
15107
  }
15098
- findings.push(...await inspectIntegrationVersions(root, "0.13.7"));
15108
+ findings.push(...await inspectIntegrationVersions(root, "0.13.8"));
15099
15109
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
15100
15110
  const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
15101
15111
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
@@ -15589,6 +15599,21 @@ var SHIPPABLE_PATH_PREFIXES = [
15589
15599
  function isShippablePath(file) {
15590
15600
  return SHIPPABLE_PATH_PREFIXES.some((prefix) => file.startsWith(prefix)) || VERSION_FILES.includes(file);
15591
15601
  }
15602
+ var CI_SKIP_DIRECTIVE = /\[skip ci\]|\[ci skip\]|\[no ci\]|\[skip actions\]|\*\*\*NO_CI\*\*\*|skip-checks: *true/i;
15603
+ async function checkCommitMessageSkipCi(root, msgfile) {
15604
+ const file = path51.isAbsolute(msgfile) ? msgfile : path51.join(root, msgfile);
15605
+ const raw = await readFile23(file, "utf8").catch(() => "");
15606
+ const cleaned = raw.split("\n").filter((line) => !line.startsWith("#")).join("\n");
15607
+ if (!CI_SKIP_DIRECTIVE.test(cleaned)) return { block: false, message: "" };
15608
+ const staged = (await runCommand4("git", ["diff", "--cached", "--name-only"], root).catch(() => "")).split("\n").map((s) => s.trim()).filter(Boolean);
15609
+ const shippable = staged.filter(isShippablePath);
15610
+ if (shippable.length === 0) return { block: false, message: "" };
15611
+ return {
15612
+ block: true,
15613
+ 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 ? `
15614
+ \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."
15615
+ };
15616
+ }
15592
15617
  async function inspectReleaseVersionState(root, upstream) {
15593
15618
  const localEntries = await Promise.all(VERSION_FILES.map(async (file) => [file, await readPackageVersion(root, file)]));
15594
15619
  const localVersions = new Map(localEntries);
@@ -15812,6 +15837,13 @@ haive enforce check --stage pre-commit --dir . || exit $?
15812
15837
  body: `#!/bin/sh
15813
15838
  ${ENFORCE_HOOK_MARKER}
15814
15839
  haive enforce check --stage pre-push --dir . || exit $?
15840
+ `
15841
+ },
15842
+ {
15843
+ name: "commit-msg",
15844
+ body: `#!/bin/sh
15845
+ ${ENFORCE_HOOK_MARKER}
15846
+ haive enforce commit-msg "$1" --dir . || exit $?
15815
15847
  `
15816
15848
  }
15817
15849
  ];
@@ -15831,7 +15863,7 @@ ${hook.body}`, "utf8");
15831
15863
  }
15832
15864
  await chmod2(file, 493);
15833
15865
  }
15834
- ui.success("Installed blocking git enforcement hooks: pre-commit, pre-push");
15866
+ ui.success("Installed blocking git enforcement hooks: pre-commit, pre-push, commit-msg");
15835
15867
  }
15836
15868
  async function installCiEnforcement(root) {
15837
15869
  const workflowPath = path51.join(root, ".github", "workflows", "haive-enforcement.yml");
@@ -16067,8 +16099,11 @@ import {
16067
16099
  findProjectRoot as findProjectRoot53,
16068
16100
  isRetiredMemory as isRetiredMemory3,
16069
16101
  loadMemoriesFromDir as loadMemoriesFromDir39,
16102
+ loadUsageIndex as loadUsageIndex29,
16103
+ recordPrevention,
16070
16104
  resolveHaivePaths as resolveHaivePaths49,
16071
16105
  runSensors as runSensors2,
16106
+ saveUsageIndex as saveUsageIndex7,
16072
16107
  sensorTargetsFromDiff as sensorTargetsFromDiff2,
16073
16108
  serializeMemory as serializeMemory27
16074
16109
  } from "@hiveai/core";
@@ -16103,6 +16138,14 @@ function registerSensors(program2) {
16103
16138
  const diff = opts.diffFile ? await readFile24(path53.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
16104
16139
  const targets = sensorTargetsFromDiff2(diff);
16105
16140
  const hits = runSensors2(memories, targets.length > 0 ? targets : [{ path: "", content: diff }]);
16141
+ const firedIds = [...new Set(hits.map((hit) => hit.memory_id))];
16142
+ if (firedIds.length > 0) {
16143
+ const usage = await loadUsageIndex29(paths);
16144
+ let recorded = 0;
16145
+ for (const id of firedIds) if (recordPrevention(usage, id)) recorded++;
16146
+ if (recorded > 0) await saveUsageIndex7(paths, usage).catch(() => {
16147
+ });
16148
+ }
16106
16149
  const output = {
16107
16150
  scanned: memories.length,
16108
16151
  hits: hits.map((hit) => ({
@@ -16446,7 +16489,7 @@ import {
16446
16489
  buildDashboard,
16447
16490
  findProjectRoot as findProjectRoot55,
16448
16491
  loadMemoriesFromDir as loadMemoriesFromDir41,
16449
- loadUsageIndex as loadUsageIndex29,
16492
+ loadUsageIndex as loadUsageIndex30,
16450
16493
  resolveHaivePaths as resolveHaivePaths51
16451
16494
  } from "@hiveai/core";
16452
16495
  function registerDashboard(program2) {
@@ -16461,7 +16504,7 @@ function registerDashboard(program2) {
16461
16504
  return;
16462
16505
  }
16463
16506
  const memories = existsSync78(paths.memoriesDir) ? await loadMemoriesFromDir41(paths.memoriesDir) : [];
16464
- const usage = await loadUsageIndex29(paths);
16507
+ const usage = await loadUsageIndex30(paths);
16465
16508
  const top = Math.max(1, Number.parseInt(opts.top ?? "10", 10) || 10);
16466
16509
  const dormantDays = opts.dormantDays ? Number.parseInt(opts.dormantDays, 10) : void 0;
16467
16510
  const report = buildDashboard(memories, usage, {
@@ -16476,7 +16519,7 @@ function registerDashboard(program2) {
16476
16519
  });
16477
16520
  }
16478
16521
  function renderDashboard(r) {
16479
- const { inventory: inv, impact, sensors, health, decay, corpus } = r;
16522
+ const { inventory: inv, impact, sensors, health, decay, corpus, prevention } = r;
16480
16523
  console.log(ui.bold("hAIve dashboard"));
16481
16524
  console.log(
16482
16525
  ` ${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 +16549,16 @@ function renderDashboard(r) {
16506
16549
  console.log(` ${marker} ${s.id} ${ui.dim(`last fired ${s.last_fired.slice(0, 10)}`)}`);
16507
16550
  }
16508
16551
  console.log();
16552
+ console.log(ui.bold("Prevention") + ui.dim(" (outcome: sensors that caught a real diff)"));
16553
+ console.log(
16554
+ ` ${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`
16555
+ );
16556
+ for (const p of prevention.top.slice(0, 5)) {
16557
+ console.log(
16558
+ ` ${ui.green("\u2713")} ${p.prevented_count}\xD7 ${p.id}` + (p.last_prevented_at ? ui.dim(` last ${p.last_prevented_at.slice(0, 10)}`) : "")
16559
+ );
16560
+ }
16561
+ console.log();
16509
16562
  console.log(ui.bold("Health"));
16510
16563
  console.log(
16511
16564
  ` stale ${warnNum(health.stale)} \xB7 anchorless ${warnNum(health.anchorless)} \xB7 pending ${health.pending} \xB7 prune candidates ${warnNum(health.prune_candidates)}`
@@ -16542,7 +16595,7 @@ function warnNum(n) {
16542
16595
 
16543
16596
  // src/index.ts
16544
16597
  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");
16598
+ program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.13.8").option("--advanced", "show maintenance and experimental commands in help");
16546
16599
  registerInit(program);
16547
16600
  registerWelcome(program);
16548
16601
  registerResolveProject(program);