@hiveai/cli 0.9.31 → 0.10.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
@@ -249,11 +249,13 @@ import "commander";
249
249
  import {
250
250
  findProjectRoot,
251
251
  getUsage,
252
+ retirementSignal,
252
253
  loadCodeMap,
253
254
  loadMemoriesFromDir,
254
255
  loadUsageIndex,
255
256
  resolveHaivePaths,
256
- serializeMemory
257
+ serializeMemory,
258
+ specificityScore
257
259
  } from "@hiveai/core";
258
260
  async function lintMemoriesAsync(root, options = {}) {
259
261
  const paths = resolveHaivePaths(root);
@@ -271,6 +273,16 @@ async function lintMemoriesAsync(root, options = {}) {
271
273
  if (fm.type === "session_recap") continue;
272
274
  const body = memory2.body.trim();
273
275
  const naked = body.replace(/^#.*$/gm, "").replace(/```[\s\S]*?```/g, "").trim();
276
+ const retired = retirementSignal(fm, memory2.body);
277
+ if (retired.retired && fm.status !== "deprecated" && fm.status !== "rejected" && fm.status !== "stale") {
278
+ out.push({
279
+ file: filePath,
280
+ id: fm.id,
281
+ severity: "warn",
282
+ code: "RETIRED_ACTIVE_MEMORY",
283
+ message: `Record is still active but lifecycle metadata says it should be retired (${retired.reason}). Mark it deprecated or remove the fixed/superseded marker if it still applies.`
284
+ });
285
+ }
274
286
  if (naked.length < 40 && fm.status !== "rejected") {
275
287
  out.push({
276
288
  file: filePath,
@@ -289,6 +301,15 @@ async function lintMemoriesAsync(root, options = {}) {
289
301
  message: "Record does not contain obvious action/rationale words. Add the concrete rule, why it exists, and what to do instead."
290
302
  });
291
303
  }
304
+ if (["decision", "gotcha", "convention", "architecture"].includes(fm.type) && fm.status !== "rejected" && naked.length >= 40 && specificityScore(naked) < 0.2) {
305
+ out.push({
306
+ file: filePath,
307
+ id: fm.id,
308
+ severity: "info",
309
+ code: "LOW_VALUE_GUESSABLE",
310
+ message: "Reads like generic best practice a capable model already follows. hAIve's value is UNGUESSABLE team knowledge \u2014 add the concrete, arbitrary specifics (exact names, values, formats, magic numbers) or consider removing it to keep briefings high-signal."
311
+ });
312
+ }
292
313
  const suggestedAnchors = suggestAnchors(root, { filePath, memory: memory2 }, codeMap, trackedFiles);
293
314
  if (ANCHOR_TYPES.has(fm.type) && fm.anchor.paths.length === 0 && fm.status === "validated") {
294
315
  out.push({
@@ -935,12 +956,10 @@ function registerBriefing(program2) {
935
956
  if (existsSync3(paths.projectContext) && !stopped()) {
936
957
  const ctx = await readFile2(paths.projectContext, "utf8");
937
958
  const isTemplate = ctx.includes("TODO \u2014 high-level overview") || ctx.includes("Generated by `haive init`");
938
- if (isTemplate) {
939
- ui.warn(
940
- "project-context.md still contains the default template \u2014 get_briefing will return little value."
941
- );
959
+ const bootstrapUnfilled = /Auto-generated by `haive init/i.test(ctx) && (ctx.match(/TODO —/g)?.length ?? 0) >= 2;
960
+ if (isTemplate || bootstrapUnfilled) {
942
961
  ui.warn(
943
- "Fix: in your AI client, invoke the MCP prompt bootstrap_project to auto-fill it from your codebase."
962
+ "project-context.md is still auto-generated/unfilled \u2014 skipping it (low value). Fill it in, or invoke the bootstrap_project MCP prompt for real context."
944
963
  );
945
964
  out("");
946
965
  } else {
@@ -3701,6 +3720,7 @@ import { existsSync as existsSync15 } from "fs";
3701
3720
  import path62 from "path";
3702
3721
  import {
3703
3722
  buildFrontmatter as buildFrontmatter3,
3723
+ isLikelyGuessable,
3704
3724
  memoryFilePath as memoryFilePath3,
3705
3725
  serializeMemory as serializeMemory7
3706
3726
  } from "@hiveai/core";
@@ -3736,6 +3756,7 @@ import {
3736
3756
  getUsage as getUsage5,
3737
3757
  inferModulesFromPaths as inferModulesFromPaths2,
3738
3758
  isGlobPath,
3759
+ isRetiredMemory,
3739
3760
  isAutoPromoteEligible,
3740
3761
  isDecaying,
3741
3762
  isStackPackSeed as isStackPackSeed2,
@@ -3750,6 +3771,8 @@ import {
3750
3771
  queryCodeMap as queryCodeMap2,
3751
3772
  resolveBriefingBudget as resolveBriefingBudget2,
3752
3773
  serializeMemory as serializeMemory9,
3774
+ specificityScore as specificityScore2,
3775
+ GUESSABLE_THRESHOLD,
3753
3776
  tokenizeQuery as tokenizeQuery22,
3754
3777
  trackReads as trackReads3,
3755
3778
  truncateToTokens,
@@ -3782,6 +3805,7 @@ import { existsSync as existsSync222 } from "fs";
3782
3805
  import {
3783
3806
  deriveConfidence as deriveConfidence6,
3784
3807
  getUsage as getUsage7,
3808
+ isRetiredMemory as isRetiredMemory2,
3785
3809
  loadMemoriesFromDir as loadMemoriesFromDir17,
3786
3810
  loadUsageIndex as loadUsageIndex9,
3787
3811
  literalMatchesAnyToken as literalMatchesAnyToken3,
@@ -4780,12 +4804,25 @@ var MemObserveInputSchema = {
4780
4804
  scope: z15.enum(["personal", "team", "module"]).default("team").describe("Visibility scope \u2014 defaults to team since discoveries benefit everyone"),
4781
4805
  module: z15.string().optional().describe("Module name (required when scope=module)"),
4782
4806
  tags: z15.array(z15.string()).default([]).describe("Tags for filtering"),
4783
- author: z15.string().optional().describe("Author handle or email")
4807
+ author: z15.string().optional().describe("Author handle or email"),
4808
+ force: z15.boolean().default(false).describe(
4809
+ "Save even if the observation looks like generic, guessable knowledge. By default, low-specificity observations (things a capable model already knows) are SKIPPED to keep the corpus high-signal \u2014 only unguessable, team-specific discoveries are worth storing."
4810
+ )
4784
4811
  };
4785
4812
  async function memObserve(input, ctx) {
4786
4813
  if (!existsSync15(ctx.paths.haiveDir)) {
4787
4814
  throw new Error(`No .ai/ directory at ${ctx.paths.root}. Run 'haive init' first.`);
4788
4815
  }
4816
+ const signalText = [input.what, input.impact, input.fix ?? ""].join(" ");
4817
+ if (!input.force && isLikelyGuessable(signalText)) {
4818
+ return {
4819
+ id: "",
4820
+ scope: input.scope,
4821
+ file_path: "",
4822
+ skipped: true,
4823
+ reason: "Observation looks like generic, guessable knowledge (low specificity) \u2014 not saved. Capture only arbitrary, team-specific facts (exact names, values, formats). Pass force=true to override."
4824
+ };
4825
+ }
4789
4826
  const slug = input.what.toLowerCase().replace(/[^a-z0-9\s]/g, "").trim().split(/\s+/).slice(0, 6).join("-");
4790
4827
  const anchorPaths = input.where.split(/[,\n]/).map((s) => s.trim()).filter(Boolean);
4791
4828
  const baseFm = buildFrontmatter3({
@@ -5117,6 +5154,7 @@ async function getBriefing(input, ctx) {
5117
5154
  const s = memory2.frontmatter.status;
5118
5155
  if (s === "rejected" || s === "deprecated") return false;
5119
5156
  if (!input.include_stale && s === "stale") return false;
5157
+ if (!input.include_stale && isRetiredMemory(memory2.frontmatter, memory2.body)) return false;
5120
5158
  if (memory2.frontmatter.type === "session_recap") return false;
5121
5159
  return true;
5122
5160
  });
@@ -5472,6 +5510,14 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
5472
5510
  const memoriesEmpty = outputMemories.length === 0;
5473
5511
  const hasMemoriesDir = existsSync18(ctx.paths.memoriesDir);
5474
5512
  const isColdStart = isTemplateContext && memoriesEmpty && !lastSession && !autoContextGenerated;
5513
+ const hasUnguessableSignal = outputMemories.some(
5514
+ (m) => (m.priority === "must_read" || m.priority === "useful") && specificityScore2(m.body) >= GUESSABLE_THRESHOLD
5515
+ );
5516
+ const briefingValueLow = !hasUnguessableSignal;
5517
+ const adaptiveConfig = await loadConfig3(ctx.paths);
5518
+ const bootstrapUnfilled = /Auto-generated by `haive init/i.test(projectContextRaw) && (projectContextRaw.match(/TODO —/g)?.length ?? 0) >= 2;
5519
+ const contextIsInferable = isTemplateContext || autoContextGenerated || bootstrapUnfilled;
5520
+ const adaptiveTrim = adaptiveConfig.adaptiveBriefing !== false && briefingValueLow && contextIsInferable;
5475
5521
  const hints = [];
5476
5522
  if (isColdStart) {
5477
5523
  hints.push(
@@ -5504,6 +5550,11 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
5504
5550
  );
5505
5551
  }
5506
5552
  }
5553
+ if (adaptiveTrim) {
5554
+ hints.push(
5555
+ "No team-specific policy matched these files/task \u2014 nothing here a capable model can't infer. The auto-generated project context was trimmed to keep this briefing near-zero-cost; proceed with normal Read/Grep."
5556
+ );
5557
+ }
5507
5558
  if (existsSync18(ctx.paths.haiveDir)) {
5508
5559
  await writeBriefingMarker2(ctx.paths, {
5509
5560
  sessionId: process.env.HAIVE_SESSION_ID,
@@ -5519,7 +5570,12 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
5519
5570
  search_mode: searchMode,
5520
5571
  inferred_modules: inferred,
5521
5572
  ...lastSession ? { last_session: lastSession } : {},
5522
- project_context: projectContextRaw || autoContextGenerated ? {
5573
+ project_context: adaptiveTrim ? {
5574
+ content: "(adaptive briefing: auto-generated context omitted \u2014 no team-specific policy matched, so a capable model needs nothing extra here)",
5575
+ truncated: false,
5576
+ ...isTemplateContext && !autoContextGenerated ? { is_template: true } : {},
5577
+ ...autoContextGenerated ? { auto_generated: true } : {}
5578
+ } : projectContextRaw || autoContextGenerated ? {
5523
5579
  content: projectSlice.text,
5524
5580
  truncated: projectSlice.truncated,
5525
5581
  ...isTemplateContext && !autoContextGenerated ? { is_template: true } : {},
@@ -5533,6 +5589,7 @@ When done, call \`mem_session_end\` to acknowledge \u2014 this clears the pendin
5533
5589
  decay_warnings: decayWarnings,
5534
5590
  setup_warnings: setupWarnings,
5535
5591
  ...isColdStart ? { low_value: true } : {},
5592
+ briefing_value: briefingValueLow ? "low" : "high",
5536
5593
  ...hints.length > 0 ? { hints } : {},
5537
5594
  estimated_tokens: totalTokens,
5538
5595
  budget: {
@@ -6011,6 +6068,9 @@ var AntiPatternsCheckInputSchema = {
6011
6068
  limit: z24.number().int().positive().max(20).default(8).describe("Cap on returned warnings."),
6012
6069
  semantic: z24.boolean().default(true).describe(
6013
6070
  "When true, also use semantic search (requires @hiveai/embeddings + memory index) to find related anti-patterns."
6071
+ ),
6072
+ min_semantic_score: z24.number().min(0).max(1).default(0.45).describe(
6073
+ "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."
6014
6074
  )
6015
6075
  };
6016
6076
  var CODE_STOPWORDS = /* @__PURE__ */ new Set([
@@ -6061,8 +6121,12 @@ var CODE_STOPWORDS = /* @__PURE__ */ new Set([
6061
6121
  "console"
6062
6122
  ]);
6063
6123
  function tokenizeDiffForLiteral(diff) {
6064
- const wsTokens = tokenizeQuery3(diff);
6065
- const wordTokens = diff.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 4 && !CODE_STOPWORDS.has(t));
6124
+ const lines = diff.split("\n");
6125
+ const looksLikeDiff = lines.some((l) => /^[+-]/.test(l));
6126
+ const addedOnly = looksLikeDiff ? lines.filter((l) => l.startsWith("+") && !l.startsWith("+++")).join("\n") : diff;
6127
+ const source = addedOnly.trim().length > 0 ? addedOnly : diff;
6128
+ const wsTokens = tokenizeQuery3(source);
6129
+ const wordTokens = source.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length >= 4 && !CODE_STOPWORDS.has(t));
6066
6130
  return [.../* @__PURE__ */ new Set([...wsTokens, ...wordTokens])];
6067
6131
  }
6068
6132
  async function antiPatternsCheck(input, ctx) {
@@ -6077,11 +6141,12 @@ async function antiPatternsCheck(input, ctx) {
6077
6141
  return { scanned: 0, warnings: [], notice: "No .ai/memories directory \u2014 nothing to check against." };
6078
6142
  }
6079
6143
  const all = await loadMemoriesFromDir17(ctx.paths.memoriesDir);
6144
+ const minSemanticScore = input.min_semantic_score ?? 0.45;
6080
6145
  const negative = all.filter(({ memory: memory2 }) => {
6081
6146
  const t = memory2.frontmatter.type;
6082
6147
  if (t !== "attempt" && t !== "gotcha") return false;
6083
6148
  const s = memory2.frontmatter.status;
6084
- return s !== "rejected" && s !== "deprecated" && s !== "stale";
6149
+ return s !== "rejected" && s !== "deprecated" && s !== "stale" && !isRetiredMemory2(memory2.frontmatter, memory2.body);
6085
6150
  });
6086
6151
  if (negative.length === 0) {
6087
6152
  return { scanned: 0, warnings: [], notice: "No attempt/gotcha memories found yet." };
@@ -6135,6 +6200,7 @@ async function antiPatternsCheck(input, ctx) {
6135
6200
  const negativeIds = new Set(negative.map(({ memory: memory2 }) => memory2.frontmatter.id));
6136
6201
  for (const hit of result.hits) {
6137
6202
  if (!negativeIds.has(hit.id)) continue;
6203
+ if (hit.score < minSemanticScore && !seen.has(hit.id)) continue;
6138
6204
  const found = negative.find(({ memory: memory2 }) => memory2.frontmatter.id === hit.id);
6139
6205
  if (found) upsert(found.memory.frontmatter, found.memory.body, "semantic", hit.score);
6140
6206
  }
@@ -7323,7 +7389,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
7323
7389
  };
7324
7390
  }
7325
7391
  var SERVER_NAME = "haive";
7326
- var SERVER_VERSION = "0.9.31";
7392
+ var SERVER_VERSION = "0.10.1";
7327
7393
  function jsonResult(data) {
7328
7394
  return {
7329
7395
  content: [
@@ -7362,7 +7428,6 @@ var MAINTENANCE_PROFILE_TOOLS = [
7362
7428
  "mem_delete",
7363
7429
  "mem_diff",
7364
7430
  "get_recap",
7365
- "code_search",
7366
7431
  "anti_patterns_check",
7367
7432
  "mem_distill",
7368
7433
  "mem_timeline",
@@ -12043,6 +12108,8 @@ import "commander";
12043
12108
  import {
12044
12109
  findProjectRoot as findProjectRoot41,
12045
12110
  getUsage as getUsage18,
12111
+ retirementSignal as retirementSignal2,
12112
+ loadConfig as loadConfig10,
12046
12113
  loadMemoriesFromDir as loadMemoriesFromDir31,
12047
12114
  loadUsageIndex as loadUsageIndex24,
12048
12115
  resolveHaivePaths as resolveHaivePaths37,
@@ -12052,7 +12119,7 @@ var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
12052
12119
  function registerMemoryArchive(memory2) {
12053
12120
  memory2.command("archive").description(
12054
12121
  "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."
12055
- ).option("--since <window>", "minimum age since last read (e.g. '180d', '6m')", "180d").option("--type <type>", "limit to a memory type (default 'attempt'). Pass 'all' to scan all types.", "attempt").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) => {
12122
+ ).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) => {
12056
12123
  const root = findProjectRoot41(opts.dir);
12057
12124
  const paths = resolveHaivePaths37(root);
12058
12125
  if (!existsSync60(paths.memoriesDir)) {
@@ -12060,7 +12127,9 @@ function registerMemoryArchive(memory2) {
12060
12127
  process.exitCode = 1;
12061
12128
  return;
12062
12129
  }
12063
- const minDays = parseDays(opts.since ?? "180d");
12130
+ const config = await loadConfig10(paths);
12131
+ const defaultWindow = config.enforcement?.decayAfterDays ? `${config.enforcement.decayAfterDays}d` : "180d";
12132
+ const minDays = parseDays(opts.since ?? defaultWindow);
12064
12133
  if (minDays === null) {
12065
12134
  ui.error(`Invalid --since value: ${opts.since}. Use formats like '180d', '6m', '1y'.`);
12066
12135
  process.exitCode = 1;
@@ -12074,20 +12143,23 @@ function registerMemoryArchive(memory2) {
12074
12143
  for (const { memory: mem, filePath } of all) {
12075
12144
  const fm = mem.frontmatter;
12076
12145
  if (typeFilter && fm.type !== typeFilter) continue;
12146
+ if (fm.type === "session_recap" || fm.requires_human_approval) continue;
12077
12147
  if (fm.status === "deprecated" || fm.status === "rejected") continue;
12148
+ const retired = retirementSignal2(fm, mem.body);
12078
12149
  const hasAnyAnchor = fm.anchor.paths.length + fm.anchor.symbols.length > 0;
12079
12150
  const allPathsGone = fm.anchor.paths.length > 0 && fm.anchor.paths.every((p) => !existsSync60(path44.join(paths.root, p)));
12080
12151
  const isAnchorless = !hasAnyAnchor;
12081
- if (!isAnchorless && !allPathsGone) continue;
12152
+ if (!retired.retired && !opts.unread && !isAnchorless && !allPathsGone) continue;
12082
12153
  const u = getUsage18(usage, fm.id);
12083
12154
  const lastSeen = u.last_read_at ?? fm.created_at;
12084
- if (Date.parse(lastSeen) >= cutoff) continue;
12155
+ if (!retired.retired && Date.parse(lastSeen) >= cutoff) continue;
12156
+ 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)`;
12085
12157
  candidates.push({
12086
12158
  id: fm.id,
12087
12159
  type: fm.type,
12088
12160
  status: fm.status,
12089
12161
  last_seen: lastSeen,
12090
- reason: isAnchorless ? `anchorless and not read since ${lastSeen.slice(0, 10)}` : `all ${fm.anchor.paths.length} anchored path(s) missing and not read since ${lastSeen.slice(0, 10)}`,
12162
+ reason,
12091
12163
  filePath
12092
12164
  });
12093
12165
  }
@@ -12160,7 +12232,7 @@ import {
12160
12232
  findProjectRoot as findProjectRoot42,
12161
12233
  getUsage as getUsage19,
12162
12234
  loadCodeMap as loadCodeMap7,
12163
- loadConfig as loadConfig10,
12235
+ loadConfig as loadConfig11,
12164
12236
  loadMemoriesFromDir as loadMemoriesFromDir33,
12165
12237
  loadUsageIndex as loadUsageIndex25,
12166
12238
  readUsageEvents as readUsageEvents4,
@@ -12175,7 +12247,7 @@ function registerDoctor(program2) {
12175
12247
  const paths = resolveHaivePaths38(root);
12176
12248
  const findings = [];
12177
12249
  const repairs = [];
12178
- const config = await loadConfig10(paths);
12250
+ const config = await loadConfig11(paths);
12179
12251
  if (!existsSync61(paths.haiveDir)) {
12180
12252
  findings.push({
12181
12253
  severity: "error",
@@ -12395,14 +12467,14 @@ function registerDoctor(program2) {
12395
12467
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
12396
12468
  });
12397
12469
  }
12398
- findings.push(...await collectInstallFindings(root, "0.9.31"));
12470
+ findings.push(...await collectInstallFindings(root, "0.10.1"));
12399
12471
  try {
12400
12472
  const legacyRaw = execSync3("haive-mcp --version", {
12401
12473
  encoding: "utf8",
12402
12474
  timeout: 3e3,
12403
12475
  stdio: ["ignore", "pipe", "ignore"]
12404
12476
  }).trim();
12405
- const cliVersion = "0.9.31";
12477
+ const cliVersion = "0.10.1";
12406
12478
  if (legacyRaw && legacyRaw !== cliVersion) {
12407
12479
  findings.push({
12408
12480
  severity: "warn",
@@ -12984,7 +13056,7 @@ import "commander";
12984
13056
  import {
12985
13057
  antiPatternGateParams,
12986
13058
  findProjectRoot as findProjectRoot44,
12987
- loadConfig as loadConfig11,
13059
+ loadConfig as loadConfig12,
12988
13060
  resolveHaivePaths as resolveHaivePaths40
12989
13061
  } from "@hiveai/core";
12990
13062
  function registerPrecommit(program2) {
@@ -13000,7 +13072,7 @@ function registerPrecommit(program2) {
13000
13072
  const root = findProjectRoot44(opts.dir);
13001
13073
  const paths = resolveHaivePaths40(root);
13002
13074
  const ctx = { paths };
13003
- const config = await loadConfig11(paths);
13075
+ const config = await loadConfig12(paths);
13004
13076
  const gate = config.enforcement?.antiPatternGate ?? "anchored";
13005
13077
  const gateParams = antiPatternGateParams(gate);
13006
13078
  const blockOn = opts.blockOn ?? gateParams.block_on;
@@ -13376,7 +13448,7 @@ import {
13376
13448
  findProjectRoot as findProjectRoot49,
13377
13449
  hasRecentBriefingMarker,
13378
13450
  isFreshIsoDate,
13379
- loadConfig as loadConfig12,
13451
+ loadConfig as loadConfig13,
13380
13452
  loadMemoriesFromDir as loadMemoriesFromDir36,
13381
13453
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
13382
13454
  readRecentBriefingMarker,
@@ -13397,7 +13469,7 @@ function registerEnforce(program2) {
13397
13469
  const root = findProjectRoot49(opts.dir);
13398
13470
  const paths = resolveHaivePaths45(root);
13399
13471
  await mkdir19(paths.haiveDir, { recursive: true });
13400
- const current = await loadConfig12(paths);
13472
+ const current = await loadConfig13(paths);
13401
13473
  await saveConfig4(paths, {
13402
13474
  ...current,
13403
13475
  enforcement: {
@@ -13670,7 +13742,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
13670
13742
  const root = findProjectRoot49(dir);
13671
13743
  const paths = resolveHaivePaths45(root);
13672
13744
  const initialized = existsSync68(paths.haiveDir);
13673
- const config = initialized ? await loadConfig12(paths) : {};
13745
+ const config = initialized ? await loadConfig13(paths) : {};
13674
13746
  if (initialized) await applyLightweightRepairs(root, paths);
13675
13747
  const mode = config.enforcement?.mode ?? "strict";
13676
13748
  const findings = [];
@@ -13700,7 +13772,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
13700
13772
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
13701
13773
  });
13702
13774
  }
13703
- findings.push(...await inspectIntegrationVersions(root, "0.9.31"));
13775
+ findings.push(...await inspectIntegrationVersions(root, "0.10.1"));
13704
13776
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
13705
13777
  const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
13706
13778
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
@@ -14312,7 +14384,7 @@ function registerRun(program2) {
14312
14384
 
14313
14385
  // src/index.ts
14314
14386
  var program = new Command52();
14315
- program.name("haive").description("hAIve \u2014 the memory and enforcement layer of your agent harness").version("0.9.31").option("--advanced", "show maintenance and experimental commands in help");
14387
+ program.name("haive").description("hAIve \u2014 the memory and enforcement layer of your agent harness").version("0.10.1").option("--advanced", "show maintenance and experimental commands in help");
14316
14388
  registerInit(program);
14317
14389
  registerWelcome(program);
14318
14390
  registerResolveProject(program);
@@ -14371,17 +14443,14 @@ registerPrecommit(program);
14371
14443
  var CORE_ROOT_COMMANDS = /* @__PURE__ */ new Set([
14372
14444
  "init",
14373
14445
  "doctor",
14374
- "tui",
14375
14446
  "agent",
14447
+ "briefing",
14376
14448
  "enforce",
14377
14449
  "run",
14378
- "briefing",
14379
14450
  "sync",
14380
14451
  "mcp",
14381
14452
  "memory",
14382
- "session",
14383
- "precommit",
14384
- "welcome"
14453
+ "session"
14385
14454
  ]);
14386
14455
  var CORE_MEMORY_COMMANDS = /* @__PURE__ */ new Set([
14387
14456
  "add",