@hiveai/cli 0.11.0 → 0.12.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
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command55 } from "commander";
4
+ import { Command as Command56 } from "commander";
5
5
 
6
6
  // src/commands/briefing.ts
7
7
  import { existsSync as existsSync3 } from "fs";
@@ -2743,7 +2743,7 @@ ${SEED_FOOTER(stack)}` });
2743
2743
  }
2744
2744
 
2745
2745
  // src/commands/init.ts
2746
- var HAIVE_GITHUB_ACTION_REF = `v${"0.11.0"}`;
2746
+ var HAIVE_GITHUB_ACTION_REF = `v${"0.12.0"}`;
2747
2747
  var PROJECT_CONTEXT_TEMPLATE = `# Project context
2748
2748
 
2749
2749
  > Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
@@ -3822,7 +3822,10 @@ import { z as z24 } from "zod";
3822
3822
  import { existsSync as existsSync24 } from "fs";
3823
3823
  import {
3824
3824
  addedLinesFromDiff,
3825
+ buildDocFrequency,
3826
+ CODE_STOPWORDS,
3825
3827
  deriveConfidence as deriveConfidence6,
3828
+ diffHasDistinctiveOverlap,
3826
3829
  getUsage as getUsage8,
3827
3830
  isRetiredMemory as isRetiredMemory2,
3828
3831
  loadMemoriesFromDir as loadMemoriesFromDir18,
@@ -6145,53 +6148,6 @@ var AntiPatternsCheckInputSchema = {
6145
6148
  "Minimum cosine score for semantic-only anti-pattern hits. Anchor/literal matches still surface. Default 0.45 keeps broad, weakly-related memories out of review noise."
6146
6149
  )
6147
6150
  };
