@hiveai/cli 0.11.0 → 0.12.1

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.1"}`;
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,
@@ -3782,6 +3782,7 @@ import {
3782
3782
  loadMemoriesFromDir as loadMemoriesFromDir14,
3783
3783
  loadUsageIndex as loadUsageIndex8,
3784
3784
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths22,
3785
+ rankMemoriesLexical as rankMemoriesLexical2,
3785
3786
  queryCodeMap as queryCodeMap2,
3786
3787
  resolveBriefingBudget as resolveBriefingBudget2,
3787
3788
  serializeMemory as serializeMemory9,
@@ -3822,7 +3823,10 @@ import { z as z24 } from "zod";
3822
3823
  import { existsSync as existsSync24 } from "fs";
3823
3824
  import {
3824
3825
  addedLinesFromDiff,
3826
+ buildDocFrequency,
3827
+ CODE_STOPWORDS,
3825
3828
  deriveConfidence as deriveConfidence6,
3829
+ diffHasDistinctiveOverlap,
3826
3830
  getUsage as getUsage8,
3827
3831
  isRetiredMemory as isRetiredMemory2,
3828
3832
  loadMemoriesFromDir as loadMemoriesFromDir18,
@@ -5469,13 +5473,25 @@ async function getBriefing(input, ctx) {
5469
5473
  }
5470
5474
  if (act.applicable && act.activated) activatedSkills.add(id);
5471
5475
  }
5476
+ const lexNorm = /* @__PURE__ */ new Map();
5477
+ if (input.task) {
5478
+ const candidates = [...seen.keys()].map((id) => byId.get(id)).filter((x) => Boolean(x));
5479
+ const lex = rankMemoriesLexical2(candidates, input.task, candidates.length);
5480
+ const maxScore = lex.scores.reduce((m, s) => s > m ? s : m, 0);
5481
+ if (maxScore > 0) {
5482
+ lex.ranked.forEach((loaded, i) => {
5483
+ lexNorm.set(loaded.memory.frontmatter.id, (lex.scores[i] ?? 0) / maxScore);
5484
+ });
5485
+ }
5486
+ }
5472
5487
  const ranked = [...seen.values()].sort((a, b) => {
5473
5488
  const reasonScore = (m) => (m.type === "attempt" ? 3 : 0) + (m.reasons.includes("anchor") ? 4 : 0) + (m.reasons.includes("symbol") ? 4 : 0) + (m.reasons.includes("module") ? 2 : 0) + (m.reasons.includes("semantic") ? 2 : 0) + (m.reasons.includes("domain") ? 1 : 0);
5474
5489
  const confidenceScore = (m) => m.confidence === "authoritative" ? 4 : m.confidence === "trusted" ? 3 : m.confidence === "low" ? 1 : m.confidence === "stale" ? -2 : 0;
5475
5490
  const impactScore = (m) => (m.impact_score ?? 0) * 3;
5476
5491
  const activationBoost = (m) => activatedSkills.has(m.id) ? 5 : 0;
5477
- const sa = priorityRank(classifyMemoryPriority(a, byId.get(a.id), input.files, input.symbols)) * 100 + reasonScore(a) + confidenceScore(a) + impactScore(a) + activationBoost(a) + (a.semantic_score ?? 0);
5478
- const sb = priorityRank(classifyMemoryPriority(b, byId.get(b.id), input.files, input.symbols)) * 100 + reasonScore(b) + confidenceScore(b) + impactScore(b) + activationBoost(b) + (b.semantic_score ?? 0);
5492
+ const lexScore = (m) => 12 * (lexNorm.get(m.id) ?? 0);
5493
+ const sa = priorityRank(classifyMemoryPriority(a, byId.get(a.id), input.files, input.symbols)) * 100 + reasonScore(a) + confidenceScore(a) + impactScore(a) + activationBoost(a) + lexScore(a) + (a.semantic_score ?? 0);
5494
+ const sb = priorityRank(classifyMemoryPriority(b, byId.get(b.id), input.files, input.symbols)) * 100 + reasonScore(b) + confidenceScore(b) + impactScore(b) + activationBoost(b) + lexScore(b) + (b.semantic_score ?? 0);
5479
5495
  return sb - sa;
5480
5496
  });
5481
5497
  for (const mem of ranked.slice(0, briefingMaxMemories)) {
@@ -6145,53 +6161,6 @@ var AntiPatternsCheckInputSchema = {
6145
6161
  "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
6162
  )
6147
6163
  };
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
6164
  function tokenizeDiffForLiteral(diff) {
6196
6165
  const lines = diff.split("\n");
6197
6166
  const looksLikeDiff = lines.some((l) => /^[+-]/.test(l));
@@ -6224,6 +6193,7 @@ async function antiPatternsCheck(input, ctx) {
6224
6193
  return { scanned: 0, warnings: [], notice: "No attempt/gotcha memories found yet." };
6225
6194
  }
6226
6195
  const usage = await loadUsageIndex10(ctx.paths);
6196
+ const docFreq = buildDocFrequency(negative.map(({ memory: memory2 }) => memory2.body));
6227
6197
  const seen = /* @__PURE__ */ new Map();
6228
6198
  const upsert = (fm, body, reason, score) => {
6229
6199
  const existing = seen.get(fm.id);
@@ -6257,10 +6227,16 @@ async function antiPatternsCheck(input, ctx) {
6257
6227
  }
6258
6228
  if (input.diff) {
6259
6229
  const tokens = tokenizeDiffForLiteral(input.diff);
6230
+ const added = addedLinesFromDiff(input.diff);
6231
+ const addedText = added.trim().length > 0 ? added : input.diff;
6260
6232
  if (tokens.length > 0) {
6261
6233
  for (const { memory: memory2 } of negative) {
6262
6234
  if (literalMatchesAnyToken3(memory2, tokens)) {
6263
6235
  upsert(memory2.frontmatter, memory2.body, "literal");
6236
+ if (diffHasDistinctiveOverlap(addedText, memory2.body, docFreq)) {
6237
+ const w = seen.get(memory2.frontmatter.id);
6238
+ if (w) w.distinctive_literal = true;
6239
+ }
6264
6240
  }
6265
6241
  }
6266
6242
  }
@@ -6858,7 +6834,12 @@ function classifyWarning(warning, paths, anchoredBlocks = false) {
6858
6834
  const hasSemantic = warning.reasons.includes("semantic");
6859
6835
  const semanticScore = warning.semantic_score ?? 0;
6860
6836
  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)) {
6837
+ if (anchoredBlocks && highConfidence && warning.scope !== "personal" && warning.reasons.includes("anchor") && // A literal overlap only corroborates a BLOCK when it is on a token *distinctive*
6838
+ // to this gotcha (rare in the corpus). Sharing a common domain word ("memory",
6839
+ // "scope", "version") — or a version-bump diff — no longer hard-blocks; it falls
6840
+ // through to `review` below. This kills the incidental-token false positives that
6841
+ // made agents work for nothing. A moderate semantic match still corroborates.
6842
+ (warning.distinctive_literal === true || hasSemantic && semanticScore >= 0.45)) {
6862
6843
  if (warning.has_sensor && !warning.reasons.includes("sensor")) {
6863
6844
  return {
6864
6845
  ...warning,
@@ -7550,7 +7531,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
7550
7531
  };
7551
7532
  }
7552
7533
  var SERVER_NAME = "haive";
7553
- var SERVER_VERSION = "0.11.0";
7534
+ var SERVER_VERSION = "0.12.1";
7554
7535
  function jsonResult(data) {
7555
7536
  return {
7556
7537
  content: [
@@ -9126,7 +9107,7 @@ function registerMemoryAdd(memory2) {
9126
9107
  haive memory add --type convention --slug flyway-no-modify --topic flyway \\\\
9127
9108
  --scope team --body "Never modify existing migrations. Create V{n+1}__desc.sql."
9128
9109
  `
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) => {
9110
+ ).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
9111
  const root = findProjectRoot13(opts.dir);
9131
9112
  const paths = resolveHaivePaths10(root);
9132
9113
  if (!existsSync33(paths.haiveDir)) {
@@ -9137,6 +9118,11 @@ function registerMemoryAdd(memory2) {
9137
9118
  const config = await loadConfig5(paths);
9138
9119
  const userTags = parseCsv2(opts.tags);
9139
9120
  const anchorPaths = parseCsv2(opts.paths ?? opts.files);
9121
+ const activation = opts.type === "skill" && (opts.activationKeyword || opts.activationGlob || opts.activationAlways) ? {
9122
+ keywords: parseCsv2(opts.activationKeyword),
9123
+ globs: parseCsv2(opts.activationGlob),
9124
+ always: Boolean(opts.activationAlways)
9125
+ } : void 0;
9140
9126
  const autoTagsEnabled = opts.autoTag !== false;
9141
9127
  const inferredTags = autoTagsEnabled ? inferModulesFromPaths3(anchorPaths) : [];
9142
9128
  const mergedTags = Array.from(/* @__PURE__ */ new Set([...userTags, ...inferredTags]));
@@ -9194,6 +9180,7 @@ TODO \u2014 write the memory body.
9194
9180
  const newFrontmatter = {
9195
9181
  ...fm,
9196
9182
  revision_count: revisionCount,
9183
+ ...activation ? { activation } : {},
9197
9184
  tags: mergedTags.length ? mergedTags : fm.tags,
9198
9185
  anchor: {
9199
9186
  commit: opts.commit ?? fm.anchor.commit,
@@ -9224,7 +9211,8 @@ TODO \u2014 write the memory body.
9224
9211
  commit: opts.commit,
9225
9212
  topic: opts.topic,
9226
9213
  status: config.defaultStatus === "validated" ? "validated" : void 0,
9227
- sensor: suggestSensorForCliMemory(opts.type, body, anchorPaths) ?? void 0
9214
+ sensor: suggestSensorForCliMemory(opts.type, body, anchorPaths) ?? void 0,
9215
+ activation
9228
9216
  });
9229
9217
  const file = memoryFilePath6(paths, frontmatter.scope, frontmatter.id, frontmatter.module);
9230
9218
  await mkdir11(path16.dirname(file), { recursive: true });
@@ -10556,14 +10544,68 @@ function pad(value, width) {
10556
10544
  return value.slice(0, width - 1) + "\u2026";
10557
10545
  }
10558
10546
 
10559
- // src/commands/memory-verify.ts
10560
- import { writeFile as writeFile21 } from "fs/promises";
10547
+ // src/commands/memory-feedback.ts
10561
10548
  import { existsSync as existsSync53 } from "fs";
10562
- import path34 from "path";
10563
10549
  import "commander";
10564
10550
  import {
10551
+ computeImpact as computeImpact4,
10565
10552
  findProjectRoot as findProjectRoot31,
10553
+ getUsage as getUsage19,
10554
+ loadUsageIndex as loadUsageIndex24,
10555
+ recordApplied as recordApplied2,
10556
+ recordRejection as recordRejection4,
10566
10557
  resolveHaivePaths as resolveHaivePaths28,
10558
+ saveUsageIndex as saveUsageIndex6
10559
+ } from "@hiveai/core";
10560
+ function registerMemoryFeedback(memory2) {
10561
+ memory2.command("feedback <id>").description(
10562
+ "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`."
10563
+ ).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) => {
10564
+ if (opts.applied === opts.rejected) {
10565
+ ui.error("Specify exactly one of --applied or --rejected.");
10566
+ process.exitCode = 1;
10567
+ return;
10568
+ }
10569
+ const root = findProjectRoot31(opts.dir);
10570
+ const paths = resolveHaivePaths28(root);
10571
+ if (!existsSync53(paths.memoriesDir)) {
10572
+ ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
10573
+ process.exitCode = 1;
10574
+ return;
10575
+ }
10576
+ const all = await loadMemoriesFromDir26(paths.memoriesDir);
10577
+ const target = all.find((m) => m.memory.frontmatter.id === id);
10578
+ if (!target) {
10579
+ ui.error(`No memory with id '${id}'.`);
10580
+ process.exitCode = 1;
10581
+ return;
10582
+ }
10583
+ const index = await loadUsageIndex24(paths);
10584
+ const outcome = opts.applied ? "applied" : "rejected";
10585
+ if (opts.applied) recordApplied2(index, id);
10586
+ else recordRejection4(index, id, opts.reason ?? null);
10587
+ await saveUsageIndex6(paths, index);
10588
+ const usage = getUsage19(index, id);
10589
+ const impact = computeImpact4(target.memory.frontmatter, usage);
10590
+ if (opts.json) {
10591
+ console.log(JSON.stringify({ id, outcome, usage, impact }, null, 2));
10592
+ return;
10593
+ }
10594
+ ui.success(`Recorded '${outcome}' for ${id}`);
10595
+ ui.info(
10596
+ `applied=${usage.applied_count} \xB7 rejected=${usage.rejected_count} \xB7 read=${usage.read_count} \u2192 impact ${impact.score.toFixed(2)} (${impact.tier})`
10597
+ );
10598
+ });
10599
+ }
10600
+
10601
+ // src/commands/memory-verify.ts
10602
+ import { writeFile as writeFile21 } from "fs/promises";
10603
+ import { existsSync as existsSync54 } from "fs";
10604
+ import path34 from "path";
10605
+ import "commander";
10606
+ import {
10607
+ findProjectRoot as findProjectRoot32,
10608
+ resolveHaivePaths as resolveHaivePaths29,
10567
10609
  serializeMemory as serializeMemory19,
10568
10610
  verifyAnchor as verifyAnchor3
10569
10611
  } from "@hiveai/core";
@@ -10571,9 +10613,9 @@ function registerMemoryVerify(memory2) {
10571
10613
  memory2.command("verify").description(
10572
10614
  "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
10615
  ).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)) {
10616
+ const root = findProjectRoot32(opts.dir);
10617
+ const paths = resolveHaivePaths29(root);
10618
+ if (!existsSync54(paths.memoriesDir)) {
10577
10619
  if (opts.json) {
10578
10620
  console.log(JSON.stringify({ error: "not-initialized", root }, null, 2));
10579
10621
  } else {
@@ -10694,24 +10736,24 @@ function applyVerification2(mem, result) {
10694
10736
 
10695
10737
  // src/commands/memory-import.ts
10696
10738
  import { readFile as readFile15 } from "fs/promises";
10697
- import { existsSync as existsSync54 } from "fs";
10739
+ import { existsSync as existsSync55 } from "fs";
10698
10740
  import "commander";
10699
10741
  import {
10700
- findProjectRoot as findProjectRoot32,
10701
- resolveHaivePaths as resolveHaivePaths29
10742
+ findProjectRoot as findProjectRoot33,
10743
+ resolveHaivePaths as resolveHaivePaths30
10702
10744
  } from "@hiveai/core";
10703
10745
  function registerMemoryImport(memory2) {
10704
10746
  memory2.command("import").description(
10705
10747
  "Parse a Markdown file and suggest memories via the import_docs MCP prompt (prints a ready-to-use prompt invocation)"
10706
10748
  ).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)) {
10749
+ const root = findProjectRoot33(opts.dir);
10750
+ const paths = resolveHaivePaths30(root);
10751
+ if (!existsSync55(paths.haiveDir)) {
10710
10752
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
10711
10753
  process.exitCode = 1;
10712
10754
  return;
10713
10755
  }
10714
- if (!existsSync54(opts.from)) {
10756
+ if (!existsSync55(opts.from)) {
10715
10757
  ui.error(`File not found: ${opts.from}`);
10716
10758
  process.exitCode = 1;
10717
10759
  return;
@@ -10744,14 +10786,14 @@ function registerMemoryImport(memory2) {
10744
10786
  }
10745
10787
 
10746
10788
  // src/commands/memory-import-changelog.ts
10747
- import { existsSync as existsSync55 } from "fs";
10789
+ import { existsSync as existsSync56 } from "fs";
10748
10790
  import { readFile as readFile16, mkdir as mkdir14, writeFile as writeFile23 } from "fs/promises";
10749
10791
  import path35 from "path";
10750
10792
  import "commander";
10751
10793
  import {
10752
10794
  buildFrontmatter as buildFrontmatter9,
10753
- findProjectRoot as findProjectRoot33,
10754
- resolveHaivePaths as resolveHaivePaths30,
10795
+ findProjectRoot as findProjectRoot34,
10796
+ resolveHaivePaths as resolveHaivePaths31,
10755
10797
  serializeMemory as serializeMemory20
10756
10798
  } from "@hiveai/core";
10757
10799
  function parseChangelog(content) {
@@ -10816,10 +10858,10 @@ function registerMemoryImportChangelog(memory2) {
10816
10858
  "--versions <csv>",
10817
10859
  "only import specific versions (comma-separated), or 'latest' for the most recent breaking version"
10818
10860
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
10819
- const root = findProjectRoot33(opts.dir);
10820
- const paths = resolveHaivePaths30(root);
10861
+ const root = findProjectRoot34(opts.dir);
10862
+ const paths = resolveHaivePaths31(root);
10821
10863
  const changelogPath = path35.resolve(root, opts.fromChangelog);
10822
- if (!existsSync55(changelogPath)) {
10864
+ if (!existsSync56(changelogPath)) {
10823
10865
  ui.error(`CHANGELOG not found: ${changelogPath}`);
10824
10866
  process.exitCode = 1;
10825
10867
  return;
@@ -10906,17 +10948,17 @@ ${ui.bold(`Imported ${saved} changelog entr${saved === 1 ? "y" : "ies"} from ${p
10906
10948
  }
10907
10949
 
10908
10950
  // src/commands/memory-digest.ts
10909
- import { existsSync as existsSync56 } from "fs";
10951
+ import { existsSync as existsSync57 } from "fs";
10910
10952
  import { writeFile as writeFile24 } from "fs/promises";
10911
10953
  import path36 from "path";
10912
10954
  import "commander";
10913
10955
  import {
10914
10956
  deriveConfidence as deriveConfidence12,
10915
- findProjectRoot as findProjectRoot34,
10916
- getUsage as getUsage19,
10957
+ findProjectRoot as findProjectRoot35,
10958
+ getUsage as getUsage20,
10917
10959
  loadMemoriesFromDir as loadMemoriesFromDir27,
10918
- loadUsageIndex as loadUsageIndex24,
10919
- resolveHaivePaths as resolveHaivePaths31
10960
+ loadUsageIndex as loadUsageIndex25,
10961
+ resolveHaivePaths as resolveHaivePaths32
10920
10962
  } from "@hiveai/core";
10921
10963
  var CONFIDENCE_EMOJI = {
10922
10964
  unverified: "\u2B1C",
@@ -10929,9 +10971,9 @@ function registerMemoryDigest(program2) {
10929
10971
  program2.command("digest").description(
10930
10972
  "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
10973
  ).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)) {
10974
+ const root = findProjectRoot35(opts.dir);
10975
+ const paths = resolveHaivePaths32(root);
10976
+ if (!existsSync57(paths.memoriesDir)) {
10935
10977
  ui.error("No .ai/memories found. Run `haive init` first.");
10936
10978
  process.exitCode = 1;
10937
10979
  return;
@@ -10940,7 +10982,7 @@ function registerMemoryDigest(program2) {
10940
10982
  const scopeFilter = opts.scope ?? "team";
10941
10983
  const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3);
10942
10984
  const all = await loadMemoriesFromDir27(paths.memoriesDir);
10943
- const usage = await loadUsageIndex24(paths);
10985
+ const usage = await loadUsageIndex25(paths);
10944
10986
  const recent = all.filter(({ memory: mem }) => {
10945
10987
  const fm = mem.frontmatter;
10946
10988
  if (fm.type === "session_recap") return false;
@@ -10971,7 +11013,7 @@ function registerMemoryDigest(program2) {
10971
11013
  lines.push(``);
10972
11014
  for (const { memory: mem } of mems) {
10973
11015
  const fm = mem.frontmatter;
10974
- const u = getUsage19(usage, fm.id);
11016
+ const u = getUsage20(usage, fm.id);
10975
11017
  const confidence = deriveConfidence12(fm, u);
10976
11018
  const emoji = CONFIDENCE_EMOJI[confidence] ?? "\u2B1C";
10977
11019
  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 +11056,21 @@ function registerMemoryDigest(program2) {
11014
11056
 
11015
11057
  // src/commands/session-end.ts
11016
11058
  import { writeFile as writeFile25, mkdir as mkdir15, readFile as readFile17, rm as rm2 } from "fs/promises";
11017
- import { existsSync as existsSync57 } from "fs";
11059
+ import { existsSync as existsSync58 } from "fs";
11018
11060
  import { spawn as spawn4 } from "child_process";
11019
11061
  import path37 from "path";
11020
11062
  import "commander";
11021
11063
  import {
11022
11064
  buildFrontmatter as buildFrontmatter10,
11023
- findProjectRoot as findProjectRoot35,
11065
+ findProjectRoot as findProjectRoot36,
11024
11066
  loadMemoriesFromDir as loadMemoriesFromDir28,
11025
11067
  memoryFilePath as memoryFilePath9,
11026
- resolveHaivePaths as resolveHaivePaths32,
11068
+ resolveHaivePaths as resolveHaivePaths33,
11027
11069
  serializeMemory as serializeMemory21
11028
11070
  } from "@hiveai/core";
11029
11071
  async function buildAutoRecap(paths) {
11030
11072
  const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
11031
- if (!existsSync57(obsFile)) return await buildGitAutoRecap(paths);
11073
+ if (!existsSync58(obsFile)) return await buildGitAutoRecap(paths);
11032
11074
  const raw = await readFile17(obsFile, "utf8").catch(() => "");
11033
11075
  if (!raw.trim()) return await buildGitAutoRecap(paths);
11034
11076
  const lines = raw.split("\n").filter(Boolean);
@@ -11217,9 +11259,9 @@ function registerSessionEnd(session2) {
11217
11259
  --next "Add integration tests for webhook signature validation"
11218
11260
  `
11219
11261
  ).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)) {
11262
+ const root = findProjectRoot36(opts.dir);
11263
+ const paths = resolveHaivePaths33(root);
11264
+ if (!existsSync58(paths.haiveDir)) {
11223
11265
  if (opts.auto || opts.quiet) return;
11224
11266
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
11225
11267
  process.exitCode = 1;
@@ -11252,7 +11294,7 @@ function registerSessionEnd(session2) {
11252
11294
  });
11253
11295
  const topic = recapTopic2(scope, opts.module);
11254
11296
  const filesTouched = parseCsv5(resolvedFiles).map((p) => normalizeAnchorPath(root, p));
11255
- const missingPaths = filesTouched.filter((p) => !existsSync57(path37.resolve(root, p)));
11297
+ const missingPaths = filesTouched.filter((p) => !existsSync58(path37.resolve(root, p)));
11256
11298
  if (missingPaths.length > 0 && !opts.quiet) {
11257
11299
  ui.warn(`Anchor path${missingPaths.length > 1 ? "s" : ""} not found in project (will be stale):`);
11258
11300
  for (const p of missingPaths) ui.warn(` \u2717 ${p}`);
@@ -11260,10 +11302,10 @@ function registerSessionEnd(session2) {
11260
11302
  const cleanupObservations = async () => {
11261
11303
  if (!opts.auto) return;
11262
11304
  const obsFile = path37.join(paths.haiveDir, ".cache", "observations.jsonl");
11263
- if (existsSync57(obsFile)) await rm2(obsFile).catch(() => {
11305
+ if (existsSync58(obsFile)) await rm2(obsFile).catch(() => {
11264
11306
  });
11265
11307
  };
11266
- if (existsSync57(paths.memoriesDir)) {
11308
+ if (existsSync58(paths.memoriesDir)) {
11267
11309
  const existing = await loadMemoriesFromDir28(paths.memoriesDir);
11268
11310
  const topicMatch = existing.find(
11269
11311
  ({ memory: memory2 }) => memory2.frontmatter.topic === topic && memory2.frontmatter.scope === scope && (!opts.module || memory2.frontmatter.module === opts.module)
@@ -11325,15 +11367,15 @@ function normalizeAnchorPath(root, filePath) {
11325
11367
  }
11326
11368
 
11327
11369
  // src/commands/snapshot.ts
11328
- import { existsSync as existsSync58 } from "fs";
11370
+ import { existsSync as existsSync59 } from "fs";
11329
11371
  import { readdir as readdir4 } from "fs/promises";
11330
11372
  import path38 from "path";
11331
11373
  import "commander";
11332
11374
  import {
11333
11375
  diffContract,
11334
- findProjectRoot as findProjectRoot36,
11376
+ findProjectRoot as findProjectRoot37,
11335
11377
  loadConfig as loadConfig7,
11336
- resolveHaivePaths as resolveHaivePaths33,
11378
+ resolveHaivePaths as resolveHaivePaths34,
11337
11379
  snapshotContract
11338
11380
  } from "@hiveai/core";
11339
11381
  function registerSnapshot(program2) {
@@ -11358,16 +11400,16 @@ function registerSnapshot(program2) {
11358
11400
  "--format <format>",
11359
11401
  "contract format: openapi | graphql | proto | typescript | json-schema (auto-detected if omitted)"
11360
11402
  ).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)) {
11403
+ const root = findProjectRoot37(opts.dir);
11404
+ const paths = resolveHaivePaths34(root);
11405
+ if (!existsSync59(paths.haiveDir)) {
11364
11406
  ui.error("No .ai/ found. Run `haive init` first.");
11365
11407
  process.exitCode = 1;
11366
11408
  return;
11367
11409
  }
11368
11410
  if (opts.list) {
11369
11411
  const contractsDir = path38.join(paths.haiveDir, "contracts");
11370
- if (!existsSync58(contractsDir)) {
11412
+ if (!existsSync59(contractsDir)) {
11371
11413
  console.log(ui.dim("No contract snapshots found."));
11372
11414
  return;
11373
11415
  }
@@ -11491,16 +11533,16 @@ function detectFormat(filePath) {
11491
11533
  }
11492
11534
 
11493
11535
  // src/commands/hub.ts
11494
- import { existsSync as existsSync59 } from "fs";
11536
+ import { existsSync as existsSync60 } from "fs";
11495
11537
  import { mkdir as mkdir16, readFile as readFile18, writeFile as writeFile26, copyFile } from "fs/promises";
11496
11538
  import path39 from "path";
11497
11539
  import { spawnSync as spawnSync5 } from "child_process";
11498
11540
  import "commander";
11499
11541
  import {
11500
- findProjectRoot as findProjectRoot37,
11542
+ findProjectRoot as findProjectRoot38,
11501
11543
  loadConfig as loadConfig8,
11502
11544
  loadMemoriesFromDir as loadMemoriesFromDir29,
11503
- resolveHaivePaths as resolveHaivePaths34,
11545
+ resolveHaivePaths as resolveHaivePaths35,
11504
11546
  saveConfig as saveConfig3,
11505
11547
  serializeMemory as serializeMemory23
11506
11548
  } from "@hiveai/core";
@@ -11582,8 +11624,8 @@ Next steps:
11582
11624
  haive hub push --commit --message "feat: add payment API contract memories"
11583
11625
  `
11584
11626
  ).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);
11627
+ const root = findProjectRoot38(opts.dir);
11628
+ const paths = resolveHaivePaths35(root);
11587
11629
  const config = await loadConfig8(paths);
11588
11630
  if (!config.hubPath) {
11589
11631
  ui.error(
@@ -11593,7 +11635,7 @@ Next steps:
11593
11635
  return;
11594
11636
  }
11595
11637
  const hubRoot = path39.resolve(root, config.hubPath);
11596
- if (!existsSync59(hubRoot)) {
11638
+ if (!existsSync60(hubRoot)) {
11597
11639
  ui.error(`Hub not found at ${hubRoot}. Run \`haive hub init ${config.hubPath}\` first.`);
11598
11640
  process.exitCode = 1;
11599
11641
  return;
@@ -11651,8 +11693,8 @@ Next steps:
11651
11693
  hub.command("pull").description(
11652
11694
  "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
11695
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
11654
- const root = findProjectRoot37(opts.dir);
11655
- const paths = resolveHaivePaths34(root);
11696
+ const root = findProjectRoot38(opts.dir);
11697
+ const paths = resolveHaivePaths35(root);
11656
11698
  const config = await loadConfig8(paths);
11657
11699
  if (!config.hubPath) {
11658
11700
  ui.error(
@@ -11663,7 +11705,7 @@ Next steps:
11663
11705
  }
11664
11706
  const hubRoot = path39.resolve(root, config.hubPath);
11665
11707
  const hubSharedDir = path39.join(hubRoot, ".ai", "memories", "shared");
11666
- if (!existsSync59(hubSharedDir)) {
11708
+ if (!existsSync60(hubSharedDir)) {
11667
11709
  ui.warn("Hub has no shared memories yet. Run `haive hub push` from other projects first.");
11668
11710
  return;
11669
11711
  }
@@ -11710,15 +11752,15 @@ Next steps:
11710
11752
  );
11711
11753
  });
11712
11754
  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);
11755
+ const root = findProjectRoot38(opts.dir);
11756
+ const paths = resolveHaivePaths35(root);
11715
11757
  const config = await loadConfig8(paths);
11716
11758
  console.log(ui.bold("Hub status"));
11717
11759
  console.log(
11718
11760
  ` hubPath: ${config.hubPath ? ui.green(config.hubPath) : ui.dim("not configured")}`
11719
11761
  );
11720
11762
  const sharedDir = path39.join(paths.memoriesDir, "shared");
11721
- if (existsSync59(sharedDir)) {
11763
+ if (existsSync60(sharedDir)) {
11722
11764
  const { readdir: readdir7 } = await import("fs/promises");
11723
11765
  const sources = (await readdir7(sharedDir, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
11724
11766
  console.log(`
@@ -11747,17 +11789,17 @@ Next steps:
11747
11789
 
11748
11790
  // src/commands/stats.ts
11749
11791
  import "commander";
11750
- import { existsSync as existsSync60 } from "fs";
11792
+ import { existsSync as existsSync61 } from "fs";
11751
11793
  import { mkdir as mkdir17, writeFile as writeFile27 } from "fs/promises";
11752
11794
  import path40 from "path";
11753
11795
  import {
11754
11796
  aggregateUsage,
11755
- findProjectRoot as findProjectRoot38,
11797
+ findProjectRoot as findProjectRoot39,
11756
11798
  loadMemoriesFromDir as loadMemoriesFromDir30,
11757
- loadUsageIndex as loadUsageIndex25,
11799
+ loadUsageIndex as loadUsageIndex26,
11758
11800
  parseSince,
11759
11801
  readUsageEvents as readUsageEvents2,
11760
- resolveHaivePaths as resolveHaivePaths35,
11802
+ resolveHaivePaths as resolveHaivePaths36,
11761
11803
  usageLogSize
11762
11804
  } from "@hiveai/core";
11763
11805
  function registerStats(program2) {
@@ -11766,8 +11808,8 @@ function registerStats(program2) {
11766
11808
  "write a JSON rollup (tools + briefing counts + heuristic ROI hints). Parent dirs are created if needed.",
11767
11809
  void 0
11768
11810
  ).option("-d, --dir <dir>", "project root").action(async (opts) => {
11769
- const root = findProjectRoot38(opts.dir);
11770
- const paths = resolveHaivePaths35(root);
11811
+ const root = findProjectRoot39(opts.dir);
11812
+ const paths = resolveHaivePaths36(root);
11771
11813
  if (opts.exportReport) {
11772
11814
  await writeRoiReport(paths, root, opts.since ?? "30d", opts.exportReport);
11773
11815
  return;
@@ -11825,7 +11867,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
11825
11867
  const size = await usageLogSize(paths);
11826
11868
  let events = await readUsageEvents2(paths);
11827
11869
  let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
11828
- if (existsSync60(paths.memoriesDir)) {
11870
+ if (existsSync61(paths.memoriesDir)) {
11829
11871
  const mems = await loadMemoriesFromDir30(paths.memoriesDir);
11830
11872
  for (const { memory: memory2 } of mems) {
11831
11873
  const fm = memory2.frontmatter;
@@ -11840,7 +11882,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
11840
11882
  const briefingCalls = events.filter((e) => inWindow(e.at) && e.tool === "get_briefing").length;
11841
11883
  let memoryHitsLeader = null;
11842
11884
  try {
11843
- const usageIdx = await loadUsageIndex25(paths);
11885
+ const usageIdx = await loadUsageIndex26(paths);
11844
11886
  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
11887
  memoryHitsLeader = tops[0] ?? null;
11846
11888
  } catch {
@@ -11872,7 +11914,7 @@ async function writeRoiReport(paths, root, sinceRaw, outRelative) {
11872
11914
  ui.success(`Wrote ROI / usage rollup \u2192 ${outAbs}`);
11873
11915
  }
11874
11916
  async function renderMemoryHits(paths, opts) {
11875
- const index = await loadUsageIndex25(paths);
11917
+ const index = await loadUsageIndex26(paths);
11876
11918
  const since = parseSince(opts.since ?? "30d");
11877
11919
  const sinceMs = since ? new Date(since).getTime() : null;
11878
11920
  const entries = Object.entries(index.by_id).map(([id, usage]) => ({ id, ...usage })).filter((e) => e.read_count > 0).filter((e) => {
@@ -11920,13 +11962,13 @@ import { performance } from "perf_hooks";
11920
11962
  import "commander";
11921
11963
  import {
11922
11964
  estimateTokens as estimateTokens3,
11923
- findProjectRoot as findProjectRoot39,
11924
- resolveHaivePaths as resolveHaivePaths36
11965
+ findProjectRoot as findProjectRoot40,
11966
+ resolveHaivePaths as resolveHaivePaths37
11925
11967
  } from "@hiveai/core";
11926
11968
  function registerBench(program2) {
11927
11969
  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);
11970
+ const root = findProjectRoot40(opts.dir);
11971
+ const paths = resolveHaivePaths37(root);
11930
11972
  const ctx = { paths };
11931
11973
  const task = opts.task ?? "audit dependencies for security risks";
11932
11974
  const scenarios = [
@@ -12045,11 +12087,11 @@ function summarize(name, t0, payload, notes) {
12045
12087
  }
12046
12088
 
12047
12089
  // src/commands/benchmark.ts
12048
- import { existsSync as existsSync61 } from "fs";
12090
+ import { existsSync as existsSync63 } from "fs";
12049
12091
  import { readdir as readdir5, readFile as readFile19, writeFile as writeFile28 } from "fs/promises";
12050
12092
  import path41 from "path";
12051
12093
  import "commander";
12052
- import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot40 } from "@hiveai/core";
12094
+ import { estimateTokens as estimateTokens4, findProjectRoot as findProjectRoot41 } from "@hiveai/core";
12053
12095
  function registerBenchmark(program2) {
12054
12096
  const benchmark = program2.command("benchmark").description("Official hAIve benchmark/demo utilities for measuring agent enforcement value.");
12055
12097
  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 +12131,18 @@ function registerBenchmark(program2) {
12089
12131
  function resolveBenchmarkRoot(dir) {
12090
12132
  const candidate = dir ?? "benchmarks/agent-benchmark";
12091
12133
  if (path41.isAbsolute(candidate)) return candidate;
12092
- const projectRoot = findProjectRoot40(process.cwd());
12134
+ const projectRoot = findProjectRoot41(process.cwd());
12093
12135
  return path41.join(projectRoot, candidate);
12094
12136
  }
12095
12137
  async function collectRows(root) {
12096
- if (!existsSync61(root)) throw new Error(`Benchmark directory not found: ${root}`);
12138
+ if (!existsSync63(root)) throw new Error(`Benchmark directory not found: ${root}`);
12097
12139
  const entries = await readdir5(root, { withFileTypes: true });
12098
12140
  const rows = [];
12099
12141
  for (const entry of entries) {
12100
12142
  if (!entry.isDirectory()) continue;
12101
12143
  const fixtureDir = path41.join(root, entry.name);
12102
12144
  const reportFile = path41.join(fixtureDir, "BENCHMARK_AGENT_REPORT.md");
12103
- if (!existsSync61(reportFile)) continue;
12145
+ if (!existsSync63(reportFile)) continue;
12104
12146
  const report = await readFile19(reportFile, "utf8");
12105
12147
  rows.push(parseAgentReport(entry.name, report));
12106
12148
  }
@@ -12191,15 +12233,15 @@ function escapeRegExp(value) {
12191
12233
 
12192
12234
  // src/commands/eval.ts
12193
12235
  import { readFile as readFile20, writeFile as writeFile29 } from "fs/promises";
12194
- import { existsSync as existsSync63 } from "fs";
12236
+ import { existsSync as existsSync64 } from "fs";
12195
12237
  import path43 from "path";
12196
12238
  import "commander";
12197
12239
  import {
12198
12240
  aggregateRetrieval,
12199
12241
  aggregateSensors,
12200
12242
  buildReport,
12201
- findProjectRoot as findProjectRoot41,
12202
- resolveHaivePaths as resolveHaivePaths37,
12243
+ findProjectRoot as findProjectRoot42,
12244
+ resolveHaivePaths as resolveHaivePaths38,
12203
12245
  scoreRetrievalCase,
12204
12246
  scoreSensorCase,
12205
12247
  synthesizeSelfEvalCases
@@ -12207,10 +12249,10 @@ import {
12207
12249
  function registerEval(program2) {
12208
12250
  program2.command("eval").description(
12209
12251
  "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)) {
12252
+ ).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) => {
12253
+ const root = findProjectRoot42(opts.dir);
12254
+ const paths = resolveHaivePaths38(root);
12255
+ if (!existsSync64(paths.memoriesDir)) {
12214
12256
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
12215
12257
  process.exitCode = 1;
12216
12258
  return;
@@ -12243,16 +12285,26 @@ function registerEval(program2) {
12243
12285
  const report = buildReport(retrievalAgg, sensorAgg);
12244
12286
  if (opts.json) {
12245
12287
  console.log(JSON.stringify({ root, k, report }, null, 2));
12246
- return;
12288
+ } else {
12289
+ const md = renderMarkdown2(root, k, report);
12290
+ if (opts.out) {
12291
+ const outFile = path43.isAbsolute(opts.out) ? opts.out : path43.join(root, opts.out);
12292
+ await writeFile29(outFile, md, "utf8");
12293
+ ui.success(`wrote ${path43.relative(process.cwd(), outFile)}`);
12294
+ } else {
12295
+ console.log(md);
12296
+ }
12247
12297
  }
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;
12298
+ if (opts.failUnder !== void 0) {
12299
+ const threshold = Number(opts.failUnder);
12300
+ if (Number.isNaN(threshold)) {
12301
+ ui.error(`--fail-under expects a number, got "${opts.failUnder}"`);
12302
+ process.exitCode = 1;
12303
+ } else if (report.score < threshold) {
12304
+ ui.error(`eval score ${report.score} is below --fail-under ${threshold}`);
12305
+ process.exitCode = 1;
12306
+ }
12254
12307
  }
12255
- console.log(md);
12256
12308
  });
12257
12309
  }
12258
12310
  async function resolveSpec(opts, memoriesDir) {
@@ -12348,19 +12400,19 @@ function renderMarkdown2(root, k, report) {
12348
12400
 
12349
12401
  // src/commands/memory-suggest.ts
12350
12402
  import { mkdir as mkdir18, writeFile as writeFile30 } from "fs/promises";
12351
- import { existsSync as existsSync64 } from "fs";
12403
+ import { existsSync as existsSync65 } from "fs";
12352
12404
  import path44 from "path";
12353
12405
  import "commander";
12354
12406
  import {
12355
12407
  aggregateUsage as aggregateUsage2,
12356
12408
  buildFrontmatter as buildFrontmatter11,
12357
- findProjectRoot as findProjectRoot42,
12409
+ findProjectRoot as findProjectRoot43,
12358
12410
  loadConfig as loadConfig9,
12359
12411
  loadMemoriesFromDir as loadMemoriesFromDir31,
12360
12412
  memoryFilePath as memoryFilePath10,
12361
12413
  parseSince as parseSince2,
12362
12414
  readUsageEvents as readUsageEvents3,
12363
- resolveHaivePaths as resolveHaivePaths38,
12415
+ resolveHaivePaths as resolveHaivePaths39,
12364
12416
  serializeMemory as serializeMemory24
12365
12417
  } from "@hiveai/core";
12366
12418
  var SEARCH_TOOLS = /* @__PURE__ */ new Set([
@@ -12377,8 +12429,8 @@ function registerMemorySuggest(memory2) {
12377
12429
  memory2.command("suggest").description(
12378
12430
  "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
12431
  ).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);
12432
+ const root = findProjectRoot43(opts.dir);
12433
+ const paths = resolveHaivePaths39(root);
12382
12434
  const events = await readUsageEvents3(paths);
12383
12435
  if (events.length === 0) {
12384
12436
  if (opts.json) {
@@ -12427,7 +12479,7 @@ function registerMemorySuggest(memory2) {
12427
12479
  }
12428
12480
  const created = [];
12429
12481
  const skipped = [];
12430
- const existing = existsSync64(paths.memoriesDir) ? await loadMemoriesFromDir31(paths.memoriesDir) : [];
12482
+ const existing = existsSync65(paths.memoriesDir) ? await loadMemoriesFromDir31(paths.memoriesDir) : [];
12431
12483
  for (const s of top) {
12432
12484
  const slug = slugify2(s.query);
12433
12485
  if (!slug) {
@@ -12451,7 +12503,7 @@ function registerMemorySuggest(memory2) {
12451
12503
  const body = renderTemplate(s, fm.id, status);
12452
12504
  const file = memoryFilePath10(paths, fm.scope, fm.id, fm.module);
12453
12505
  await mkdir18(path44.dirname(file), { recursive: true });
12454
- if (existsSync64(file)) {
12506
+ if (existsSync65(file)) {
12455
12507
  skipped.push({ query: s.query, reason: `file already exists at ${path44.relative(root, file)}` });
12456
12508
  continue;
12457
12509
  }
@@ -12554,18 +12606,18 @@ function truncate2(text, max) {
12554
12606
  }
12555
12607
 
12556
12608
  // src/commands/memory-archive.ts
12557
- import { existsSync as existsSync65 } from "fs";
12609
+ import { existsSync as existsSync66 } from "fs";
12558
12610
  import { writeFile as writeFile31 } from "fs/promises";
12559
12611
  import path45 from "path";
12560
12612
  import "commander";
12561
12613
  import {
12562
- findProjectRoot as findProjectRoot43,
12563
- getUsage as getUsage20,
12614
+ findProjectRoot as findProjectRoot44,
12615
+ getUsage as getUsage21,
12564
12616
  retirementSignal as retirementSignal2,
12565
12617
  loadConfig as loadConfig10,
12566
12618
  loadMemoriesFromDir as loadMemoriesFromDir33,
12567
- loadUsageIndex as loadUsageIndex26,
12568
- resolveHaivePaths as resolveHaivePaths39,
12619
+ loadUsageIndex as loadUsageIndex27,
12620
+ resolveHaivePaths as resolveHaivePaths40,
12569
12621
  serializeMemory as serializeMemory25
12570
12622
  } from "@hiveai/core";
12571
12623
  var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
@@ -12573,9 +12625,9 @@ function registerMemoryArchive(memory2) {
12573
12625
  memory2.command("archive").description(
12574
12626
  "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
12627
  ).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)) {
12628
+ const root = findProjectRoot44(opts.dir);
12629
+ const paths = resolveHaivePaths40(root);
12630
+ if (!existsSync66(paths.memoriesDir)) {
12579
12631
  ui.error(`No .ai/memories at ${root}. Run \`haive init\` first.`);
12580
12632
  process.exitCode = 1;
12581
12633
  return;
@@ -12590,7 +12642,7 @@ function registerMemoryArchive(memory2) {
12590
12642
  }
12591
12643
  const cutoff = Date.now() - minDays * MS_PER_DAY2;
12592
12644
  const all = await loadMemoriesFromDir33(paths.memoriesDir);
12593
- const usage = await loadUsageIndex26(paths);
12645
+ const usage = await loadUsageIndex27(paths);
12594
12646
  const typeFilter = opts.type === "all" ? null : opts.type ?? "attempt";
12595
12647
  const candidates = [];
12596
12648
  for (const { memory: mem, filePath } of all) {
@@ -12600,10 +12652,10 @@ function registerMemoryArchive(memory2) {
12600
12652
  if (fm.status === "deprecated" || fm.status === "rejected") continue;
12601
12653
  const retired = retirementSignal2(fm, mem.body);
12602
12654
  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)));
12655
+ const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync66(path45.join(paths.root, p)));
12604
12656
  const isAnchorless = !hasAnyAnchor;
12605
12657
  if (!retired.retired && !opts.unread && !isAnchorless && !allPathsGone) continue;
12606
- const u = getUsage20(usage, fm.id);
12658
+ const u = getUsage21(usage, fm.id);
12607
12659
  const lastSeen = u.last_read_at ?? fm.created_at;
12608
12660
  if (!retired.retired && Date.parse(lastSeen) >= cutoff) continue;
12609
12661
  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 +12727,34 @@ function parseDays(input) {
12675
12727
  }
12676
12728
 
12677
12729
  // src/commands/doctor.ts
12678
- import { existsSync as existsSync66, statSync as statSync2 } from "fs";
12730
+ import { existsSync as existsSync67, statSync as statSync2 } from "fs";
12679
12731
  import { readFile as readFile21, stat, writeFile as writeFile33 } from "fs/promises";
12680
12732
  import path46 from "path";
12681
12733
  import { execFileSync, execSync as execSync3 } from "child_process";
12682
12734
  import "commander";
12683
12735
  import {
12684
12736
  codeMapPath as codeMapPath2,
12685
- findProjectRoot as findProjectRoot44,
12686
- getUsage as getUsage21,
12737
+ findProjectRoot as findProjectRoot45,
12738
+ getUsage as getUsage23,
12687
12739
  isStackPackSeed as isStackPackSeed4,
12688
12740
  loadCodeMap as loadCodeMap7,
12689
12741
  loadConfig as loadConfig11,
12690
12742
  loadMemoriesFromDir as loadMemoriesFromDir34,
12691
- loadUsageIndex as loadUsageIndex27,
12743
+ loadUsageIndex as loadUsageIndex28,
12692
12744
  readUsageEvents as readUsageEvents4,
12693
- resolveHaivePaths as resolveHaivePaths40
12745
+ resolveHaivePaths as resolveHaivePaths41
12694
12746
  } from "@hiveai/core";
12695
12747
  var MS_PER_DAY3 = 24 * 60 * 60 * 1e3;
12696
12748
  function registerDoctor(program2) {
12697
12749
  program2.command("doctor").description(
12698
12750
  "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
12751
  ).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);
12752
+ const root = findProjectRoot45(opts.dir);
12753
+ const paths = resolveHaivePaths41(root);
12702
12754
  const findings = [];
12703
12755
  const repairs = [];
12704
12756
  const config = await loadConfig11(paths);
12705
- if (!existsSync66(paths.haiveDir)) {
12757
+ if (!existsSync67(paths.haiveDir)) {
12706
12758
  findings.push({
12707
12759
  severity: "error",
12708
12760
  code: "not-initialized",
@@ -12723,7 +12775,7 @@ function registerDoctor(program2) {
12723
12775
  })
12724
12776
  );
12725
12777
  }
12726
- if (!existsSync66(paths.projectContext)) {
12778
+ if (!existsSync67(paths.projectContext)) {
12727
12779
  findings.push({
12728
12780
  severity: "warn",
12729
12781
  code: "no-project-context",
@@ -12752,7 +12804,7 @@ function registerDoctor(program2) {
12752
12804
  });
12753
12805
  }
12754
12806
  }
12755
- const memories = existsSync66(paths.memoriesDir) ? await loadMemoriesFromDir34(paths.memoriesDir) : [];
12807
+ const memories = existsSync67(paths.memoriesDir) ? await loadMemoriesFromDir34(paths.memoriesDir) : [];
12756
12808
  const now = Date.now();
12757
12809
  if (memories.length === 0) {
12758
12810
  findings.push({
@@ -12761,7 +12813,7 @@ function registerDoctor(program2) {
12761
12813
  message: "No memories yet. Capture knowledge as agents work via mem_save / mem_observe / mem_tried."
12762
12814
  });
12763
12815
  } else {
12764
- const usage = await loadUsageIndex27(paths);
12816
+ const usage = await loadUsageIndex28(paths);
12765
12817
  const stale = memories.filter((m) => m.memory.frontmatter.status === "stale");
12766
12818
  if (stale.length > 0) {
12767
12819
  findings.push({
@@ -12818,7 +12870,7 @@ function registerDoctor(program2) {
12818
12870
  }
12819
12871
  const decayCandidates = memories.filter((m) => {
12820
12872
  if (m.memory.frontmatter.status !== "validated") return false;
12821
- const u = getUsage21(usage, m.memory.frontmatter.id);
12873
+ const u = getUsage23(usage, m.memory.frontmatter.id);
12822
12874
  const last = u.last_read_at ?? m.memory.frontmatter.created_at;
12823
12875
  return (now - Date.parse(last)) / MS_PER_DAY3 > 180;
12824
12876
  });
@@ -12904,7 +12956,7 @@ function registerDoctor(program2) {
12904
12956
  if (config.enforcement?.requireBriefingFirst) {
12905
12957
  const claudeSettings = path46.join(root, ".claude", "settings.local.json");
12906
12958
  let hasClaudeEnforcement = false;
12907
- if (existsSync66(claudeSettings)) {
12959
+ if (existsSync67(claudeSettings)) {
12908
12960
  try {
12909
12961
  const { readFile: readFile25 } = await import("fs/promises");
12910
12962
  const raw = await readFile25(claudeSettings, "utf8");
@@ -12930,14 +12982,14 @@ function registerDoctor(program2) {
12930
12982
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
12931
12983
  });
12932
12984
  }
12933
- findings.push(...await collectInstallFindings(root, "0.11.0"));
12985
+ findings.push(...await collectInstallFindings(root, "0.12.1"));
12934
12986
  try {
12935
12987
  const legacyRaw = execSync3("haive-mcp --version", {
12936
12988
  encoding: "utf8",
12937
12989
  timeout: 3e3,
12938
12990
  stdio: ["ignore", "pipe", "ignore"]
12939
12991
  }).trim();
12940
- const cliVersion = "0.11.0";
12992
+ const cliVersion = "0.12.1";
12941
12993
  if (legacyRaw && legacyRaw !== cliVersion) {
12942
12994
  findings.push({
12943
12995
  severity: "warn",
@@ -12959,7 +13011,7 @@ npm uninstall -g @hiveai/mcp`
12959
13011
  ];
12960
13012
  const staleConfigs = [];
12961
13013
  for (const cfgPath of configPaths) {
12962
- if (!existsSync66(cfgPath)) continue;
13014
+ if (!existsSync67(cfgPath)) continue;
12963
13015
  try {
12964
13016
  const raw = await readFile21(cfgPath, "utf8");
12965
13017
  if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
@@ -13254,7 +13306,7 @@ which -a haive`
13254
13306
  ];
13255
13307
  for (const rel of integrationFiles) {
13256
13308
  const file = path46.join(root, rel);
13257
- if (!existsSync66(file)) continue;
13309
+ if (!existsSync67(file)) continue;
13258
13310
  const text = await readFile21(file, "utf8").catch(() => "");
13259
13311
  for (const bin of extractAbsoluteHaiveBins(text)) {
13260
13312
  const version = versionForBinary(bin);
@@ -13350,7 +13402,7 @@ function collectGlobalHivemoduleFindings(expectedVersion) {
13350
13402
  }
13351
13403
  }
13352
13404
  async function readJson(file) {
13353
- if (!existsSync66(file)) return null;
13405
+ if (!existsSync67(file)) return null;
13354
13406
  try {
13355
13407
  return JSON.parse(await readFile21(file, "utf8"));
13356
13408
  } catch {
@@ -13397,22 +13449,22 @@ function extractAbsoluteHaiveBins(text) {
13397
13449
  }
13398
13450
 
13399
13451
  // src/commands/playback.ts
13400
- import { existsSync as existsSync67 } from "fs";
13452
+ import { existsSync as existsSync68 } from "fs";
13401
13453
  import "commander";
13402
13454
  import {
13403
- findProjectRoot as findProjectRoot45,
13455
+ findProjectRoot as findProjectRoot46,
13404
13456
  loadMemoriesFromDir as loadMemoriesFromDir35,
13405
13457
  parseSince as parseSince3,
13406
13458
  readUsageEvents as readUsageEvents5,
13407
- resolveHaivePaths as resolveHaivePaths41
13459
+ resolveHaivePaths as resolveHaivePaths42
13408
13460
  } from "@hiveai/core";
13409
13461
  var MS_PER_MINUTE = 6e4;
13410
13462
  function registerPlayback(program2) {
13411
13463
  program2.command("playback").description(
13412
13464
  "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
13465
  ).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);
13466
+ const root = findProjectRoot46(opts.dir);
13467
+ const paths = resolveHaivePaths42(root);
13416
13468
  const events = await readUsageEvents5(paths);
13417
13469
  if (events.length === 0) {
13418
13470
  if (opts.json) {
@@ -13427,7 +13479,7 @@ function registerPlayback(program2) {
13427
13479
  const filtered = cutoff > 0 ? events.filter((e) => Date.parse(e.at) >= cutoff) : events;
13428
13480
  const gapMs = Math.max(1, parseInt(opts.sessionGap ?? "30", 10)) * MS_PER_MINUTE;
13429
13481
  const sessions = bucketSessions(filtered, gapMs);
13430
- const all = existsSync67(paths.memoriesDir) ? await loadMemoriesFromDir35(paths.memoriesDir) : [];
13482
+ const all = existsSync68(paths.memoriesDir) ? await loadMemoriesFromDir35(paths.memoriesDir) : [];
13431
13483
  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
13484
  const enriched = sessions.map((s, i) => {
13433
13485
  const startMs = Date.parse(s.start);
@@ -13518,9 +13570,9 @@ import { spawn as spawn5 } from "child_process";
13518
13570
  import "commander";
13519
13571
  import {
13520
13572
  antiPatternGateParams,
13521
- findProjectRoot as findProjectRoot46,
13573
+ findProjectRoot as findProjectRoot47,
13522
13574
  loadConfig as loadConfig12,
13523
- resolveHaivePaths as resolveHaivePaths42
13575
+ resolveHaivePaths as resolveHaivePaths43
13524
13576
  } from "@hiveai/core";
13525
13577
  function registerPrecommit(program2) {
13526
13578
  program2.command("precommit").description(
@@ -13532,8 +13584,8 @@ function registerPrecommit(program2) {
13532
13584
  "--no-anchored-blocks",
13533
13585
  "do not block on anchored, diff-corroborated anti-patterns (only block on very strong semantic matches)"
13534
13586
  ).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);
13587
+ const root = findProjectRoot47(opts.dir);
13588
+ const paths = resolveHaivePaths43(root);
13537
13589
  const ctx = { paths };
13538
13590
  const config = await loadConfig12(paths);
13539
13591
  const gate = config.enforcement?.antiPatternGate ?? "anchored";
@@ -13669,12 +13721,12 @@ function runCommand3(cmd, args, cwd) {
13669
13721
  }
13670
13722
 
13671
13723
  // src/commands/welcome.ts
13672
- import { existsSync as existsSync68 } from "fs";
13724
+ import { existsSync as existsSync69 } from "fs";
13673
13725
  import "commander";
13674
13726
  import {
13675
- findProjectRoot as findProjectRoot47,
13727
+ findProjectRoot as findProjectRoot48,
13676
13728
  loadMemoriesFromDir as loadMemoriesFromDir36,
13677
- resolveHaivePaths as resolveHaivePaths43
13729
+ resolveHaivePaths as resolveHaivePaths44
13678
13730
  } from "@hiveai/core";
13679
13731
  var TYPE_RANK = {
13680
13732
  skill: 0,
@@ -13689,9 +13741,9 @@ function registerWelcome(program2) {
13689
13741
  program2.command("welcome").description(
13690
13742
  "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
13743
  ).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)) {
13744
+ const root = findProjectRoot48(opts.dir);
13745
+ const paths = resolveHaivePaths44(root);
13746
+ if (!existsSync69(paths.memoriesDir)) {
13695
13747
  ui.error(`No memories at ${paths.memoriesDir}. Run 'haive init' first.`);
13696
13748
  process.exitCode = 1;
13697
13749
  return;
@@ -13763,14 +13815,14 @@ function registerResolveProject(program2) {
13763
13815
  }
13764
13816
 
13765
13817
  // src/commands/runtime-journal.ts
13766
- import { existsSync as existsSync69 } from "fs";
13818
+ import { existsSync as existsSync70 } from "fs";
13767
13819
  import path48 from "path";
13768
13820
  import "commander";
13769
13821
  import {
13770
13822
  appendRuntimeJournalEntry as appendRuntimeJournalEntry3,
13771
- findProjectRoot as findProjectRoot48,
13823
+ findProjectRoot as findProjectRoot49,
13772
13824
  readRuntimeJournalTail as readRuntimeJournalTail2,
13773
- resolveHaivePaths as resolveHaivePaths44
13825
+ resolveHaivePaths as resolveHaivePaths45
13774
13826
  } from "@hiveai/core";
13775
13827
  function registerRuntime(program2) {
13776
13828
  const runtime = program2.command("runtime").description(
@@ -13779,7 +13831,7 @@ function registerRuntime(program2) {
13779
13831
  const journal = runtime.command("journal").description("Append or read the machine-local session journal (NDJSON)");
13780
13832
  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
13833
  const root = path48.resolve(opts.dir ?? process.cwd());
13782
- const paths = resolveHaivePaths44(findProjectRoot48(root));
13834
+ const paths = resolveHaivePaths45(findProjectRoot49(root));
13783
13835
  const raw = opts.kind ?? "note";
13784
13836
  const kind = ["note", "session_end", "mcp"].includes(raw) ? raw : "note";
13785
13837
  await appendRuntimeJournalEntry3(paths, { kind, message });
@@ -13787,9 +13839,9 @@ function registerRuntime(program2) {
13787
13839
  });
13788
13840
  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
13841
  const root = path48.resolve(opts.dir ?? process.cwd());
13790
- const paths = resolveHaivePaths44(findProjectRoot48(root));
13842
+ const paths = resolveHaivePaths45(findProjectRoot49(root));
13791
13843
  const limit = Math.min(500, Math.max(1, parseInt(opts.limit, 10) || 30));
13792
- if (!existsSync69(paths.haiveDir)) {
13844
+ if (!existsSync70(paths.haiveDir)) {
13793
13845
  ui.error("No .ai/ \u2014 run `haive init` first.");
13794
13846
  process.exitCode = 1;
13795
13847
  return;
@@ -13804,13 +13856,13 @@ function registerRuntime(program2) {
13804
13856
  }
13805
13857
 
13806
13858
  // src/commands/memory-timeline.ts
13807
- import { existsSync as existsSync70 } from "fs";
13859
+ import { existsSync as existsSync71 } from "fs";
13808
13860
  import path49 from "path";
13809
13861
  import "commander";
13810
13862
  import {
13811
13863
  collectTimelineEntries as collectTimelineEntries2,
13812
- findProjectRoot as findProjectRoot49,
13813
- resolveHaivePaths as resolveHaivePaths45
13864
+ findProjectRoot as findProjectRoot50,
13865
+ resolveHaivePaths as resolveHaivePaths46
13814
13866
  } from "@hiveai/core";
13815
13867
  function registerMemoryTimeline(memory2) {
13816
13868
  memory2.command("timeline").description(
@@ -13822,8 +13874,8 @@ function registerMemoryTimeline(memory2) {
13822
13874
  return;
13823
13875
  }
13824
13876
  const root = path49.resolve(opts.dir ?? process.cwd());
13825
- const paths = resolveHaivePaths45(findProjectRoot49(root));
13826
- if (!existsSync70(paths.memoriesDir)) {
13877
+ const paths = resolveHaivePaths46(findProjectRoot50(root));
13878
+ if (!existsSync71(paths.memoriesDir)) {
13827
13879
  ui.error("No memories \u2014 run `haive init`.");
13828
13880
  process.exitCode = 1;
13829
13881
  return;
@@ -13841,14 +13893,14 @@ function registerMemoryTimeline(memory2) {
13841
13893
  }
13842
13894
 
13843
13895
  // src/commands/memory-conflict-candidates.ts
13844
- import { existsSync as existsSync71 } from "fs";
13896
+ import { existsSync as existsSync73 } from "fs";
13845
13897
  import path50 from "path";
13846
13898
  import "commander";
13847
13899
  import {
13848
13900
  findLexicalConflictPairs as findLexicalConflictPairs2,
13849
13901
  findTopicStatusConflictPairs as findTopicStatusConflictPairs2,
13850
- findProjectRoot as findProjectRoot50,
13851
- resolveHaivePaths as resolveHaivePaths46
13902
+ findProjectRoot as findProjectRoot51,
13903
+ resolveHaivePaths as resolveHaivePaths47
13852
13904
  } from "@hiveai/core";
13853
13905
  function parseTypes(csv) {
13854
13906
  const allowed = ["decision", "architecture", "convention", "gotcha"];
@@ -13865,8 +13917,8 @@ function registerMemoryConflictCandidates(memory2) {
13865
13917
  "decision,architecture"
13866
13918
  ).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
13919
  const root = path50.resolve(opts.dir ?? process.cwd());
13868
- const paths = resolveHaivePaths46(findProjectRoot50(root));
13869
- if (!existsSync71(paths.memoriesDir)) {
13920
+ const paths = resolveHaivePaths47(findProjectRoot51(root));
13921
+ if (!existsSync73(paths.memoriesDir)) {
13870
13922
  ui.error("No memories \u2014 run `haive init`.");
13871
13923
  process.exitCode = 1;
13872
13924
  return;
@@ -13902,13 +13954,13 @@ function registerMemoryConflictCandidates(memory2) {
13902
13954
 
13903
13955
  // src/commands/enforce.ts
13904
13956
  import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
13905
- import { existsSync as existsSync73, statSync as statSync3 } from "fs";
13957
+ import { existsSync as existsSync74, statSync as statSync3 } from "fs";
13906
13958
  import { chmod as chmod2, mkdir as mkdir19, readFile as readFile23, readdir as readdir6, rm as rm3, writeFile as writeFile34 } from "fs/promises";
13907
13959
  import path51 from "path";
13908
13960
  import "commander";
13909
13961
  import {
13910
13962
  antiPatternGateParams as antiPatternGateParams2,
13911
- findProjectRoot as findProjectRoot51,
13963
+ findProjectRoot as findProjectRoot52,
13912
13964
  hasRecentBriefingMarker as hasRecentBriefingMarker2,
13913
13965
  isFreshIsoDate,
13914
13966
  loadConfig as loadConfig13,
@@ -13916,7 +13968,7 @@ import {
13916
13968
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
13917
13969
  readRecentBriefingMarker,
13918
13970
  resolveBriefingBudget as resolveBriefingBudget3,
13919
- resolveHaivePaths as resolveHaivePaths47,
13971
+ resolveHaivePaths as resolveHaivePaths48,
13920
13972
  saveConfig as saveConfig4,
13921
13973
  SESSION_RECAP_TTL_MS,
13922
13974
  verifyAnchor as verifyAnchor4,
@@ -13929,8 +13981,8 @@ function registerEnforce(program2) {
13929
13981
  "Agent-agnostic enforcement helpers: install policy gates, report status, and block unsafe workflows."
13930
13982
  );
13931
13983
  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);
13984
+ const root = findProjectRoot52(opts.dir);
13985
+ const paths = resolveHaivePaths48(root);
13934
13986
  await mkdir19(paths.haiveDir, { recursive: true });
13935
13987
  const current = await loadConfig13(paths);
13936
13988
  await saveConfig4(paths, {
@@ -13974,17 +14026,17 @@ function registerEnforce(program2) {
13974
14026
  if (report.should_block) process.exit(2);
13975
14027
  });
13976
14028
  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);
14029
+ const root = findProjectRoot52(opts.dir);
14030
+ const paths = resolveHaivePaths48(root);
13979
14031
  const cacheDir = path51.join(paths.haiveDir, ".cache");
13980
- if (existsSync73(cacheDir)) {
14032
+ if (existsSync74(cacheDir)) {
13981
14033
  if (opts.dryRun) ui.info(`would clean ${path51.relative(root, cacheDir)} (preserving .gitignore)`);
13982
14034
  else {
13983
14035
  const removed = await cleanupCacheDir(cacheDir);
13984
14036
  ui.success(`cleaned ${path51.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
13985
14037
  }
13986
14038
  }
13987
- if (existsSync73(paths.runtimeDir)) {
14039
+ if (existsSync74(paths.runtimeDir)) {
13988
14040
  if (opts.dryRun) ui.info(`would clean ${path51.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
13989
14041
  else {
13990
14042
  const removed = await cleanupRuntimeDir(paths.runtimeDir);
@@ -14008,8 +14060,8 @@ function registerEnforce(program2) {
14008
14060
  const payload = await readHookPayload();
14009
14061
  const root = resolveRoot(opts.dir, payload);
14010
14062
  if (!root) return;
14011
- const paths = resolveHaivePaths47(root);
14012
- if (!existsSync73(paths.haiveDir)) return;
14063
+ const paths = resolveHaivePaths48(root);
14064
+ if (!existsSync74(paths.haiveDir)) return;
14013
14065
  await mkdir19(paths.runtimeDir, { recursive: true });
14014
14066
  const sessionId = opts.sessionId ?? payload.session_id;
14015
14067
  const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this hAIve-initialized project.";
@@ -14071,8 +14123,8 @@ ${briefing.project_context.content.slice(0, 1800)}`);
14071
14123
  const payload = await readHookPayload();
14072
14124
  const root = resolveRoot(opts.dir, payload);
14073
14125
  if (!root) return;
14074
- const paths = resolveHaivePaths47(root);
14075
- if (!existsSync73(paths.haiveDir)) return;
14126
+ const paths = resolveHaivePaths48(root);
14127
+ if (!existsSync74(paths.haiveDir)) return;
14076
14128
  if (!isWriteLikeTool(payload)) return;
14077
14129
  const ok = await hasRecentBriefingMarker2(paths, payload.session_id);
14078
14130
  if (ok) {
@@ -14114,9 +14166,9 @@ ${briefing.project_context.content.slice(0, 1800)}`);
14114
14166
  });
14115
14167
  }
14116
14168
  async function buildFinishReport(dir) {
14117
- const root = findProjectRoot51(dir);
14118
- const paths = resolveHaivePaths47(root);
14119
- const initialized = existsSync73(paths.haiveDir);
14169
+ const root = findProjectRoot52(dir);
14170
+ const paths = resolveHaivePaths48(root);
14171
+ const initialized = existsSync74(paths.haiveDir);
14120
14172
  const config = initialized ? await loadConfig13(paths) : {};
14121
14173
  const mode = config.enforcement?.mode ?? "strict";
14122
14174
  const findings = [];
@@ -14310,9 +14362,9 @@ function finishReport(root, initialized, mode, findings, config) {
14310
14362
  });
14311
14363
  }
14312
14364
  async function runWithEnforcement(command, args, opts) {
14313
- const root = findProjectRoot51(opts.dir);
14314
- const paths = resolveHaivePaths47(root);
14315
- if (!existsSync73(paths.haiveDir)) {
14365
+ const root = findProjectRoot52(opts.dir);
14366
+ const paths = resolveHaivePaths48(root);
14367
+ if (!existsSync74(paths.haiveDir)) {
14316
14368
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
14317
14369
  process.exit(1);
14318
14370
  }
@@ -14405,9 +14457,9 @@ async function writeWrapperBriefing(paths, sessionId, task) {
14405
14457
  return file;
14406
14458
  }
14407
14459
  async function buildEnforcementReport(dir, stage, sessionId) {
14408
- const root = findProjectRoot51(dir);
14409
- const paths = resolveHaivePaths47(root);
14410
- const initialized = existsSync73(paths.haiveDir);
14460
+ const root = findProjectRoot52(dir);
14461
+ const paths = resolveHaivePaths48(root);
14462
+ const initialized = existsSync74(paths.haiveDir);
14411
14463
  const config = initialized ? await loadConfig13(paths) : {};
14412
14464
  if (initialized) await applyLightweightRepairs(root, paths);
14413
14465
  const mode = config.enforcement?.mode ?? "strict";
@@ -14438,7 +14490,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
14438
14490
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
14439
14491
  });
14440
14492
  }
14441
- findings.push(...await inspectIntegrationVersions(root, "0.11.0"));
14493
+ findings.push(...await inspectIntegrationVersions(root, "0.12.1"));
14442
14494
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
14443
14495
  const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
14444
14496
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
@@ -14508,7 +14560,7 @@ function withCategories(report) {
14508
14560
  };
14509
14561
  }
14510
14562
  async function hasRecentSessionRecap(paths) {
14511
- if (!existsSync73(paths.memoriesDir)) return false;
14563
+ if (!existsSync74(paths.memoriesDir)) return false;
14512
14564
  const all = await loadMemoriesFromDir37(paths.memoriesDir);
14513
14565
  return all.some(({ memory: memory2 }) => {
14514
14566
  const fm = memory2.frontmatter;
@@ -14517,7 +14569,7 @@ async function hasRecentSessionRecap(paths) {
14517
14569
  });
14518
14570
  }
14519
14571
  async function verifyMemoryPolicy(paths, config) {
14520
- if (!existsSync73(paths.memoriesDir)) return [];
14572
+ if (!existsSync74(paths.memoriesDir)) return [];
14521
14573
  const all = await loadMemoriesFromDir37(paths.memoriesDir);
14522
14574
  const findings = [];
14523
14575
  const staleImportant = [];
@@ -14555,7 +14607,7 @@ async function verifyMemoryPolicy(paths, config) {
14555
14607
  return findings;
14556
14608
  }
14557
14609
  async function verifyDecisionCoverage(paths, stage, sessionId) {
14558
- if (!existsSync73(paths.memoriesDir)) return [];
14610
+ if (!existsSync74(paths.memoriesDir)) return [];
14559
14611
  const changedFiles = await getChangedFiles(paths.root, stage);
14560
14612
  if (changedFiles.length === 0) {
14561
14613
  return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
@@ -14667,7 +14719,7 @@ async function cleanupRuntimeDir(runtimeDir) {
14667
14719
  removed++;
14668
14720
  }
14669
14721
  await writeFile34(path51.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
14670
- if (!existsSync73(path51.join(runtimeDir, "README.md"))) {
14722
+ if (!existsSync74(path51.join(runtimeDir, "README.md"))) {
14671
14723
  await writeFile34(
14672
14724
  path51.join(runtimeDir, "README.md"),
14673
14725
  "# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. hAIve cleanup preserves briefing markers so enforcement state remains valid.\n",
@@ -14710,7 +14762,7 @@ async function inspectIntegrationVersions(root, expectedVersion) {
14710
14762
  const findings = [];
14711
14763
  for (const rel of files) {
14712
14764
  const file = path51.join(root, rel);
14713
- if (!existsSync73(file)) continue;
14765
+ if (!existsSync74(file)) continue;
14714
14766
  const text = await readFile23(file, "utf8").catch(() => "");
14715
14767
  for (const bin of extractAbsoluteHaiveBins2(text)) {
14716
14768
  const version = versionForBinary2(bin);
@@ -14820,7 +14872,7 @@ async function resolveCiDiffRange(root) {
14820
14872
  }
14821
14873
  async function resolveGithubEventRange(root) {
14822
14874
  const eventPath = process.env.GITHUB_EVENT_PATH;
14823
- if (!eventPath || !existsSync73(eventPath)) return null;
14875
+ if (!eventPath || !existsSync74(eventPath)) return null;
14824
14876
  try {
14825
14877
  const event = JSON.parse(await readFile23(eventPath, "utf8"));
14826
14878
  const prBase = cleanGitSha(event.pull_request?.base?.sha);
@@ -15001,7 +15053,7 @@ function buildScore(findings, threshold = 80) {
15001
15053
  }
15002
15054
  async function installGitEnforcement(root) {
15003
15055
  const hooksDir = path51.join(root, ".git", "hooks");
15004
- if (!existsSync73(path51.join(root, ".git"))) {
15056
+ if (!existsSync74(path51.join(root, ".git"))) {
15005
15057
  ui.warn("No .git directory found; git enforcement hooks skipped.");
15006
15058
  return;
15007
15059
  }
@@ -15024,7 +15076,7 @@ haive enforce check --stage pre-push --dir . || exit $?
15024
15076
  ];
15025
15077
  for (const hook of hooks) {
15026
15078
  const file = path51.join(hooksDir, hook.name);
15027
- if (existsSync73(file)) {
15079
+ if (existsSync74(file)) {
15028
15080
  const current = await readFile23(file, "utf8").catch(() => "");
15029
15081
  if (current.includes(ENFORCE_HOOK_MARKER)) {
15030
15082
  await writeFile34(file, hook.body, "utf8");
@@ -15043,7 +15095,7 @@ ${hook.body}`, "utf8");
15043
15095
  async function installCiEnforcement(root) {
15044
15096
  const workflowPath = path51.join(root, ".github", "workflows", "haive-enforcement.yml");
15045
15097
  await mkdir19(path51.dirname(workflowPath), { recursive: true });
15046
- if (existsSync73(workflowPath)) {
15098
+ if (existsSync74(workflowPath)) {
15047
15099
  ui.info("GitHub Actions enforcement workflow already exists \u2014 skipped");
15048
15100
  return;
15049
15101
  }
@@ -15134,7 +15186,7 @@ async function readHookPayload() {
15134
15186
  }
15135
15187
  function resolveRoot(dir, payload) {
15136
15188
  try {
15137
- return findProjectRoot51(dir ?? payload.cwd);
15189
+ return findProjectRoot52(dir ?? payload.cwd);
15138
15190
  } catch {
15139
15191
  return null;
15140
15192
  }
@@ -15175,7 +15227,7 @@ function normalizeToolPath(file, root) {
15175
15227
  return path51.relative(root, normalized).replace(/\\/g, "/");
15176
15228
  }
15177
15229
  async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
15178
- if (!existsSync73(paths.memoriesDir)) return [];
15230
+ if (!existsSync74(paths.memoriesDir)) return [];
15179
15231
  const marker = await readRecentBriefingMarker(paths, sessionId);
15180
15232
  const consulted = new Set(marker?.memory_ids ?? []);
15181
15233
  const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention", "attempt"]);
@@ -15249,16 +15301,16 @@ function registerRun(program2) {
15249
15301
 
15250
15302
  // src/commands/sensors.ts
15251
15303
  import { execFile as execFile2 } from "child_process";
15252
- import { existsSync as existsSync74 } from "fs";
15304
+ import { existsSync as existsSync75 } from "fs";
15253
15305
  import { chmod as chmod3, mkdir as mkdir20, readFile as readFile24, writeFile as writeFile35 } from "fs/promises";
15254
15306
  import path53 from "path";
15255
15307
  import { promisify as promisify2 } from "util";
15256
15308
  import "commander";
15257
15309
  import {
15258
- findProjectRoot as findProjectRoot52,
15310
+ findProjectRoot as findProjectRoot53,
15259
15311
  isRetiredMemory as isRetiredMemory3,
15260
15312
  loadMemoriesFromDir as loadMemoriesFromDir38,
15261
- resolveHaivePaths as resolveHaivePaths48,
15313
+ resolveHaivePaths as resolveHaivePaths49,
15262
15314
  runSensors as runSensors2,
15263
15315
  sensorTargetsFromDiff as sensorTargetsFromDiff2,
15264
15316
  serializeMemory as serializeMemory26
@@ -15267,8 +15319,8 @@ var exec2 = promisify2(execFile2);
15267
15319
  function registerSensors(program2) {
15268
15320
  const sensors = program2.command("sensors").description("Operate executable sensors derived from hAIve memories");
15269
15321
  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);
15322
+ const root = findProjectRoot53(opts.dir);
15323
+ const paths = resolveHaivePaths49(root);
15272
15324
  const rows = await sensorRows(paths);
15273
15325
  if (opts.json) {
15274
15326
  console.log(JSON.stringify(rows, null, 2));
@@ -15288,8 +15340,8 @@ function registerSensors(program2) {
15288
15340
  }
15289
15341
  });
15290
15342
  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);
15343
+ const root = findProjectRoot53(opts.dir);
15344
+ const paths = resolveHaivePaths49(root);
15293
15345
  const memories = await runnableSensorMemories(paths);
15294
15346
  const diff = opts.diffFile ? await readFile24(path53.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
15295
15347
  const targets = sensorTargetsFromDiff2(diff);
@@ -15330,9 +15382,9 @@ function registerSensors(program2) {
15330
15382
  process.exitCode = 1;
15331
15383
  return;
15332
15384
  }
15333
- const root = findProjectRoot52(opts.dir);
15334
- const paths = resolveHaivePaths48(root);
15335
- const loaded = existsSync74(paths.memoriesDir) ? await loadMemoriesFromDir38(paths.memoriesDir) : [];
15385
+ const root = findProjectRoot53(opts.dir);
15386
+ const paths = resolveHaivePaths49(root);
15387
+ const loaded = existsSync75(paths.memoriesDir) ? await loadMemoriesFromDir38(paths.memoriesDir) : [];
15336
15388
  const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
15337
15389
  if (!found) {
15338
15390
  ui.error(`No memory found with id ${id}`);
@@ -15364,8 +15416,8 @@ function registerSensors(program2) {
15364
15416
  process.exitCode = 1;
15365
15417
  return;
15366
15418
  }
15367
- const root = findProjectRoot52(opts.dir);
15368
- const paths = resolveHaivePaths48(root);
15419
+ const root = findProjectRoot53(opts.dir);
15420
+ const paths = resolveHaivePaths49(root);
15369
15421
  const rows = await sensorRows(paths);
15370
15422
  const outDir = path53.resolve(root, opts.outDir ?? ".ai/generated");
15371
15423
  await mkdir20(outDir, { recursive: true });
@@ -15394,7 +15446,7 @@ async function sensorRows(paths) {
15394
15446
  });
15395
15447
  }
15396
15448
  async function runnableSensorMemories(paths, regexOnly = true) {
15397
- if (!existsSync74(paths.memoriesDir)) return [];
15449
+ if (!existsSync75(paths.memoriesDir)) return [];
15398
15450
  const loaded = await loadMemoriesFromDir38(paths.memoriesDir);
15399
15451
  return loaded.map(({ memory: memory2 }) => memory2).filter((memory2) => {
15400
15452
  const sensor = memory2.frontmatter.sensor;
@@ -15436,8 +15488,8 @@ function shellQuote(value) {
15436
15488
  }
15437
15489
 
15438
15490
  // 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");
15491
+ var program = new Command56();
15492
+ program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.12.1").option("--advanced", "show maintenance and experimental commands in help");
15441
15493
  registerInit(program);
15442
15494
  registerWelcome(program);
15443
15495
  registerResolveProject(program);
@@ -15462,6 +15514,7 @@ registerMemoryPromote(memory);
15462
15514
  registerMemoryVerify(memory);
15463
15515
  registerMemoryStats(memory);
15464
15516
  registerMemoryImpact(memory);
15517
+ registerMemoryFeedback(memory);
15465
15518
  registerMemoryReject(memory);
15466
15519
  registerMemoryAutoPromote(memory);
15467
15520
  registerMemoryForFiles(memory);