6148
- var CODE_STOPWORDS = /* @__PURE__ */ new Set([
6149
- "import",
6150
- "export",
6151
- "function",
6152
- "return",
6153
- "const",
6154
- "let",
6155
- "var",
6156
- "class",
6157
- "public",
6158
- "private",
6159
- "protected",
6160
- "static",
6161
- "this",
6162
- "true",
6163
- "false",
6164
- "null",
6165
- "undefined",
6166
- "void",
6167
- "async",
6168
- "await",
6169
- "from",
6170
- "type",
6171
- "interface",
6172
- "extends",
6173
- "implements",
6174
- "number",
6175
- "string",
6176
- "boolean",
6177
- "value",
6178
- "default",
6179
- "case",
6180
- "break",
6181
- "continue",
6182
- "throw",
6183
- "catch",
6184
- "finally",
6185
- "else",
6186
- "while",
6187
- "for",
6188
- "new",
6189
- "super",
6190
- "yield",
6191
- "module",
6192
- "require",
6193
- "console"
6194
- ]);
6195
6151
  function tokenizeDiffForLiteral(diff) {
6196
6152
  const lines = diff.split("\n");
6197
6153
  const looksLikeDiff = lines.some((l) => /^[+-]/.test(l));
@@ -6224,6 +6180,7 @@ async function antiPatternsCheck(input, ctx) {
6224
6180
  return { scanned: 0, warnings: [], notice: "No attempt/gotcha memories found yet." };
6225
6181
  }
6226
6182
  const usage = await loadUsageIndex10(ctx.paths);
6183
+ const docFreq = buildDocFrequency(negative.map(({ memory: memory2 }) => memory2.body));
6227
6184
  const seen = /* @__PURE__ */ new Map();
6228
6185
  const upsert = (fm, body, reason, score) => {
6229
6186
  const existing = seen.get(fm.id);
@@ -6257,10 +6214,16 @@ async function antiPatternsCheck(input, ctx) {
6257
6214
  }
6258
6215
  if (input.diff) {
6259
6216
  const tokens = tokenizeDiffForLiteral(input.diff);
6217
+ const added = addedLinesFromDiff(input.diff);
6218
+ const addedText = added.trim().length > 0 ? added : input.diff;
6260
6219
  if (tokens.length > 0) {
6261
6220
  for (const { memory: memory2 } of negative) {
6262
6221
  if (literalMatchesAnyToken3(memory2, tokens)) {
6263
6222
  upsert(memory2.frontmatter, memory2.body, "literal");
6223
+ if (diffHasDistinctiveOverlap(addedText, memory2.body, docFreq)) {
6224
+ const w = seen.get(memory2.frontmatter.id);
6225
+ if (w) w.distinctive_literal = true;
6226
+ }
6264
6227
  }
6265
6228
  }
6266
6229
  }
@@ -6858,7 +6821,12 @@ function classifyWarning(warning, paths, anchoredBlocks = false) {
6858
6821
  const hasSemantic = warning.reasons.includes("semantic");
6859
6822
  const semanticScore = warning.semantic_score ?? 0;
6860
6823
  const highConfidence = warning.confidence === "authoritative" || warning.confidence === "trusted";
6861
- if (anchoredBlocks && highConfidence && warning.scope !== "personal" && warning.reasons.includes("anchor") && (warning.reasons.includes("literal") || hasSemantic && semanticScore >= 0.45)) {
6824
+ if (anchoredBlocks && highConfidence && warning.scope !== "personal" && warning.reasons.includes("anchor") && // A literal overlap only corroborates a BLOCK when it is on a token *distinctive*
6825
+ // to this gotcha (rare in the corpus). Sharing a common domain word ("memory",
6826
+ // "scope", "version") — or a version-bump diff — no longer hard-blocks; it falls
6827
+ // through to `review` below. This kills the incidental-token false positives that
6828
+ // made agents work for nothing. A moderate semantic match still corroborates.
6829
+ (warning.distinctive_literal === true || hasSemantic && semanticScore >= 0.45)) {
6862
6830
  if (warning.has_sensor && !warning.reasons.includes("sensor")) {
6863
6831
  return {
6864
6832
  ...warning,
@@ -7550,7 +7518,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
7550
7518
  };
7551
7519
  }
7552
7520
  var SERVER_NAME = "haive";
7553
- var SERVER_VERSION = "0.11.0";
7521
+ var SERVER_VERSION = "0.12.0";
7554
7522
  function jsonResult(data) {
7555
7523
  return {
7556
7524
  content: [
@@ -9126,7 +9094,7 @@ function registerMemoryAdd(memory2) {
9126
9094
  haive memory add --type convention --slug flyway-no-modify --topic flyway \\\\
9127
9095
  --scope team --body "Never modify existing migrations. Create V{n+1}__desc.sql."
9128
9096
  `
9129
- ).requiredOption("--type <type>", "skill | convention | decision | gotcha | architecture | glossary | attempt").option("--slug <slug>", "short kebab-case identifier used in the file name (auto-derived from --title/--body when omitted)").option("--title <text>", "memory title \u2014 becomes the first heading of the body").option("--scope <scope>", "personal | team | module (default: config default; team in autopilot)").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags for easier retrieval").option("--domain <domain>", "domain (e.g. transactions)").option("--author <author>", "author email or handle").option("--paths <csv>", "anchor to source files \u2014 used for staleness detection by haive sync").option("--files <csv>", "alias for --paths (matches the MCP `files` parameter)").option("--symbols <csv>", "anchor to specific symbols (class/function names)").option("--commit <sha>", "anchor to a specific commit SHA").option("--body <text>", "memory body content (Markdown) \u2014 overrides --title default body").option("--body-file <path>", "read memory body from a Markdown file \u2014 for long content").option("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("--topic <key>", "stable key for upsert: if a memory with this topic+scope already exists, update it in-place (revision_count++)").option("-d, --dir <dir>", "project root").action(async (opts) => {
9097
+ ).requiredOption("--type <type>", "skill | convention | decision | gotcha | architecture | glossary | attempt").option("--slug <slug>", "short kebab-case identifier used in the file name (auto-derived from --title/--body when omitted)").option("--title <text>", "memory title \u2014 becomes the first heading of the body").option("--scope <scope>", "personal | team | module (default: config default; team in autopilot)").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags for easier retrieval").option("--domain <domain>", "domain (e.g. transactions)").option("--author <author>", "author email or handle").option("--paths <csv>", "anchor to source files \u2014 used for staleness detection by haive sync").option("--files <csv>", "alias for --paths (matches the MCP `files` parameter)").option("--symbols <csv>", "anchor to specific symbols (class/function names)").option("--commit <sha>", "anchor to a specific commit SHA").option("--body <text>", "memory body content (Markdown) \u2014 overrides --title default body").option("--body-file <path>", "read memory body from a Markdown file \u2014 for long content").option("--no-auto-tag", "disable automatic tag suggestions inferred from anchor paths").option("--topic <key>", "stable key for upsert: if a memory with this topic+scope already exists, update it in-place (revision_count++)").option("--activation-keyword <csv>", "skill only: comma-separated keywords that trigger progressive disclosure of this skill").option("--activation-glob <csv>", "skill only: comma-separated path globs that trigger this skill").option("--activation-always", "skill only: always surface this skill (no triggers needed)").option("-d, --dir <dir>", "project root").action(async (opts) => {
9130
9098
  const root = findProjectRoot13(opts.dir);
9131
9099
  const paths = resolveHaivePaths10(root);
9132
9100
  if (!existsSync33(paths.haiveDir)) {
@@ -9137,6 +9105,11 @@ function registerMemoryAdd(memory2) {
9137
9105
  const config = await loadConfig5(paths);
9138
9106
  const userTags = parseCsv2(opts.tags);
9139
9107
  const anchorPaths = parseCsv2(opts.paths ?? opts.files);
9108
+ const activation = opts.type === "skill" && (opts.activationKeyword || opts.activationGlob || opts.activationAlways) ? {
9109
+ keywords: parseCsv2(opts.activationKeyword),
9110
+ globs: parseCsv2(opts.activationGlob),
9111
+ always: Boolean(opts.activationAlways)
9112
+ } : void 0;
9140
9113
  const autoTagsEnabled = opts.autoTag !== false;
9141
9114
  const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
9142
9115
  const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
@@ -9194,6 +9167,7 @@ TODO \u2014 write the memory body.
9194
9167
  const newFrontmatter = {
9195
9168
  ...fm,
9196
9169
  revision_count: revisionCount,
9170
+ ...activation ? { activation } : {},
9197
9171
  tags: mergedTags.length ? mergedTags : fm.tags,
9198
9172
  anchor: {
9199
9173
  commit: opts.commit ?? fm.anchor.commit,
@@ -9224,7 +9198,8 @@ TODO \u2014 write the memory body.
9224
9198
  commit: opts.commit,
9225
9199
  topic: opts.topic,
9226
9200
  status: config.defaultStatus === "validated" ? "validated" : void 0,
9227
- sensor: suggestSensorForCliMemory(opts.type, body, anchorPaths) ?? void 0
9201
+ sensor: suggestSensorForCliMemory(opts.type, body, anchorPaths) ?? void 0,
9202
+ activation
9228
9203
  });
9229
9204
  const file = memoryFilePath6(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
9230
9205
  await mkdir11(path16.dirname(file), { recursive: true });
@@ -10556,14 +10531,68 @@ function pad(value, width) {
10556
10531
  return value.slice(0, width - 1) + "\u2026";
10557
10532
  }
10558
10533
 
10559
- // src/commands/memory-verify.ts
10560
- import { writeFile as writeFile21 } from "fs/promises";
10534
+ // src/commands/memory-feedback.ts
10561
10535
  import { existsSync as existsSync53 } from "fs";
10562
- import path34 from "path";
10563
10536
  import "commander";
10564
10537
  import {
10538
+ computeImpact as computeImpact4,
10565
10539
  findProjectRoot as findProjectRoot31,
10540
+ getUsage as getUsage19,
10541
+ loadUsageIndex as loadUsageIndex24,
10542
+ recordApplied as recordApplied2,
10543
+ recordRejection as recordRejection4,
10566
10544
  resolveHaivePaths as resolveHaivePaths28,
10545
+ saveUsageIndex as saveUsageIndex6
10546
+ } from "@hiveai/core";
10547
+ function registerMemoryFeedback(memory2) {
10548
+ memory2.command("feedback <id>").description(
10549
+ "Record whether a memory actually helped \u2014 the closed-loop utility signal (mirror of the mem_feedback MCP tool). 'applied' = it steered your work; 'rejected' = it was wrong/unhelpful. Feeds `haive memory impact`."
10550
+ ).option("--applied", "the memory changed what you did (positive signal)", false).option("--rejected", "the memory was wrong/outdated/unhelpful (negative signal)", false).option("--reason <text>", "why it was rejected (stored on the usage record)").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (id, opts) => {
10551
+ if (opts.applied === opts.rejected) {
10552
+ ui.error("Specify exactly one of --applied or --rejected.");
10553
+ process.exitCode = 1;
10554
+ return;
10555
+ }
10556
+ const root = findProjectRoot31(opts.dir);
10557
+ const paths = resolveHaivePaths28(root);
10558
+ if (!existsSync53(paths.memoriesDir)) {
10559
+ ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
10560
+ process.exitCode = 1;
10561
+ return;
10562
+ }
10563
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
10564
+ const target = all.find((m) => m.memory.frontmatter.id === id);
10565
+ if (!target) {
10566
+ ui.error(`No memory with id '${id}'.`);
10567
+ process.exitCode = 1;
10568
+ return;
10569
+ }
10570
+ const index = await loadUsageIndex24(paths);
10571
+ const outcome = opts.applied ? "applied" : "rejected";
10572
+ if (opts.applied) recordApplied2(index, id);
10573
+ else recordRejection4(index, id, opts.reason ?? null);
10574
+ await saveUsageIndex6(paths, index);
10575
+ const usage = getUsage19(index, id);
10576
+ const impact = computeImpact4(target.memory.frontmatter, usage);
10577
+ if (opts.json) {
10578
+ console.log(JSON.stringify({ id, outcome, usage, impact }, null, 2));
10579
+ return;
10580
+ }
10581
+ ui.success(`Recorded '${outcome}' for ${id}`);
10582
+ ui.info(
10583
+ `applied=${usage.applied_count} \xB7 rejected=${usage.rejected_count} \xB7 read=${usage.read_count} \u2192 impact ${impact.score.toFixed(2)} (${impact.tier})`
10584
+ );
10585
+ });
10586
+ }
10587
+
10588
+ // src/commands/memory-verify.ts
10589
+ import { writeFile as writeFile21 } from "fs/promises";
10590
+ import { existsSync as existsSync54 } from "fs";
10591
+ import path34 from "path";
10592
+ import "commander";
10593
+ import {
10594
+ findProjectRoot as findProjectRoot32,
10595
+ resolveHaivePaths as resolveHaivePaths29,
10567
10596
  serializeMemory as serializeMemory19,
10568
10597
  verifyAnchor as verifyAnchor3
10569
10598
  } from "@hiveai/core";
@@ -10571,9 +10600,9 @@ function registerMemoryVerify(memory2) {
10571
10600
  memory2.command("verify").description(
10572
10601
  "Check that memory anchor paths still exist in the current codebase.\n\n A memory is 'stale' when its anchored file or symbol was moved, deleted, or renamed.\n Stale memories are shown with a warning in get_briefing and should be updated or deleted.\n\n haive sync runs this automatically. Use this command for on-demand checks or in CI.\n\n CI recommendation: add 'haive memory verify' to your haive-sync.yml PR check job\n to catch stale memories before they reach main.\n\n Examples:\n haive memory verify # check all, report only\n haive memory verify --update # mark stale/fresh on disk\n haive memory verify --id 2026-04-28-gotcha-x # check one memory\n"
10573
10602
  ).option("--id <id>", "verify a single memory by id").option("--all", "verify every memory (default if --id is omitted)").option("--update", "write status=stale or status=validated back to disk").option("--json", "emit machine-readable JSON (for CI / agents)").option("-d, --dir <dir>", "project root").action(async (opts) => {
10574
- const root = findProjectRoot31(opts.dir);
10575
- const paths = resolveHaivePaths28(root);
10576
- if (!existsSync53(paths.memoriesDir)) {
10603
+ const root = findProjectRoot32(opts.dir);
10604
+ const paths = resolveHaivePaths29(root);
10605
+ if (!existsSync54(paths.memoriesDir)) {
10577
10606
  if (opts.json) {
10578
10607
  console.log(JSON.stringify({ error: "not-initialized", root }, null, 2));
10579
10608
  } else {
@@ -10694,24 +10723,24 @@ function applyVerification2(mem, result) {
10694
10723
 
10695
10724
  // src/commands/memory-import.ts
10696
10725
  import { readFile as readFile15 } from "fs/promises";
10697
- import { existsSync as existsSync54 } from "fs";
10726
+ import { existsSync as existsSync55 } from "fs";
10698
10727
  import "commander";
10699
10728
  import {
10700
- findProjectRoot as findProjectRoot32,
10701
- resolveHaivePaths as resolveHaivePaths29
10729
+ findProjectRoot as findProjectRoot33,
10730
+ resolveHaivePaths as resolveHaivePaths30
10702
10731
  } from "@hiveai/core";
10703
10732
  function registerMemoryImport(memory2) {
10704
10733
  memory2.command("import").description(
10705
10734
  "Parse a Markdown file and suggest memories via the import_docs MCP prompt (prints a ready-to-use prompt invocation)"
10706
10735
  ).requiredOption("--from <file>", "Markdown/text file to import from").option("--scope <scope>", "personal | team (default: team)", "team").option("-d, --dir <dir>", "project root").action(async (opts) => {
10707
- const root = findProjectRoot32(opts.dir);
10708
- const paths = resolveHaivePaths29(root);
10709
- if (!existsSync54(paths.haiveDir)) {
10736
+ const root = findProjectRoot33(opts.dir);
10737
+ const paths = resolveHaivePaths30(root);
10738
+ if (!existsSync55(paths.haiveDir)) {
10710
10739
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
10711
10740
  process.exitCode = 1;
10712
10741
  return;
10713
10742
  }
10714
- if (!existsSync54(opts.from)) {
10743
+ if (!existsSync55(opts.from)) {
10715
10744
  ui.error(`File not found: ${opts.from}`);
10716
10745
  process.exitCode = 1;
10717
10746
  return;
@@ -10744,14 +10773,14 @@ function registerMemoryImport(memory2) {
10744
10773
  }
10745
10774
 
10746
10775
  // src/commands/memory-import-changelog.ts
10747
- import { existsSync as existsSync55 } from "fs";
10776
+ import { existsSync as existsSync56 } from "fs";
10748
10777
  import { readFile as readFile16, mkdir as mkdir14, writeFile as writeFile23 } from "fs/promises";
10749
10778
  import path35 from "path";
10750
10779
  import "commander";
10751
10780
  import {
10752
10781
  buildFrontmatter as buildFrontmatter9,
10753
- findProjectRoot as findProjectRoot33,
10754
- resolveHaivePaths as resolveHaivePaths30,
10782
+ findProjectRoot as findProjectRoot34,
10783
+ resolveHaivePaths as resolveHaivePaths31,
10755
10784
  serializeMemory as serializeMemory20
10756
10785
  } from "@hiveai/core";
10757
10786
  function parseChangelog(content) {
@@ -10816,10 +10845,10 @@ function registerMemoryImportChangelog(memory2) {
10816
10845
  "--versions <csv>",
10817
10846
  "only import specific versions (comma-separated), or 'latest' for the most recent breaking version"
10818
10847
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
10819
- const root = findProjectRoot33(opts.dir);
10820
- const paths = resolveHaivePaths30(root);
10848
+ const root = findProjectRoot34(opts.dir);
10849
+ const paths = resolveHaivePaths31(root);
10821
10850
  const changelogPath = path35.resolve(root, opts.fromChangelog);
10822
- if (!existsSync55(changelogPath)) {
10851
+ if (!existsSync56(changelogPath)) {
10823
10852
  ui.error(`CHANGELOG not found: ${changelogPath}`);
10824
10853
  process.exitCode = 1;
10825
10854
  return;
@@ -10906,17 +10935,17 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
10906
10935
  }
10907
10936
 
10908
10937
  // src/commands/memory-digest.ts
10909
- import { existsSync as existsSync56 } from "fs";
10938
+ import { existsSync as existsSync57 } from "fs";
10910
10939
  import { writeFile as writeFile24 } from "fs/promises";
10911
10940
  import path36 from "path";
10912
10941
  import "commander";
10913
10942
  import {
10914
10943
  deriveConfidence as deriveConfidence12,
10915
- findProjectRoot as findProjectRoot34,
10916
- getUsage as getUsage19,
10944
+ findProjectRoot as findProjectRoot35,
10945
+ getUsage as getUsage20,
10917
10946
  loadMemoriesFromDir as loadMemoriesFromDir27,
10918
- loadUsageIndex as loadUsageIndex24,
10919
- resolveHaivePaths as resolveHaivePaths31
10947
+ loadUsageIndex as loadUsageIndex25,
10948
+ resolveHaivePaths as resolveHaivePaths32
10920
10949
  } from "@hiveai/core";
10921
10950
  var CONFIDENCE_EMOJI = {
10922
10951
  unverified: "\u2B1C",
@@ -10929,9 +10958,9 @@ function registerMemoryDigest(program2) {
10929
10958
  program2.command("digest").description(
10930
10959
  "Generate a Markdown review digest of recently added or updated memories.\n\n Groups memories by type, shows confidence, status, read count, and anchor info.\n Each memory has action checkboxes (approve / reject / keep as-is) for peer review.\n\n Use this to do a bulk weekly review of team memories, or share with teammates\n as a pull-request attachment so humans can validate what the AI captured.\n\n Examples:\n haive memory digest # last 7 days, team scope\n haive memory digest --days 30 --scope all # last 30 days, all scopes\n haive memory digest --out review.md # write to file\n"
10931
10960
  ).option("--days <n>", "look-back window in days (default: 7)", "7").option("--scope <scope>", "personal | team | module | all (default: team)", "team").option("--out <file>", "write digest to a file instead of stdout").option("-d, --dir <dir>", "project root").action(async (opts) => {
10932
- const root = findProjectRoot34(opts.dir);
10933
- const paths = resolveHaivePaths31(root);
10934
- if (!existsSync56(paths.memoriesDir)) {
10961
+ const root = findProjectRoot35(opts.dir);
10962
+ const paths = resolveHaivePaths32(root);
10963
+ if (!existsSync57(paths.memoriesDir)) {
10935
10964
  ui.error("No .ai/memories found. Run `haive init` first.");
10936
10965
  process.exitCode = 1;
10937
10966
  return;
@@ -10940,7 +10969,7 @@ function registerMemoryDigest(program2) {
10940
10969
  const scopeFilter = opts.scope ?? "team";
10941
10970
  const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
10942
10971
  const all = await loadMemoriesFromDir27(paths.memoriesDir);
10943
- const usage = await loadUsageIndex24(paths);
10972
+ const usage = await loadUsageIndex25(paths);
10944
10973
  const recent = all.filter(({ memory: mem }) => {
10945
10974
  const fm = mem.frontmatter;
10946
10975
  if (fm.type === "session_recap") return false;
@@ -10971,7 +11000,7 @@ function registerMemoryDigest(program2) {
10971
11000
  lines.push(``);
10972
11001
  for (const { memory: mem } of mems) {
10973
11002
  const fm = mem.frontmatter;
10974
- const u = getUsage19(usage, fm.id);
11003
+ const u = getUsage20(usage, fm.id);
10975
11004
  const confidence = deriveConfidence12(fm, u);
10976
11005
  const emoji = CONFIDENCE_EMOJI[confidence] ?? "\u2B1C";
10977
11006
  const anchor = fm.anchor.paths.length > 0 ? `\`${fm.anchor.paths[0]}\`` + (fm.anchor.paths.length > 1 ? ` +${fm.anchor.paths.length - 1}` : "") : "_no anchor_";
@@ -11014,21 +11043,21 @@ function registerMemoryDigest(program2) {
11014
11043
 
11015
11044
  // src/commands/session-end.ts
11016
11045
  import { writeFile as writeFile25, mkdir as mkdir15, readFile as readFile17, rm as rm2 } from "fs/promises";
11017
- import { existsSync as existsSync57 } from "fs";
11046
+ import { existsSync as existsSync58 } from "fs";
11018
11047
  import { spawn as spawn4 } from "child_process";
11019
11048
  import path37 from "path";
11020
11049
  import "commander";
11021
11050
  import {
11022
11051
  buildFrontmatter as buildFrontmatter10,
11023
- findProjectRoot as findProjectRoot35,
11052
+ findProjectRoot as findProjectRoot36,
11024
11053
  loadMemoriesFromDir as loadMemoriesFromDir28,
11025
11054
  memoryFilePath as memoryFilePath9,
11026
- resolveHaivePaths as resolveHaivePaths32,
11055
+ resolveHaivePaths as resolveHaivePaths33,
11027
11056
  serializeMemory as serializeMemory21
11028
11057
  } from "@hiveai/core";
11029
11058
  async function buildAutoRecap(paths) {
11030
11059
  const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
11031
- if (!existsSync57(obsFile)) return await buildGitAutoRecap(paths);
11060
+ if (!existsSync58(obsFile)) return await buildGitAutoRecap(paths);
11032
11061
  const raw = await readFile17(obsFile, "utf8").catch(() => "");
11033
11062
  if (!raw.trim()) return await buildGitAutoRecap(paths);
11034
11063
  const lines = raw.split("\n").filter(Boolean);
@@ -11217,9 +11246,9 @@ function registerSessionEnd(session2) {
11217
11246
  --next "Add integration tests for webhook signature validation"
11218
11247
  `
11219
11248
  ).option("--goal <text>", "what you were trying to accomplish (1\u20132 sentences)").option("--accomplished <text>", "what was actually done (bullet list recommended)").option("--discoveries <text>", "bugs, surprises, or inconsistencies found during this session").option("--files <csv>", "key files touched, comma-separated (used as anchor for staleness detection)").option("--next <text>", "what should happen next (for the next session or a teammate)").option("--scope <scope>", "personal | team | module (default: personal)", "personal").option("--module <name>", "module name (required when scope=module)").option("--auto", "synthesize the recap from .ai/.cache/observations.jsonl (used by Claude Code SessionEnd hook)").option("--quiet", "suppress non-error output (for hook use)").option("-d, --dir <dir>", "project root").action(async (opts) => {
11220
- const root = findProjectRoot35(opts.dir);
11221
- const paths = resolveHaivePaths32(root);
11222
- if (!existsSync57(paths.haiveDir)) {
11249
+ const root = findProjectRoot36(opts.dir);
11250
+ const paths = resolveHaivePaths33(root);
11251
+ if (!existsSync58(paths.haiveDir)) {
11223
11252
  if (opts.auto || opts.quiet) return;
11224
11253
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
11225
11254
  process.exitCode = 1;
@@ -11252,7 +11281,7 @@ function registerSessionEnd(session2) {
11252
11281
  });
11253
11282
  const topic = recapTopic2(scope, opts.module);
11254
11283
  const filesTouched = parseCsv5(resolvedFiles).map((p) => normalizeAnchorPath(root, p));
11255
- const missingPaths = filesTouched.filter((p) => !existsSync57(path37.resolve(root, p)));
11284
+ const missingPaths = filesTouched.filter((p) => !existsSync58(path37.resolve(root, p)));
11256
11285
  if (missingPaths.length > 0 && !opts.quiet) {
11257
11286
  ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
11258
11287
  for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
@@ -11260,10 +11289,10 @@ function registerSessionEnd(session2) {
11260
11289
  const cleanupObservations = async () => {
11261
11290
  if (!opts.auto) return;
11262
11291
  const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
11263
- if (existsSync57(obsFile)) await rm2(obsFile).catch(() => {
11292
+ if (existsSync58(obsFile)) await rm2(obsFile).catch(() => {
11264
11293
  });
11265
11294
  };
11266
- if (existsSync57(paths.memoriesDir)) {
11295
+ if (existsSync58(paths.memoriesDir)) {
11267
11296
  const existing = await loadMemoriesFromDir28(paths.memoriesDir);
11268
11297
  const topicMatch = existing.find(
11269
11298
  ({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
@@ -11325,15 +11354,15 @@ function normalizeAnchorPath(root, filePath) {
11325
11354
  }
11326
11355
 
11327
11356
  // src/commands/snapshot.ts
11328
- import { existsSync as existsSync58 } from "fs";
11357
+ import { existsSync as existsSync59 } from "fs";
11329
11358
  import { readdir as readdir4 } from "fs/promises";
11330
11359
  import path38 from "path";
11331
11360
  import "commander";
11332
11361
  import {
11333
11362
  diffContract,
11334
- findProjectRoot as findProjectRoot36,
11363
+ findProjectRoot as findProjectRoot37,
11335
11364
  loadConfig as loadConfig7,
11336
- resolveHaivePaths as resolveHaivePaths33,
11365
+ resolveHaivePaths as resolveHaivePaths34,
11337
11366
  snapshotContract
11338
11367
  } from "@hiveai/core";
11339
11368
  function registerSnapshot(program2) {
@@ -11358,16 +11387,16 @@ function registerSnapshot(program2) {
11358
11387
  "--format <format>",
11359
11388
  "contract format: openapi | graphql | proto | typescript | json-schema (auto-detected if omitted)"
11360
11389
  ).option("--diff", "compare the contract against its stored snapshot").option("--list", "list all stored contract snapshots").option("-d, --dir <dir>", "project root").action(async (opts) => {
11361
- const root = findProjectRoot36(opts.dir);
11362
- const paths = resolveHaivePaths33(root);
11363
- if (!existsSync58(paths.haiveDir)) {
11390
+ const root = findProjectRoot37(opts.dir);
11391
+ const paths = resolveHaivePaths34(root);
11392
+ if (!existsSync59(paths.haiveDir)) {
11364
11393
  ui.error("No .ai/ found. Run `haive init` first.");
11365
11394
  process.exitCode = 1;
11366
11395
  return;
11367
11396
  }
11368
11397
  if (opts.list) {
11369
11398
  const contractsDir = path38.join(paths.haiveDir, "contracts");
11370
- if (!existsSync58(contractsDir)) {
11399
+ if (!existsSync59(contractsDir)) {
11371
11400
  console.log(ui.dim("No contract snapshots found."));
11372
11401
  return;
11373
11402
  }
@@ -11491,16 +11520,16 @@ function detectFormat(filePath) {
11491
11520
  }
11492
11521
 
11493
11522
  // src/commands/hub.ts
11494
- import { existsSync as existsSync59 } from "fs";
11523
+ import { existsSync as existsSync60 } from "fs";
11495
11524
  import { mkdir as mkdir16, readFile as readFile18, writeFile as writeFile26, copyFile } from "fs/promises";
11496
11525
  import path39 from "path";
11497
11526
  import { spawnSync as spawnSync5 } from "child_process";
11498
11527
  import "commander";
11499
11528
  import {
11500
- findProjectRoot as findProjectRoot37,
11529
+ findProjectRoot as findProjectRoot38,
11501
11530
  loadConfig as loadConfig8,
11502
11531
  loadMemoriesFromDir as loadMemoriesFromDir29,
11503
- resolveHaivePaths as resolveHaivePaths34,
11532
+ resolveHaivePaths as resolveHaivePaths35,
11504
11533
  saveConfig as saveConfig3,
11505
11534
  serializeMemory as serializeMemory23
11506
11535
  } from "@hiveai/core";
@@ -11582,8 +11611,8 @@ Next steps:
11582
11611
  haive hub push --commit --message "feat: add payment API contract memories"
11583
11612
  `
11584
11613
  ).option("-d, --dir <dir>", "project root").option("--commit", "auto-commit to the hub repo after pushing").option("--message <msg>", "commit message for the hub (used with --commit)").action(async (opts) => {
11585
- const root = findProjectRoot37(opts.dir);
11586
- const paths = resolveHaivePaths34(root);
11614
+ const root = findProjectRoot38(opts.dir);
11615
+ const paths = resolveHaivePaths35(root);
11587
11616
  const config = await loadConfig8(paths);
11588
11617
  if (!config.hubPath) {
11589
11618
  ui.error(
@@ -11593,7 +11622,7 @@ Next steps:
11593
11622
  return;
11594
11623
  }
11595
11624
  const hubRoot = path39.resolve(root, config.hubPath);
11596
- if (!existsSync59(hubRoot)) {
11625
+ if (!existsSync60(hubRoot)) {
11597
11626
  ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
11598
11627
  process.exitCode = 1;
11599
11628
  return;
@@ -11651,8 +11680,8 @@ Next steps:
11651
11680
  hub.command("pull").description(
11652
11681
  "Pull shared memories from the hub into this project.\n\n Imports all memories from hub/.ai/memories/shared/ EXCEPT this project's own.\n Imported memories land in .ai/memories/shared/<source-project-name>/.\n\n Examples:\n haive hub pull\n"
11653
11682
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
11654
- const root = findProjectRoot37(opts.dir);
11655
- const paths = resolveHaivePaths34(root);
11683
+ const root = findProjectRoot38(opts.dir);
11684
+ const paths = resolveHaivePaths35(root);
11656
11685
  const config = await loadConfig8(paths);
11657
11686
  if (!config.hubPath) {
11658
11687
  ui.error(
@@ -11663,7 +11692,7 @@ Next steps:
11663
11692
  }
11664
11693
  const hubRoot = path39.resolve(root, config.hubPath);
11665
11694
  const hubSharedDir = path39.join(hubRoot, ".ai", "memories", "shared");
11666
- if (!existsSync59(hubSharedDir)) {
11695
+ if (!existsSync60(hubSharedDir)) {
11667
11696
  ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
11668
11697
  return;
11669
11698
  }
@@ -11710,15 +11739,15 @@ Next steps:
11710
11739
  );
11711
11740
  });
11712
11741
  hub.command("status").description("Show hub sync status.").option("-d, --dir <dir>", "project root").action(async (opts) => {
11713
- const root = findProjectRoot37(opts.dir);
11714
- const paths = resolveHaivePaths34(root);
11742
+ const root = findProjectRoot38(opts.dir);
11743
+ const paths = resolveHaivePaths35(root);
11715
11744
  const config = await loadConfig8(paths);
11716
11745
  console.log(ui.bold("Hub status"));
11717
11746
  console.log(
11718
11747
  ` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
11719
11748
  );
11720
11749
  const sharedDir = path39.join(paths.memoriesDir, "shared");
11721
- if (existsSync59(sharedDir)) {
11750
+ if (existsSync60(sharedDir)) {
11722
11751
  const { readdir: readdir7 } = await import("fs/promises");
11723
11752
  const sources = (await readdir7(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
11724
11753
  console.log(`
@@ -11747,17 +11776,17 @@ Next steps:
11747
11776
 
11748
11777
  // src/commands/stats.ts
11749
11778
  import "commander";
11750
- import { existsSync as existsSync60 } from "fs";
11779
+ import { existsSync as existsSync61 } from "fs";
11751
11780
  import { mkdir as mkdir17, writeFile as writeFile27 } from "fs/promises";
11752
11781
  import path40 from "path";
11753
11782
  import {
11754
11783
  aggregateUsage,
11755
- findProjectRoot as findProjectRoot38,
11784
+ findProjectRoot as findProjectRoot39,
11756
11785
  loadMemoriesFromDir as loadMemoriesFromDir30,
11757
- loadUsageIndex as loadUsageIndex25,
11786
+ loadUsageIndex as loadUsageIndex26,
11758
11787
  parseSince,
11759
11788
  readUsageEvents as readUsageEvents2,
11760
- resolveHaivePaths as resolveHaivePaths35,
11789
+ resolveHaivePaths as resolveHaivePaths36,
11761
11790
  usageLogSize
11762
11791
  } from "@hiveai/core";
11763
11792
  function registerStats(program2) {
@@ -11766,8 +11795,8 @@ function registerStats(program2) {
11766
11795
  "write a JSON rollup (tools + briefing counts + heuristic ROI hints). Parent dirs are created if needed.",
11767
11796
  void 0
11768
11797
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
11769
- const root = findProjectRoot38(opts.dir);
11770
- const paths = resolveHaivePaths35(root);
11798
+ const root = findProjectRoot39(opts.dir);
11799
+ const paths = resolveHaivePaths36(root);
11771
11800
  if (opts.exportReport) {
11772
11801
  await writeRoiReport(paths, root, opts.since ?? "30d", opts.exportReport);
11773
11802
  return;
@@ -11825,7 +11854,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
11825
11854
  const size = await usageLogSize(paths);
11826
11855
  let events = await readUsageEvents2(paths);
11827
11856
  let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
11828
- if (existsSync60(paths.memoriesDir)) {
11857
+ if (existsSync61(paths.memoriesDir)) {
11829
11858
  const mems = await loadMemoriesFromDir30(paths.memoriesDir);
11830
11859
  for (const { memory: memory2 } of mems) {
11831
11860
  const fm = memory2.frontmatter;
@@ -11840,7 +11869,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
11840
11869
  const briefingCalls = events.filter((e) => inWindow(e.at) && e.tool === "get_briefing").length;
11841
11870
  let memoryHitsLeader = null;
11842
11871
  try {
11843
- const usageIdx = await loadUsageIndex25(paths);
11872
+ const usageIdx = await loadUsageIndex26(paths);
11844
11873
  const tops = Object.entries(usageIdx.by_id).map(([id, v]) => ({ id, read_count: v.read_count })).filter((x) => x.read_count > 0).sort((a, b) => b.read_count - a.read_count);
11845
11874
  memoryHitsLeader = tops[0] ?? null;
11846
11875
  } catch {
@@ -11872,7 +11901,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
11872
11901
  ui.success(`Wrote ROI / usage rollup \u2192 ${outAbs}`);
11873
11902
  }
11874
11903
  async function renderMemoryHits(paths, opts) {
11875
- const index = await loadUsageIndex25(paths);
11904
+ const index = await loadUsageIndex26(paths);
11876
11905
  const since = parseSince(opts.since ?? "30d");
11877
11906
  const sinceMs = since ? new Date(since).getTime() : null;
11878
11907
  const entries = Object.entries(index.by_id).map(([id, usage]) => ({ id, ...usage })).filter((e) => e.read_count > 0).filter((e) => {
@@ -11920,13 +11949,13 @@ import { performance } from "perf_hooks";
11920
11949
  import "commander";
11921
11950
  import {
11922
11951
  estimateTokens as estimateTokens3,
11923
- findProjectRoot as findProjectRoot39,
11924
- resolveHaivePaths as resolveHaivePaths36
11952
+ findProjectRoot as findProjectRoot40,
11953
+ resolveHaivePaths as resolveHaivePaths37
11925
11954
  } from "@hiveai/core";
11926
11955
  function registerBench(program2) {
11927
11956
  program2.command("bench").description("Self-test the local hAIve setup: runs core MCP tools against this project and reports latency + payload size.").option("-t, --task <task>", "task description for ranking-aware tools", "audit dependencies for security risks").option("--json", "emit JSON instead of a table", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
11928
- const root = findProjectRoot39(opts.dir);
11929
- const paths = resolveHaivePaths36(root);
11957
+ const root = findProjectRoot40(opts.dir);
11958
+ const paths = resolveHaivePaths37(root);
11930
11959
  const ctx = { paths };
11931
11960
  const task = opts.task ?? "audit dependencies for security risks";
11932
11961
  const scenarios = [
@@ -12045,11 +12074,11 @@ function summarize(name, t0, payload, notes) {
12045
12074
  }
12046
12075
 
12047
12076
  // src/commands/benchmark.ts
12048
- import { existsSync as existsSync61 } from "fs";
12077
+ import { existsSync as existsSync63 } from "fs";
12049
12078
  import { readdir as readdir5, readFile as readFile19, writeFile as writeFile28 } from "fs/promises";
12050
12079
  import path41 from "path";
12051
12080
  import "commander";
12052
- import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot40 } from "@hiveai/core";
12081
+ import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot41 } from "@hiveai/core";
12053
12082
  function registerBenchmark(program2) {
12054
12083
  const benchmark = program2.command("benchmark").description("Official hAIve benchmark/demo utilities for measuring agent enforcement value.");
12055
12084
  benchmark.command("report").description("Summarize BENCHMARK_AGENT_REPORT.md files from a paired hAIve/plain agent benchmark.").option("-d, --dir <dir>", "benchmark root", "benchmarks/agent-benchmark").option("--out <file>", "write a Markdown report").option("--json", "emit JSON", false).action(async (opts) => {
@@ -12089,18 +12118,18 @@ function registerBenchmark(program2) {
12089
12118
  function resolveBenchmarkRoot(dir) {
12090
12119
  const candidate = dir ?? "benchmarks/agent-benchmark";
12091
12120
  if (path41.isAbsolute(candidate)) return candidate;
12092
- const projectRoot = findProjectRoot40(process.cwd());
12121
+ const projectRoot = findProjectRoot41(process.cwd());
12093
12122
  return path41.join(projectRoot, candidate);
12094
12123
  }
12095
12124
  async function collectRows(root) {
12096
- if (!existsSync61(root)) throw new Error(`Benchmark directory not found: ${root}`);
12125
+ if (!existsSync63(root)) throw new Error(`Benchmark directory not found: ${root}`);
12097
12126
  const entries = await readdir5(root, { withFileTypes: true });
12098
12127
  const rows = [];
12099
12128
  for (const entry of entries) {
12100
12129
  if (!entry.isDirectory()) continue;
12101
12130
  const fixtureDir = path41.join(root, entry.name);
12102
12131
  const reportFile = path41.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
12103
- if (!existsSync61(reportFile)) continue;
12132
+ if (!existsSync63(reportFile)) continue;
12104
12133
  const report = await readFile19(reportFile, "utf8");
12105
12134
  rows.push(parseAgentReport(entry.name, report));
12106
12135
  }
@@ -12191,15 +12220,15 @@ function escapeRegExp(value) {
12191
12220
 
12192
12221
  // src/commands/eval.ts
12193
12222
  import { readFile as readFile20, writeFile as writeFile29 } from "fs/promises";
12194
- import { existsSync as existsSync63 } from "fs";
12223
+ import { existsSync as existsSync64 } from "fs";
12195
12224
  import path43 from "path";
12196
12225
  import "commander";
12197
12226
  import {
12198
12227
  aggregateRetrieval,
12199
12228
  aggregateSensors,
12200
12229
  buildReport,
12201
- findProjectRoot as findProjectRoot41,
12202
- resolveHaivePaths as resolveHaivePaths37,
12230
+ findProjectRoot as findProjectRoot42,
12231
+ resolveHaivePaths as resolveHaivePaths38,
12203
12232
  scoreRetrievalCase,
12204
12233
  scoreSensorCase,
12205
12234
  synthesizeSelfEvalCases
@@ -12207,10 +12236,10 @@ import {
12207
12236
  function registerEval(program2) {
12208
12237
  program2.command("eval").description(
12209
12238
  "Rigorous, repeatable quality eval: do the right memories surface (retrieval) and do the right sensors fire (catch-rate)? Emits a chiffr\xE9 0\u2013100 score. Uses .ai/eval cases via --spec, or auto-synthesizes cases from anchored memories."
12210
- ).option("--spec <file>", "JSON eval spec ({ retrieval: [...], sensors: [...] })").option("--semantic-only", "self-eval probes by title alone (no anchor files) \u2014 harder retrieval", false).option("-k, --top <n>", "briefing top-k considered a hit", "8").option("--json", "emit JSON", false).option("--out <file>", "write a Markdown report").option("-d, --dir <dir>", "project root").action(async (opts) => {
12211
- const root = findProjectRoot41(opts.dir);
12212
- const paths = resolveHaivePaths37(root);
12213
- if (!existsSync63(paths.memoriesDir)) {
12239
+ ).option("--spec <file>", "JSON eval spec ({ retrieval: [...], sensors: [...] })").option("--semantic-only", "self-eval probes by title alone (no anchor files) \u2014 harder retrieval", false).option("-k, --top <n>", "briefing top-k considered a hit", "8").option("--json", "emit JSON", false).option("--out <file>", "write a Markdown report").option("--fail-under <score>", "exit non-zero if the overall score is below this (0\u2013100) \u2014 for CI gates").option("-d, --dir <dir>", "project root").action(async (opts) => {
12240
+ const root = findProjectRoot42(opts.dir);
12241
+ const paths = resolveHaivePaths38(root);
12242
+ if (!existsSync64(paths.memoriesDir)) {
12214
12243
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
12215
12244
  process.exitCode = 1;
12216
12245
  return;
@@ -12243,16 +12272,26 @@ function registerEval(program2) {
12243
12272
  const report = buildReport(retrievalAgg, sensorAgg);
12244
12273
  if (opts.json) {
12245
12274
  console.log(JSON.stringify({ root, k, report }, null, 2));
12246
- return;
12275
+ } else {
12276
+ const md = renderMarkdown2(root, k, report);
12277
+ if (opts.out) {
12278
+ const outFile = path43.isAbsolute(opts.out) ? opts.out : path43.join(root, opts.out);
12279
+ await writeFile29(outFile, md, "utf8");
12280
+ ui.success(`wrote ${path43.relative(process.cwd(), outFile)}`);
12281
+ } else {
12282
+ console.log(md);
12283
+ }
12247
12284
  }
12248
- const md = renderMarkdown2(root, k, report);
12249
- if (opts.out) {
12250
- const outFile = path43.isAbsolute(opts.out) ? opts.out : path43.join(root, opts.out);
12251
- await writeFile29(outFile, md, "utf8");
12252
- ui.success(`wrote ${path43.relative(process.cwd(), outFile)}`);
12253
- return;
12285
+ if (opts.failUnder !== void 0) {
12286
+ const threshold = Number(opts.failUnder);
12287
+ if (Number.isNaN(threshold)) {
12288
+ ui.error(`--fail-under expects a number, got "${opts.failUnder}"`);
12289
+ process.exitCode = 1;
12290
+ } else if (report.score < threshold) {
12291
+ ui.error(`eval score ${report.score} is below --fail-under ${threshold}`);
12292
+ process.exitCode = 1;
12293
+ }
12254
12294
  }
12255
- console.log(md);
12256
12295
  });
12257
12296
  }
12258
12297
  async function resolveSpec(opts, memoriesDir) {
@@ -12348,19 +12387,19 @@ function renderMarkdown2(root, k, report) {
12348
12387
 
12349
12388
  // src/commands/memory-suggest.ts
12350
12389
  import { mkdir as mkdir18, writeFile as writeFile30 } from "fs/promises";
12351
- import { existsSync as existsSync64 } from "fs";
12390
+ import { existsSync as existsSync65 } from "fs";
12352
12391
  import path44 from "path";
12353
12392
  import "commander";
12354
12393
  import {
12355
12394
  aggregateUsage as aggregateUsage2,
12356
12395
  buildFrontmatter as buildFrontmatter11,
12357
- findProjectRoot as findProjectRoot42,
12396
+ findProjectRoot as findProjectRoot43,
12358
12397
  loadConfig as loadConfig9,
12359
12398
  loadMemoriesFromDir as loadMemoriesFromDir31,
12360
12399
  memoryFilePath as memoryFilePath10,
12361
12400
  parseSince as parseSince2,
12362
12401
  readUsageEvents as readUsageEvents3,
12363
- resolveHaivePaths as resolveHaivePaths38,
12402
+ resolveHaivePaths as resolveHaivePaths39,
12364
12403
  serializeMemory as serializeMemory24
12365
12404
  } from "@hiveai/core";
12366
12405
  var SEARCH_TOOLS = /* @__PURE__ */ new Set([
@@ -12377,8 +12416,8 @@ function registerMemorySuggest(memory2) {
12377
12416
  memory2.command("suggest").description(
12378
12417
  "Suggest memories to create based on recurring search queries in the usage log.\n\n Use --auto-save to save the top-N suggestions using the project defaults.\n In autopilot, suggestions land as validated team records; in manual mode they stay draft."
12379
12418
  ).option("--since <window>", "ISO date or relative (e.g. '7d', '24h')", "30d").option("--min <count>", "minimum repeat count to surface a query", "2").option("--top-n <n>", "with --auto-save, draft this many top suggestions", "3").option("--scope <scope>", "with --auto-save, scope of saved memories (personal | team; default: config default)").option("--auto-save", "save top-N suggestions as memories on disk", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
12380
- const root = findProjectRoot42(opts.dir);
12381
- const paths = resolveHaivePaths38(root);
12419
+ const root = findProjectRoot43(opts.dir);
12420
+ const paths = resolveHaivePaths39(root);
12382
12421
  const events = await readUsageEvents3(paths);
12383
12422
  if (events.length === 0) {
12384
12423
  if (opts.json) {
@@ -12427,7 +12466,7 @@ function registerMemorySuggest(memory2) {
12427
12466
  }
12428
12467
  const created = [];
12429
12468
  const skipped = [];
12430
- const existing = existsSync64(paths.memoriesDir) ? await loadMemoriesFromDir31(paths.memoriesDir) : [];
12469
+ const existing = existsSync65(paths.memoriesDir) ? await loadMemoriesFromDir31(paths.memoriesDir) : [];
12431
12470
  for (const s of top) {
12432
12471
  const slug = slugify2(s.query);
12433
12472
  if (!slug) {
@@ -12451,7 +12490,7 @@ function registerMemorySuggest(memory2) {
12451
12490
  const body = renderTemplate(s, fm.id, status);
12452
12491
  const file = memoryFilePath10(paths, fm.scope, fm.id, fm.module);
12453
12492
  await mkdir18(path44.dirname(file), { recursive: true });
12454
- if (existsSync64(file)) {
12493
+ if (existsSync65(file)) {
12455
12494
  skipped.push({ query: s.query, reason: `file already exists at ${path44.relative(root, file)}` });
12456
12495
  continue;
12457
12496
  }
@@ -12554,18 +12593,18 @@ function truncate2(text, max) {
12554
12593
  }
12555
12594
 
12556
12595
  // src/commands/memory-archive.ts
12557
- import { existsSync as existsSync65 } from "fs";
12596
+ import { existsSync as existsSync66 } from "fs";
12558
12597
  import { writeFile as writeFile31 } from "fs/promises";
12559
12598
  import path45 from "path";
12560
12599
  import "commander";
12561
12600
  import {
12562
- findProjectRoot as findProjectRoot43,
12563
- getUsage as getUsage20,
12601
+ findProjectRoot as findProjectRoot44,
12602
+ getUsage as getUsage21,
12564
12603
  retirementSignal as retirementSignal2,
12565
12604
  loadConfig as loadConfig10,
12566
12605
  loadMemoriesFromDir as loadMemoriesFromDir33,
12567
- loadUsageIndex as loadUsageIndex26,
12568
- resolveHaivePaths as resolveHaivePaths39,
12606
+ loadUsageIndex as loadUsageIndex27,
12607
+ resolveHaivePaths as resolveHaivePaths40,
12569
12608
  serializeMemory as serializeMemory25
12570
12609
  } from "@hiveai/core";
12571
12610
  var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
@@ -12573,9 +12612,9 @@ function registerMemoryArchive(memory2) {
12573
12612
  memory2.command("archive").description(
12574
12613
  "Archive obsolete memories: marks status='deprecated' for memories not read in N days\n whose anchored paths have all disappeared (or have no anchor at all).\n\n Defaults to a DRY RUN \u2014 pass --apply to actually rewrite files.\n Targets `attempt` memories by default since they age the fastest.\n\n Recover later with `haive memory edit <id>` to set status back to validated."
12575
12614
  ).option("--since <window>", "minimum age since last read (e.g. '180d', '6m'). Default: enforcement.decayAfterDays or 180d").option("--type <type>", "limit to a memory type (default 'attempt'). Pass 'all' to scan all types.", "attempt").option("--unread", "decay by unread-age ALONE (ignore anchor status) \u2014 more aggressive corpus hygiene", false).option("--apply", "actually rewrite files (default: dry run)", false).option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
12576
- const root = findProjectRoot43(opts.dir);
12577
- const paths = resolveHaivePaths39(root);
12578
- if (!existsSync65(paths.memoriesDir)) {
12615
+ const root = findProjectRoot44(opts.dir);
12616
+ const paths = resolveHaivePaths40(root);
12617
+ if (!existsSync66(paths.memoriesDir)) {
12579
12618
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
12580
12619
  process.exitCode = 1;
12581
12620
  return;
@@ -12590,7 +12629,7 @@ function registerMemoryArchive(memory2) {
12590
12629
  }
12591
12630
  const cutoff = Date.now() - minDays * MS_PER_DAY2;
12592
12631
  const all = await loadMemoriesFromDir33(paths.memoriesDir);
12593
- const usage = await loadUsageIndex26(paths);
12632
+ const usage = await loadUsageIndex27(paths);
12594
12633
  const typeFilter = opts.type === "all" ? null : opts.type ?? "attempt";
12595
12634
  const candidates = [];
12596
12635
  for (const { memory: mem, filePath } of all) {
@@ -12600,10 +12639,10 @@ function registerMemoryArchive(memory2) {
12600
12639
  if (fm.status === "deprecated" || fm.status === "rejected") continue;
12601
12640
  const retired = retirementSignal2(fm, mem.body);
12602
12641
  const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
12603
- const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync65(path45.join(paths.root, p)));
12642
+ const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync66(path45.join(paths.root, p)));
12604
12643
  const isAnchorless = !hasAnyAnchor;
12605
12644
  if (!retired.retired && !opts.unread && !isAnchorless && !allPathsGone) continue;
12606
- const u = getUsage20(usage, fm.id);
12645
+ const u = getUsage21(usage, fm.id);
12607
12646
  const lastSeen = u.last_read_at ?? fm.created_at;
12608
12647
  if (!retired.retired && Date.parse(lastSeen) >= cutoff) continue;
12609
12648
  const reason = retired.retired ? `retired lifecycle signal: ${retired.reason ?? "unknown"}` : isAnchorless ? `anchorless and not read since ${lastSeen.slice(0, 10)}` : allPathsGone ? `all ${fm.anchor.paths.length} anchored path(s) missing and not read since ${lastSeen.slice(0, 10)}` : `not read since ${lastSeen.slice(0, 10)} (unread decay)`;
@@ -12675,34 +12714,34 @@ function parseDays(input) {
12675
12714
  }
12676
12715
 
12677
12716
  // src/commands/doctor.ts
12678
- import { existsSync as existsSync66, statSync as statSync2 } from "fs";
12717
+ import { existsSync as existsSync67, statSync as statSync2 } from "fs";
12679
12718
  import { readFile as readFile21, stat, writeFile as writeFile33 } from "fs/promises";
12680
12719
  import path46 from "path";
12681
12720
  import { execFileSync, execSync as execSync3 } from "child_process";
12682
12721
  import "commander";
12683
12722
  import {
12684
12723
  codeMapPath as codeMapPath2,
12685
- findProjectRoot as findProjectRoot44,
12686
- getUsage as getUsage21,
12724
+ findProjectRoot as findProjectRoot45,
12725
+ getUsage as getUsage23,
12687
12726
  isStackPackSeed as isStackPackSeed4,
12688
12727
  loadCodeMap as loadCodeMap7,
12689
12728
  loadConfig as loadConfig11,
12690
12729
  loadMemoriesFromDir as loadMemoriesFromDir34,
12691
- loadUsageIndex as loadUsageIndex27,
12730
+ loadUsageIndex as loadUsageIndex28,
12692
12731
  readUsageEvents as readUsageEvents4,
12693
- resolveHaivePaths as resolveHaivePaths40
12732
+ resolveHaivePaths as resolveHaivePaths41
12694
12733
  } from "@hiveai/core";
12695
12734
  var MS_PER_DAY3 = 24 * 60 * 60 * 1e3;
12696
12735
  function registerDoctor(program2) {
12697
12736
  program2.command("doctor").description(
12698
12737
  "Analyze the local hAIve setup and emit actionable recommendations.\n\n Inspects: project-context status, memory health (stale/anchorless/decay/pending),\n code-map freshness, usage log signals (low-hit briefings, repeated empty searches).\n\n Read-only by default. Pass --fix to apply safe autopilot repairs."
12699
12738
  ).option("--json", "emit JSON instead of human-readable output", false).option("--fix", "include suggested fix commands in human output", false).option("--dry-run", "with --fix, show delegated repairs without applying them", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
12700
- const root = findProjectRoot44(opts.dir);
12701
- const paths = resolveHaivePaths40(root);
12739
+ const root = findProjectRoot45(opts.dir);
12740
+ const paths = resolveHaivePaths41(root);
12702
12741
  const findings = [];
12703
12742
  const repairs = [];
12704
12743
  const config = await loadConfig11(paths);
12705
- if (!existsSync66(paths.haiveDir)) {
12744
+ if (!existsSync67(paths.haiveDir)) {
12706
12745
  findings.push({
12707
12746
  severity: "error",
12708
12747
  code: "not-initialized",
@@ -12723,7 +12762,7 @@ function registerDoctor(program2) {
12723
12762
  })
12724
12763
  );
12725
12764
  }
12726
- if (!existsSync66(paths.projectContext)) {
12765
+ if (!existsSync67(paths.projectContext)) {
12727
12766
  findings.push({
12728
12767
  severity: "warn",
12729
12768
  code: "no-project-context",
@@ -12752,7 +12791,7 @@ function registerDoctor(program2) {
12752
12791
  });
12753
12792
  }
12754
12793
  }
12755
- const memories = existsSync66(paths.memoriesDir) ? await loadMemoriesFromDir34(paths.memoriesDir) : [];
12794
+ const memories = existsSync67(paths.memoriesDir) ? await loadMemoriesFromDir34(paths.memoriesDir) : [];
12756
12795
  const now = Date.now();
12757
12796
  if (memories.length === 0) {
12758
12797
  findings.push({
@@ -12761,7 +12800,7 @@ function registerDoctor(program2) {
12761
12800
  message: "No memories yet. Capture knowledge as agents work via mem_save / mem_observe / mem_tried."
12762
12801
  });
12763
12802
  } else {
12764
- const usage = await loadUsageIndex27(paths);
12803
+ const usage = await loadUsageIndex28(paths);
12765
12804
  const stale = memories.filter((m) => m.memory.frontmatter.status === "stale");
12766
12805
  if (stale.length > 0) {
12767
12806
  findings.push({
@@ -12818,7 +12857,7 @@ function registerDoctor(program2) {
12818
12857
  }
12819
12858
  const decayCandidates = memories.filter((m) => {
12820
12859
  if (m.memory.frontmatter.status !== "validated") return false;
12821
- const u = getUsage21(usage, m.memory.frontmatter.id);
12860
+ const u = getUsage23(usage, m.memory.frontmatter.id);
12822
12861
  const last = u.last_read_at ?? m.memory.frontmatter.created_at;
12823
12862
  return (now - Date.parse(last)) / MS_PER_DAY3 > 180;
12824
12863
  });
@@ -12904,7 +12943,7 @@ function registerDoctor(program2) {
12904
12943
  if (config.enforcement?.requireBriefingFirst) {
12905
12944
  const claudeSettings = path46.join(root, ".claude", "settings.local.json");
12906
12945
  let hasClaudeEnforcement = false;
12907
- if (existsSync66(claudeSettings)) {
12946
+ if (existsSync67(claudeSettings)) {
12908
12947
  try {
12909
12948
  const { readFile: readFile25 } = await import("fs/promises");
12910
12949
  const raw = await readFile25(claudeSettings, "utf8");
@@ -12930,14 +12969,14 @@ function registerDoctor(program2) {
12930
12969
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
12931
12970
  });
12932
12971
  }
12933
- findings.push(...await collectInstallFindings(root, "0.11.0"));
12972
+ findings.push(...await collectInstallFindings(root, "0.12.0"));
12934
12973
  try {
12935
12974
  const legacyRaw = execSync3("haive-mcp --version", {
12936
12975
  encoding: "utf8",
12937
12976
  timeout: 3e3,
12938
12977
  stdio: ["ignore", "pipe", "ignore"]
12939
12978
  }).trim();
12940
- const cliVersion = "0.11.0";
12979
+ const cliVersion = "0.12.0";
12941
12980
  if (legacyRaw && legacyRaw !== cliVersion) {
12942
12981
  findings.push({
12943
12982
  severity: "warn",
@@ -12959,7 +12998,7 @@ npm uninstall -g @hiveai/mcp`
12959
12998
  ];
12960
12999
  const staleConfigs = [];
12961
13000
  for (const cfgPath of configPaths) {
12962
- if (!existsSync66(cfgPath)) continue;
13001
+ if (!existsSync67(cfgPath)) continue;
12963
13002
  try {
12964
13003
  const raw = await readFile21(cfgPath, "utf8");
12965
13004
  if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
@@ -13254,7 +13293,7 @@ which -a haive`
13254
13293
  ];
13255
13294
  for (const rel of integrationFiles) {
13256
13295
  const file = path46.join(root, rel);
13257
- if (!existsSync66(file)) continue;
13296
+ if (!existsSync67(file)) continue;
13258
13297
  const text = await readFile21(file, "utf8").catch(() => "");
13259
13298
  for (const bin of extractAbsoluteHaiveBins(text)) {
13260
13299
  const version = versionForBinary(bin);
@@ -13350,7 +13389,7 @@ function collectGlobalHivemoduleFindings(expectedVersion) {
13350
13389
  }
13351
13390
  }
13352
13391
  async function readJson(file) {
13353
- if (!existsSync66(file)) return null;
13392
+ if (!existsSync67(file)) return null;
13354
13393
  try {
13355
13394
  return JSON.parse(await readFile21(file, "utf8"));
13356
13395
  } catch {
@@ -13397,22 +13436,22 @@ function extractAbsoluteHaiveBins(text) {
13397
13436
  }
13398
13437
 
13399
13438
  // src/commands/playback.ts
13400
- import { existsSync as existsSync67 } from "fs";
13439
+ import { existsSync as existsSync68 } from "fs";
13401
13440
  import "commander";
13402
13441
  import {
13403
- findProjectRoot as findProjectRoot45,
13442
+ findProjectRoot as findProjectRoot46,
13404
13443
  loadMemoriesFromDir as loadMemoriesFromDir35,
13405
13444
  parseSince as parseSince3,
13406
13445
  readUsageEvents as readUsageEvents5,
13407
- resolveHaivePaths as resolveHaivePaths41
13446
+ resolveHaivePaths as resolveHaivePaths42
13408
13447
  } from "@hiveai/core";
13409
13448
  var MS_PER_MINUTE = 6e4;
13410
13449
  function registerPlayback(program2) {
13411
13450
  program2.command("playback").description(
13412
13451
  "Replay past sessions from the usage log. For each session, show:\n - tool calls (what kind, how many)\n - briefing tasks asked\n - memories that have been created since then (that the session didn't have)\n\n Useful to ask 'would today's haive have helped past me on this task?'"
13413
13452
  ).option("--since <window>", "limit to events in this window (e.g. '7d')", "30d").option("--session-gap <minutes>", "minutes of inactivity that splits a session", "30").option("--limit <n>", "show at most this many sessions (newest first)", "10").option("--json", "emit JSON instead of human-readable output", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
13414
- const root = findProjectRoot45(opts.dir);
13415
- const paths = resolveHaivePaths41(root);
13453
+ const root = findProjectRoot46(opts.dir);
13454
+ const paths = resolveHaivePaths42(root);
13416
13455
  const events = await readUsageEvents5(paths);
13417
13456
  if (events.length === 0) {
13418
13457
  if (opts.json) {
@@ -13427,7 +13466,7 @@ function registerPlayback(program2) {
13427
13466
  const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
13428
13467
  const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
13429
13468
  const sessions = bucketSessions(filtered, gapMs);
13430
- const all = existsSync67(paths.memoriesDir) ? await loadMemoriesFromDir35(paths.memoriesDir) : [];
13469
+ const all = existsSync68(paths.memoriesDir) ? await loadMemoriesFromDir35(paths.memoriesDir) : [];
13431
13470
  const memByCreatedAt = all.filter(({ memory: memory2 }) => memory2.frontmatter.type !== "session_recap").map(({ memory: memory2 }) => ({ id: memory2.frontmatter.id, at: Date.parse(memory2.frontmatter.created_at) })).sort((a, b) => a.at - b.at);
13432
13471
  const enriched = sessions.map((s, i) => {
13433
13472
  const startMs = Date.parse(s.start);
@@ -13518,9 +13557,9 @@ import { spawn as spawn5 } from "child_process";
13518
13557
  import "commander";
13519
13558
  import {
13520
13559
  antiPatternGateParams,
13521
- findProjectRoot as findProjectRoot46,
13560
+ findProjectRoot as findProjectRoot47,
13522
13561
  loadConfig as loadConfig12,
13523
- resolveHaivePaths as resolveHaivePaths42
13562
+ resolveHaivePaths as resolveHaivePaths43
13524
13563
  } from "@hiveai/core";
13525
13564
  function registerPrecommit(program2) {
13526
13565
  program2.command("precommit").description(
@@ -13532,8 +13571,8 @@ function registerPrecommit(program2) {
13532
13571
  "--no-anchored-blocks",
13533
13572
  "do not block on anchored, diff-corroborated anti-patterns (only block on very strong semantic matches)"
13534
13573
  ).option("--json", "emit JSON instead of human-readable output", false).option("--paths <paths...>", "explicit paths to check (skips git diff)").option("-d, --dir <dir>", "project root").action(async (opts) => {
13535
- const root = findProjectRoot46(opts.dir);
13536
- const paths = resolveHaivePaths42(root);
13574
+ const root = findProjectRoot47(opts.dir);
13575
+ const paths = resolveHaivePaths43(root);
13537
13576
  const ctx = { paths };
13538
13577
  const config = await loadConfig12(paths);
13539
13578
  const gate = config.enforcement?.antiPatternGate ?? "anchored";
@@ -13669,12 +13708,12 @@ function runCommand3(cmd, args, cwd) {
13669
13708
  }
13670
13709
 
13671
13710
  // src/commands/welcome.ts
13672
- import { existsSync as existsSync68 } from "fs";
13711
+ import { existsSync as existsSync69 } from "fs";
13673
13712
  import "commander";
13674
13713
  import {
13675
- findProjectRoot as findProjectRoot47,
13714
+ findProjectRoot as findProjectRoot48,
13676
13715
  loadMemoriesFromDir as loadMemoriesFromDir36,
13677
- resolveHaivePaths as resolveHaivePaths43
13716
+ resolveHaivePaths as resolveHaivePaths44
13678
13717
  } from "@hiveai/core";
13679
13718
  var TYPE_RANK = {
13680
13719
  skill: 0,
@@ -13689,9 +13728,9 @@ function registerWelcome(program2) {
13689
13728
  program2.command("welcome").description(
13690
13729
  "Onboarding checklist: ranks validated/proposed **team** memories by type.\nUse after `haive init` so new devs skim institutional knowledge quickly.\n\n haive welcome\n haive welcome --limit 15\n"
13691
13730
  ).option("--limit <n>", "maximum memories listed", "20").option("-d, --dir <dir>", "project root").action(async (opts) => {
13692
- const root = findProjectRoot47(opts.dir);
13693
- const paths = resolveHaivePaths43(root);
13694
- if (!existsSync68(paths.memoriesDir)) {
13731
+ const root = findProjectRoot48(opts.dir);
13732
+ const paths = resolveHaivePaths44(root);
13733
+ if (!existsSync69(paths.memoriesDir)) {
13695
13734
  ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
13696
13735
  process.exitCode = 1;
13697
13736
  return;
@@ -13763,14 +13802,14 @@ function registerResolveProject(program2) {
13763
13802
  }
13764
13803
 
13765
13804
  // src/commands/runtime-journal.ts
13766
- import { existsSync as existsSync69 } from "fs";
13805
+ import { existsSync as existsSync70 } from "fs";
13767
13806
  import path48 from "path";
13768
13807
  import "commander";
13769
13808
  import {
13770
13809
  appendRuntimeJournalEntry as appendRuntimeJournalEntry3,
13771
- findProjectRoot as findProjectRoot48,
13810
+ findProjectRoot as findProjectRoot49,
13772
13811
  readRuntimeJournalTail as readRuntimeJournalTail2,
13773
- resolveHaivePaths as resolveHaivePaths44
13812
+ resolveHaivePaths as resolveHaivePaths45
13774
13813
  } from "@hiveai/core";
13775
13814
  function registerRuntime(program2) {
13776
13815
  const runtime = program2.command("runtime").description(
@@ -13779,7 +13818,7 @@ function registerRuntime(program2) {
13779
13818
  const journal = runtime.command("journal").description("Append or read the machine-local session journal (NDJSON)");
13780
13819
  journal.command("append").description("Append one JSON line to .ai/.runtime/session-journal.ndjson").argument("<message>", "short text to log").option("-k, --kind <kind>", "note | session_end | mcp", "note").option("-d, --dir <dir>", "project root", process.cwd()).action(async (message, opts) => {
13781
13820
  const root = path48.resolve(opts.dir ?? process.cwd());
13782
- const paths = resolveHaivePaths44(findProjectRoot48(root));
13821
+ const paths = resolveHaivePaths45(findProjectRoot49(root));
13783
13822
  const raw = opts.kind ?? "note";
13784
13823
  const kind = ["note", "session_end", "mcp"].includes(raw) ? raw : "note";
13785
13824
  await appendRuntimeJournalEntry3(paths, { kind, message });
@@ -13787,9 +13826,9 @@ function registerRuntime(program2) {
13787
13826
  });
13788
13827
  journal.command("tail").description("Print the last N entries from the runtime session journal as JSON").option("-n, --limit <n>", "number of lines", "30").option("-d, --dir <dir>", "project root", process.cwd()).action(async (opts) => {
13789
13828
  const root = path48.resolve(opts.dir ?? process.cwd());
13790
- const paths = resolveHaivePaths44(findProjectRoot48(root));
13829
+ const paths = resolveHaivePaths45(findProjectRoot49(root));
13791
13830
  const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
13792
- if (!existsSync69(paths.haiveDir)) {
13831
+ if (!existsSync70(paths.haiveDir)) {
13793
13832
  ui.error("No .ai/ \u2014 run `haive init` first.");
13794
13833
  process.exitCode = 1;
13795
13834
  return;
@@ -13804,13 +13843,13 @@ function registerRuntime(program2) {
13804
13843
  }
13805
13844
 
13806
13845
  // src/commands/memory-timeline.ts
13807
- import { existsSync as existsSync70 } from "fs";
13846
+ import { existsSync as existsSync71 } from "fs";
13808
13847
  import path49 from "path";
13809
13848
  import "commander";
13810
13849
  import {
13811
13850
  collectTimelineEntries as collectTimelineEntries2,
13812
- findProjectRoot as findProjectRoot49,
13813
- resolveHaivePaths as resolveHaivePaths45
13851
+ findProjectRoot as findProjectRoot50,
13852
+ resolveHaivePaths as resolveHaivePaths46
13814
13853
  } from "@hiveai/core";
13815
13854
  function registerMemoryTimeline(memory2) {
13816
13855
  memory2.command("timeline").description(
@@ -13822,8 +13861,8 @@ function registerMemoryTimeline(memory2) {
13822
13861
  return;
13823
13862
  }
13824
13863
  const root = path49.resolve(opts.dir ?? process.cwd());
13825
- const paths = resolveHaivePaths45(findProjectRoot49(root));
13826
- if (!existsSync70(paths.memoriesDir)) {
13864
+ const paths = resolveHaivePaths46(findProjectRoot50(root));
13865
+ if (!existsSync71(paths.memoriesDir)) {
13827
13866
  ui.error("No memories \u2014 run `haive init`.");
13828
13867
  process.exitCode = 1;
13829
13868
  return;
@@ -13841,14 +13880,14 @@ function registerMemoryTimeline(memory2) {
13841
13880
  }
13842
13881
 
13843
13882
  // src/commands/memory-conflict-candidates.ts
13844
- import { existsSync as existsSync71 } from "fs";
13883
+ import { existsSync as existsSync73 } from "fs";
13845
13884
  import path50 from "path";
13846
13885
  import "commander";
13847
13886
  import {
13848
13887
  findLexicalConflictPairs as findLexicalConflictPairs2,
13849
13888
  findTopicStatusConflictPairs as findTopicStatusConflictPairs2,
13850
- findProjectRoot as findProjectRoot50,
13851
- resolveHaivePaths as resolveHaivePaths46
13889
+ findProjectRoot as findProjectRoot51,
13890
+ resolveHaivePaths as resolveHaivePaths47
13852
13891
  } from "@hiveai/core";
13853
13892
  function parseTypes(csv) {
13854
13893
  const allowed = ["decision", "architecture", "convention", "gotcha"];
@@ -13865,8 +13904,8 @@ function registerMemoryConflictCandidates(memory2) {
13865
13904
  "decision,architecture"
13866
13905
  ).option("--min-jaccard <x>", "minimum Jaccard for lexical pairs", "0.45").option("--max-pairs <n>", "cap lexical pairs", "20").option("--max-scan <n>", "max memories scanned (lexical)", "500").option("--max-topic-pairs <n>", "cap topic/status pairs", "20").action(async (opts) => {
13867
13906
  const root = path50.resolve(opts.dir ?? process.cwd());
13868
- const paths = resolveHaivePaths46(findProjectRoot50(root));
13869
- if (!existsSync71(paths.memoriesDir)) {
13907
+ const paths = resolveHaivePaths47(findProjectRoot51(root));
13908
+ if (!existsSync73(paths.memoriesDir)) {
13870
13909
  ui.error("No memories \u2014 run `haive init`.");
13871
13910
  process.exitCode = 1;
13872
13911
  return;
@@ -13902,13 +13941,13 @@ function registerMemoryConflictCandidates(memory2) {
13902
13941
 
13903
13942
  // src/commands/enforce.ts
13904
13943
  import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
13905
- import { existsSync as existsSync73, statSync as statSync3 } from "fs";
13944
+ import { existsSync as existsSync74, statSync as statSync3 } from "fs";
13906
13945
  import { chmod as chmod2, mkdir as mkdir19, readFile as readFile23, readdir as readdir6, rm as rm3, writeFile as writeFile34 } from "fs/promises";
13907
13946
  import path51 from "path";
13908
13947
  import "commander";
13909
13948
  import {
13910
13949
  antiPatternGateParams as antiPatternGateParams2,
13911
- findProjectRoot as findProjectRoot51,
13950
+ findProjectRoot as findProjectRoot52,
13912
13951
  hasRecentBriefingMarker as hasRecentBriefingMarker2,
13913
13952
  isFreshIsoDate,
13914
13953
  loadConfig as loadConfig13,
@@ -13916,7 +13955,7 @@ import {
13916
13955
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
13917
13956
  readRecentBriefingMarker,
13918
13957
  resolveBriefingBudget as resolveBriefingBudget3,
13919
- resolveHaivePaths as resolveHaivePaths47,
13958
+ resolveHaivePaths as resolveHaivePaths48,
13920
13959
  saveConfig as saveConfig4,
13921
13960
  SESSION_RECAP_TTL_MS,
13922
13961
  verifyAnchor as verifyAnchor4,
@@ -13929,8 +13968,8 @@ function registerEnforce(program2) {
13929
13968
  "Agent-agnostic enforcement helpers: install policy gates, report status, and block unsafe workflows."
13930
13969
  );
13931
13970
  enforce.command("install").description("Install hAIve enforcement across MCP config, git hooks, CI template, and supported client hooks.").option("-d, --dir <dir>", "project root").option("--no-git", "skip git pre-commit/pre-push enforcement hooks").option("--no-claude", "skip Claude Code hooks").option("--no-ci", "skip GitHub Actions enforcement workflow").action(async (opts) => {
13932
- const root = findProjectRoot51(opts.dir);
13933
- const paths = resolveHaivePaths47(root);
13971
+ const root = findProjectRoot52(opts.dir);
13972
+ const paths = resolveHaivePaths48(root);
13934
13973
  await mkdir19(paths.haiveDir, { recursive: true });
13935
13974
  const current = await loadConfig13(paths);
13936
13975
  await saveConfig4(paths, {
@@ -13974,17 +14013,17 @@ function registerEnforce(program2) {
13974
14013
  if (report.should_block) process.exit(2);
13975
14014
  });
13976
14015
  enforce.command("cleanup").description("Remove generated hAIve runtime/cache artifacts that should not appear in commits.").option("-d, --dir <dir>", "project root").option("--dry-run", "print what would be removed without deleting", false).action(async (opts) => {
13977
- const root = findProjectRoot51(opts.dir);
13978
- const paths = resolveHaivePaths47(root);
14016
+ const root = findProjectRoot52(opts.dir);
14017
+ const paths = resolveHaivePaths48(root);
13979
14018
  const cacheDir = path51.join(paths.haiveDir, ".cache");
13980
- if (existsSync73(cacheDir)) {
14019
+ if (existsSync74(cacheDir)) {
13981
14020
  if (opts.dryRun) ui.info(`would clean ${path51.relative(root, cacheDir)} (preserving .gitignore)`);
13982
14021
  else {
13983
14022
  const removed = await cleanupCacheDir(cacheDir);
13984
14023
  ui.success(`cleaned ${path51.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
13985
14024
  }
13986
14025
  }
13987
- if (existsSync73(paths.runtimeDir)) {
14026
+ if (existsSync74(paths.runtimeDir)) {
13988
14027
  if (opts.dryRun) ui.info(`would clean ${path51.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
13989
14028
  else {
13990
14029
  const removed = await cleanupRuntimeDir(paths.runtimeDir);
@@ -14008,8 +14047,8 @@ function registerEnforce(program2) {
14008
14047
  const payload = await readHookPayload();
14009
14048
  const root = resolveRoot(opts.dir, payload);
14010
14049
  if (!root) return;
14011
- const paths = resolveHaivePaths47(root);
14012
- if (!existsSync73(paths.haiveDir)) return;
14050
+ const paths = resolveHaivePaths48(root);
14051
+ if (!existsSync74(paths.haiveDir)) return;
14013
14052
  await mkdir19(paths.runtimeDir, { recursive: true });
14014
14053
  const sessionId = opts.sessionId ?? payload.session_id;
14015
14054
  const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this hAIve-initialized project.";
@@ -14071,8 +14110,8 @@ ${briefing.project_context.content.slice(0, 1800)}`);
14071
14110
  const payload = await readHookPayload();
14072
14111
  const root = resolveRoot(opts.dir, payload);
14073
14112
  if (!root) return;
14074
- const paths = resolveHaivePaths47(root);
14075
- if (!existsSync73(paths.haiveDir)) return;
14113
+ const paths = resolveHaivePaths48(root);
14114
+ if (!existsSync74(paths.haiveDir)) return;
14076
14115
  if (!isWriteLikeTool(payload)) return;
14077
14116
  const ok = await hasRecentBriefingMarker2(paths, payload.session_id);
14078
14117
  if (ok) {
@@ -14114,9 +14153,9 @@ ${briefing.project_context.content.slice(0, 1800)}`);
14114
14153
  });
14115
14154
  }
14116
14155
  async function buildFinishReport(dir) {
14117
- const root = findProjectRoot51(dir);
14118
- const paths = resolveHaivePaths47(root);
14119
- const initialized = existsSync73(paths.haiveDir);
14156
+ const root = findProjectRoot52(dir);
14157
+ const paths = resolveHaivePaths48(root);
14158
+ const initialized = existsSync74(paths.haiveDir);
14120
14159
  const config = initialized ? await loadConfig13(paths) : {};
14121
14160
  const mode = config.enforcement?.mode ?? "strict";
14122
14161
  const findings = [];
@@ -14310,9 +14349,9 @@ function finishReport(root, initialized, mode, findings, config) {
14310
14349
  });
14311
14350
  }
14312
14351
  async function runWithEnforcement(command, args, opts) {
14313
- const root = findProjectRoot51(opts.dir);
14314
- const paths = resolveHaivePaths47(root);
14315
- if (!existsSync73(paths.haiveDir)) {
14352
+ const root = findProjectRoot52(opts.dir);
14353
+ const paths = resolveHaivePaths48(root);
14354
+ if (!existsSync74(paths.haiveDir)) {
14316
14355
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
14317
14356
  process.exit(1);
14318
14357
  }
@@ -14405,9 +14444,9 @@ async function writeWrapperBriefing(paths, sessionId, task) {
14405
14444
  return file;
14406
14445
  }
14407
14446
  async function buildEnforcementReport(dir, stage, sessionId) {
14408
- const root = findProjectRoot51(dir);
14409
- const paths = resolveHaivePaths47(root);
14410
- const initialized = existsSync73(paths.haiveDir);
14447
+ const root = findProjectRoot52(dir);
14448
+ const paths = resolveHaivePaths48(root);
14449
+ const initialized = existsSync74(paths.haiveDir);
14411
14450
  const config = initialized ? await loadConfig13(paths) : {};
14412
14451
  if (initialized) await applyLightweightRepairs(root, paths);
14413
14452
  const mode = config.enforcement?.mode ?? "strict";
@@ -14438,7 +14477,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
14438
14477
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
14439
14478
  });
14440
14479
  }
14441
- findings.push(...await inspectIntegrationVersions(root, "0.11.0"));
14480
+ findings.push(...await inspectIntegrationVersions(root, "0.12.0"));
14442
14481
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
14443
14482
  const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
14444
14483
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
@@ -14508,7 +14547,7 @@ function withCategories(report) {
14508
14547
  };
14509
14548
  }
14510
14549
  async function hasRecentSessionRecap(paths) {
14511
- if (!existsSync73(paths.memoriesDir)) return false;
14550
+ if (!existsSync74(paths.memoriesDir)) return false;
14512
14551
  const all = await loadMemoriesFromDir37(paths.memoriesDir);
14513
14552
  return all.some(({ memory: memory2 }) => {
14514
14553
  const fm = memory2.frontmatter;
@@ -14517,7 +14556,7 @@ async function hasRecentSessionRecap(paths) {
14517
14556
  });
14518
14557
  }
14519
14558
  async function verifyMemoryPolicy(paths, config) {
14520
- if (!existsSync73(paths.memoriesDir)) return [];
14559
+ if (!existsSync74(paths.memoriesDir)) return [];
14521
14560
  const all = await loadMemoriesFromDir37(paths.memoriesDir);
14522
14561
  const findings = [];
14523
14562
  const staleImportant = [];
@@ -14555,7 +14594,7 @@ async function verifyMemoryPolicy(paths, config) {
14555
14594
  return findings;
14556
14595
  }
14557
14596
  async function verifyDecisionCoverage(paths, stage, sessionId) {
14558
- if (!existsSync73(paths.memoriesDir)) return [];
14597
+ if (!existsSync74(paths.memoriesDir)) return [];
14559
14598
  const changedFiles = await getChangedFiles(paths.root, stage);
14560
14599
  if (changedFiles.length === 0) {
14561
14600
  return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
@@ -14667,7 +14706,7 @@ async function cleanupRuntimeDir(runtimeDir) {
14667
14706
  removed++;
14668
14707
  }
14669
14708
  await writeFile34(path51.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
14670
- if (!existsSync73(path51.join(runtimeDir, "README.md"))) {
14709
+ if (!existsSync74(path51.join(runtimeDir, "README.md"))) {
14671
14710
  await writeFile34(
14672
14711
  path51.join(runtimeDir, "README.md"),
14673
14712
  "# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. hAIve cleanup preserves briefing markers so enforcement state remains valid.\n",
@@ -14710,7 +14749,7 @@ async function inspectIntegrationVersions(root, expectedVersion) {
14710
14749
  const findings = [];
14711
14750
  for (const rel of files) {
14712
14751
  const file = path51.join(root, rel);
14713
- if (!existsSync73(file)) continue;
14752
+ if (!existsSync74(file)) continue;
14714
14753
  const text = await readFile23(file, "utf8").catch(() => "");
14715
14754
  for (const bin of extractAbsoluteHaiveBins2(text)) {
14716
14755
  const version = versionForBinary2(bin);
@@ -14820,7 +14859,7 @@ async function resolveCiDiffRange(root) {
14820
14859
  }
14821
14860
  async function resolveGithubEventRange(root) {
14822
14861
  const eventPath = process.env.GITHUB_EVENT_PATH;
14823
- if (!eventPath || !existsSync73(eventPath)) return null;
14862
+ if (!eventPath || !existsSync74(eventPath)) return null;
14824
14863
  try {
14825
14864
  const event = JSON.parse(await readFile23(eventPath, "utf8"));
14826
14865
  const prBase = cleanGitSha(event.pull_request?.base?.sha);
@@ -15001,7 +15040,7 @@ function buildScore(findings, threshold = 80) {
15001
15040
  }
15002
15041
  async function installGitEnforcement(root) {
15003
15042
  const hooksDir = path51.join(root, ".git", "hooks");
15004
- if (!existsSync73(path51.join(root, ".git"))) {
15043
+ if (!existsSync74(path51.join(root, ".git"))) {
15005
15044
  ui.warn("No .git directory found; git enforcement hooks skipped.");
15006
15045
  return;
15007
15046
  }
@@ -15024,7 +15063,7 @@ haive enforce check --stage pre-push --dir . || exit $?
15024
15063
  ];
15025
15064
  for (const hook of hooks) {
15026
15065
  const file = path51.join(hooksDir, hook.name);
15027
- if (existsSync73(file)) {
15066
+ if (existsSync74(file)) {
15028
15067
  const current = await readFile23(file, "utf8").catch(() => "");
15029
15068
  if (current.includes(ENFORCE_HOOK_MARKER)) {
15030
15069
  await writeFile34(file, hook.body, "utf8");
@@ -15043,7 +15082,7 @@ ${hook.body}`, "utf8");
15043
15082
  async function installCiEnforcement(root) {
15044
15083
  const workflowPath = path51.join(root, ".github", "workflows", "haive-enforcement.yml");
15045
15084
  await mkdir19(path51.dirname(workflowPath), { recursive: true });
15046
- if (existsSync73(workflowPath)) {
15085
+ if (existsSync74(workflowPath)) {
15047
15086
  ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
15048
15087
  return;
15049
15088
  }
@@ -15134,7 +15173,7 @@ async function readHookPayload() {
15134
15173
  }
15135
15174
  function resolveRoot(dir, payload) {
15136
15175
  try {
15137
- return findProjectRoot51(dir ?? payload.cwd);
15176
+ return findProjectRoot52(dir ?? payload.cwd);
15138
15177
  } catch {
15139
15178
  return null;
15140
15179
  }
@@ -15175,7 +15214,7 @@ function normalizeToolPath(file, root) {
15175
15214
  return path51.relative(root, normalized).replace(/\\/g, "/");
15176
15215
  }
15177
15216
  async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
15178
- if (!existsSync73(paths.memoriesDir)) return [];
15217
+ if (!existsSync74(paths.memoriesDir)) return [];
15179
15218
  const marker = await readRecentBriefingMarker(paths, sessionId);
15180
15219
  const consulted = new Set(marker?.memory_ids ?? []);
15181
15220
  const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention", "attempt"]);
@@ -15249,16 +15288,16 @@ function registerRun(program2) {
15249
15288
 
15250
15289
  // src/commands/sensors.ts
15251
15290
  import { execFile as execFile2 } from "child_process";
15252
- import { existsSync as existsSync74 } from "fs";
15291
+ import { existsSync as existsSync75 } from "fs";
15253
15292
  import { chmod as chmod3, mkdir as mkdir20, readFile as readFile24, writeFile as writeFile35 } from "fs/promises";
15254
15293
  import path53 from "path";
15255
15294
  import { promisify as promisify2 } from "util";
15256
15295
  import "commander";
15257
15296
  import {
15258
- findProjectRoot as findProjectRoot52,
15297
+ findProjectRoot as findProjectRoot53,
15259
15298
  isRetiredMemory as isRetiredMemory3,
15260
15299
  loadMemoriesFromDir as loadMemoriesFromDir38,
15261
- resolveHaivePaths as resolveHaivePaths48,
15300
+ resolveHaivePaths as resolveHaivePaths49,
15262
15301
  runSensors as runSensors2,
15263
15302
  sensorTargetsFromDiff as sensorTargetsFromDiff2,
15264
15303
  serializeMemory as serializeMemory26
@@ -15267,8 +15306,8 @@ var exec2 = promisify2(execFile2);
15267
15306
  function registerSensors(program2) {
15268
15307
  const sensors = program2.command("sensors").description("Operate executable sensors derived from hAIve memories");
15269
15308
  sensors.command("list").description("List memories carrying executable sensors").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
15270
- const root = findProjectRoot52(opts.dir);
15271
- const paths = resolveHaivePaths48(root);
15309
+ const root = findProjectRoot53(opts.dir);
15310
+ const paths = resolveHaivePaths49(root);
15272
15311
  const rows = await sensorRows(paths);
15273
15312
  if (opts.json) {
15274
15313
  console.log(JSON.stringify(rows, null, 2));
@@ -15288,8 +15327,8 @@ function registerSensors(program2) {
15288
15327
  }
15289
15328
  });
15290
15329
  sensors.command("check").description("Run regex sensors against a diff; defaults to `git diff --cached`").option("--diff-file <path>", "read unified diff from a file instead of staged changes").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
15291
- const root = findProjectRoot52(opts.dir);
15292
- const paths = resolveHaivePaths48(root);
15330
+ const root = findProjectRoot53(opts.dir);
15331
+ const paths = resolveHaivePaths49(root);
15293
15332
  const memories = await runnableSensorMemories(paths);
15294
15333
  const diff = opts.diffFile ? await readFile24(path53.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
15295
15334
  const targets = sensorTargetsFromDiff2(diff);
@@ -15330,9 +15369,9 @@ function registerSensors(program2) {
15330
15369
  process.exitCode = 1;
15331
15370
  return;
15332
15371
  }
15333
- const root = findProjectRoot52(opts.dir);
15334
- const paths = resolveHaivePaths48(root);
15335
- const loaded = existsSync74(paths.memoriesDir) ? await loadMemoriesFromDir38(paths.memoriesDir) : [];
15372
+ const root = findProjectRoot53(opts.dir);
15373
+ const paths = resolveHaivePaths49(root);
15374
+ const loaded = existsSync75(paths.memoriesDir) ? await loadMemoriesFromDir38(paths.memoriesDir) : [];
15336
15375
  const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
15337
15376
  if (!found) {
15338
15377
  ui.error(`No memory found with id ${id}`);
@@ -15364,8 +15403,8 @@ function registerSensors(program2) {
15364
15403
  process.exitCode = 1;
15365
15404
  return;
15366
15405
  }
15367
- const root = findProjectRoot52(opts.dir);
15368
- const paths = resolveHaivePaths48(root);
15406
+ const root = findProjectRoot53(opts.dir);
15407
+ const paths = resolveHaivePaths49(root);
15369
15408
  const rows = await sensorRows(paths);
15370
15409
  const outDir = path53.resolve(root, opts.outDir ?? ".ai/generated");
15371
15410
  await mkdir20(outDir, { recursive: true });
@@ -15394,7 +15433,7 @@ async function sensorRows(paths) {
15394
15433
  });
15395
15434
  }
15396
15435
  async function runnableSensorMemories(paths, regexOnly = true) {
15397
- if (!existsSync74(paths.memoriesDir)) return [];
15436
+ if (!existsSync75(paths.memoriesDir)) return [];
15398
15437
  const loaded = await loadMemoriesFromDir38(paths.memoriesDir);
15399
15438
  return loaded.map(({ memory: memory2 }) => memory2).filter((memory2) => {
15400
15439
  const sensor = memory2.frontmatter.sensor;
@@ -15436,8 +15475,8 @@ function shellQuote(value) {
15436
15475
  }
15437
15476
 
15438
15477
  // src/index.ts
15439
- var program = new Command55();
15440
- program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.11.0").option("--advanced", "show maintenance and experimental commands in help");
15478
+ var program = new Command56();
15479
+ program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.12.0").option("--advanced", "show maintenance and experimental commands in help");
15441
15480
  registerInit(program);
15442
15481
  registerWelcome(program);
15443
15482
  registerResolveProject(program);
@@ -15462,6 +15501,7 @@ registerMemoryPromote(memory);
15462
15501
  registerMemoryVerify(memory);
15463
15502
  registerMemoryStats(memory);
15464
15503
  registerMemoryImpact(memory);
15504
+ registerMemoryFeedback(memory);
15465
15505
  registerMemoryReject(memory);
15466
15506
  registerMemoryAutoPromote(memory);
15467
15507
  registerMemoryForFiles(memory);