@hiveai/cli 0.21.0 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -208,7 +208,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
208
208
  if (!f) continue;
209
209
  counts.set(f, (counts.get(f) ?? 0) + 1);
210
210
  }
211
- let entries = [...counts.entries()].map(([path60, changes]) => ({ path: path60, changes }));
211
+ let entries = [...counts.entries()].map(([path61, changes]) => ({ path: path61, changes }));
212
212
  const lowerPaths = filePaths.map((p) => p.toLowerCase());
213
213
  if (lowerPaths.length > 0) {
214
214
  entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
@@ -3386,7 +3386,7 @@ ${SEED_FOOTER(stack)}` });
3386
3386
 
3387
3387
  // src/commands/init.ts
3388
3388
  var execFileAsync = promisify2(execFile2);
3389
- var HAIVE_GITHUB_ACTION_REF = `v${"0.21.0"}`;
3389
+ var HAIVE_GITHUB_ACTION_REF = `v${"0.23.0"}`;
3390
3390
  var PROJECT_CONTEXT_TEMPLATE = `# Project context
3391
3391
 
3392
3392
  > Generated by \`haive init\`. Run \`haive init --bootstrap\` to auto-fill from your codebase,
@@ -4742,7 +4742,6 @@ import { z as z25 } from "zod";
4742
4742
  import { existsSync as existsSync25 } from "fs";
4743
4743
  import {
4744
4744
  addedLinesFromDiff,
4745
- appendPreventionEvent,
4746
4745
  BRIDGE_TARGET_PATH,
4747
4746
  buildDocFrequency,
4748
4747
  CODE_STOPWORDS,
@@ -4754,9 +4753,8 @@ import {
4754
4753
  loadUsageIndex as loadUsageIndex10,
4755
4754
  literalMatchesAnyToken as literalMatchesAnyToken3,
4756
4755
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths4,
4757
- recordPrevention,
4756
+ recordPreventionHits,
4758
4757
  runSensors,
4759
- saveUsageIndex as saveUsageIndex4,
4760
4758
  sensorTargetsFromDiff,
4761
4759
  tokenizeQuery as tokenizeQuery3
4762
4760
  } from "@hiveai/core";
@@ -4803,7 +4801,8 @@ import { existsSync as existsSync30 } from "fs";
4803
4801
  import {
4804
4802
  findLexicalConflictPairs,
4805
4803
  findTopicStatusConflictPairs,
4806
- loadMemoriesFromDir as loadMemoriesFromDir23
4804
+ loadMemoriesFromDir as loadMemoriesFromDir23,
4805
+ planConflictResolution
4807
4806
  } from "@hiveai/core";
4808
4807
  import { z as z32 } from "zod";
4809
4808
  import { resolveProjectInfo } from "@hiveai/core";
@@ -5807,7 +5806,15 @@ async function memTried(input, ctx) {
5807
5806
  throw new Error(`Memory already exists at ${file}`);
5808
5807
  }
5809
5808
  await writeFile82(file, serializeMemory7({ frontmatter, body }), "utf8");
5810
- return { id: frontmatter.id, scope: frontmatter.scope, file_path: file };
5809
+ const sensorGenerated = Boolean(sensor);
5810
+ const hint = sensorGenerated ? void 0 : input.paths.length === 0 ? "No sensor was generated (no `paths` given), so this lesson is feedforward-only \u2014 it will be briefed but the gate cannot block the repeat. Re-run with `paths` set to the file(s) where the mistake lives to close the loop." : "No sensor could be derived from the wording (no distinctive code token). The lesson is briefed but not enforced; add a concrete forbidden token/value, or attach a sensor manually, to make the gate block the repeat.";
5811
+ return {
5812
+ id: frontmatter.id,
5813
+ scope: frontmatter.scope,
5814
+ file_path: file,
5815
+ sensor_generated: sensorGenerated,
5816
+ ...hint ? { hint } : {}
5817
+ };
5811
5818
  }
5812
5819
  var IngestFindingsInputSchema = {
5813
5820
  format: z16.enum(["sarif", "sonar"]).describe("Report format: 'sarif' (ESLint/Semgrep/CodeQL) or 'sonar' (SonarQube issues JSON)"),
@@ -7358,19 +7365,7 @@ async function antiPatternsCheck(input, ctx) {
7358
7365
  const strongCatches = warnings.filter(
7359
7366
  (w) => w.reasons.includes("sensor") || w.reasons.includes("anchor") && w.reasons.includes("literal")
7360
7367
  );
7361
- if (strongCatches.length > 0) {
7362
- const recordedIds = [];
7363
- for (const w of strongCatches) if (recordPrevention(usage, w.id)) recordedIds.push(w.id);
7364
- if (recordedIds.length > 0) {
7365
- await saveUsageIndex4(ctx.paths, usage).catch(() => {
7366
- });
7367
- const at = (/* @__PURE__ */ new Date()).toISOString();
7368
- for (const id of recordedIds) {
7369
- await appendPreventionEvent(ctx.paths, { at, id, source: "anti-pattern" }).catch(() => {
7370
- });
7371
- }
7372
- }
7373
- }
7368
+ await recordPreventionHits(ctx.paths, strongCatches.map((w) => w.id), "anti-pattern");
7374
7369
  return {
7375
7370
  scanned: negative.length,
7376
7371
  warnings
@@ -8288,6 +8283,18 @@ function gitFileDiff(root, file, sinceDays) {
8288
8283
  return null;
8289
8284
  }
8290
8285
  }
8286
+ function suggestResolution(byId, idA, idB) {
8287
+ const a = byId.get(idA);
8288
+ const b = byId.get(idB);
8289
+ if (!a || !b) return null;
8290
+ const plan = planConflictResolution(a, b);
8291
+ return {
8292
+ keep_id: plan.keep_id,
8293
+ supersede_id: plan.supersede_id,
8294
+ reason: plan.reason,
8295
+ command: `haive memory resolve-conflict ${plan.keep_id} ${plan.supersede_id} --yes`
8296
+ };
8297
+ }
8291
8298
  var MemConflictCandidatesInputSchema = {
8292
8299
  since_days: z32.number().int().positive().max(3650).default(365).describe("Only memories created since N days ago"),
8293
8300
  types: z32.array(z32.enum(["decision", "architecture", "convention", "gotcha"])).default(["decision", "architecture"]).describe("Memory types scanned for pairwise lexical overlap"),
@@ -8309,6 +8316,7 @@ async function memConflictCandidates(input, ctx) {
8309
8316
  };
8310
8317
  }
8311
8318
  const all = await loadMemoriesFromDir23(ctx.paths.memoriesDir);
8319
+ const byId = new Map(all.map((m) => [m.memory.frontmatter.id, m]));
8312
8320
  const { pairs, scanned, truncated } = findLexicalConflictPairs(all, {
8313
8321
  sinceDays: input.since_days,
8314
8322
  types: input.types,
@@ -8317,8 +8325,22 @@ async function memConflictCandidates(input, ctx) {
8317
8325
  maxScan: input.max_scan
8318
8326
  });
8319
8327
  const topicStatusPairs = findTopicStatusConflictPairs(all, input.max_topic_pairs);
8328
+ const enrichedPairs = pairs.map((p) => ({
8329
+ ...p,
8330
+ suggested_resolution: suggestResolution(byId, p.id_a, p.id_b)
8331
+ }));
8332
+ const enrichedTopicStatusPairs = topicStatusPairs.map((p) => ({
8333
+ ...p,
8334
+ suggested_resolution: suggestResolution(byId, p.id_a, p.id_b)
8335
+ }));
8320
8336
  const notice = pairs.length === 0 && topicStatusPairs.length === 0 ? "No lexical or topic-status candidates \u2014 widen since_days/types or lower min_jaccard." : void 0;
8321
- return { pairs, topic_status_pairs: topicStatusPairs, scanned, truncated, notice };
8337
+ return {
8338
+ pairs: enrichedPairs,
8339
+ topic_status_pairs: enrichedTopicStatusPairs,
8340
+ scanned,
8341
+ truncated,
8342
+ notice
8343
+ };
8322
8344
  }
8323
8345
  var MemResolveProjectInputSchema = {
8324
8346
  cwd: z33.string().optional().describe("Directory used for root discovery when HAIVE_PROJECT_ROOT is unset.")
@@ -8632,7 +8654,7 @@ When done, respond with: "Imported N memories: [list of IDs]" or "Nothing action
8632
8654
  };
8633
8655
  }
8634
8656
  var SERVER_NAME = "haive";
8635
- var SERVER_VERSION = "0.21.0";
8657
+ var SERVER_VERSION = "0.23.0";
8636
8658
  function jsonResult(data) {
8637
8659
  return {
8638
8660
  content: [
@@ -11166,7 +11188,13 @@ function registerMemoryTried(memory2) {
11166
11188
  await writeFile21(file, serializeMemory19({ frontmatter, body }), "utf8");
11167
11189
  ui.success(`Recorded: ${path27.relative(root, file)}`);
11168
11190
  ui.info(`id=${frontmatter.id} type=attempt status=validated (auto-approved)`);
11169
- if (sensor) ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(sensor.pattern)}`);
11191
+ if (sensor) {
11192
+ ui.info(`sensor=regex warn autogen pattern=${JSON.stringify(sensor.pattern)}`);
11193
+ } else {
11194
+ ui.warn(
11195
+ frontmatter.anchor.paths.length === 0 ? "No sensor generated (no --paths) \u2014 lesson is briefed but NOT enforced. Add --paths to close the loop." : "No sensor could be derived from the wording \u2014 lesson is briefed but NOT enforced. Use a concrete forbidden token to enable a gate block."
11196
+ );
11197
+ }
11170
11198
  });
11171
11199
  }
11172
11200
  function parseCsv4(value) {
@@ -11426,7 +11454,7 @@ import {
11426
11454
  loadUsageIndex as loadUsageIndex18,
11427
11455
  recordRejection as recordRejection3,
11428
11456
  resolveHaivePaths as resolveHaivePaths23,
11429
- saveUsageIndex as saveUsageIndex5,
11457
+ saveUsageIndex as saveUsageIndex4,
11430
11458
  serializeMemory as serializeMemory20
11431
11459
  } from "@hiveai/core";
11432
11460
  function registerMemoryReject(memory2) {
@@ -11459,7 +11487,7 @@ function registerMemoryReject(memory2) {
11459
11487
  );
11460
11488
  const idx = await loadUsageIndex18(paths);
11461
11489
  recordRejection3(idx, id, opts.reason ?? null);
11462
- await saveUsageIndex5(paths, idx);
11490
+ await saveUsageIndex4(paths, idx);
11463
11491
  const u = idx.by_id[id];
11464
11492
  ui.success(
11465
11493
  `Rejected ${id} (status=rejected, ${u.rejected_count} rejection${u.rejected_count === 1 ? "" : "s"})`
@@ -11478,7 +11506,7 @@ import {
11478
11506
  findProjectRoot as findProjectRoot27,
11479
11507
  loadUsageIndex as loadUsageIndex19,
11480
11508
  resolveHaivePaths as resolveHaivePaths24,
11481
- saveUsageIndex as saveUsageIndex6
11509
+ saveUsageIndex as saveUsageIndex5
11482
11510
  } from "@hiveai/core";
11483
11511
  function registerMemoryRm(memory2) {
11484
11512
  memory2.command("delete <id>").alias("rm").description("Delete a memory file (and its usage entry by default). Mirrors MCP mem_delete. Alias: rm").option("-y, --yes", "skip the confirmation prompt").option("--keep-usage", "do not remove the usage.json entry").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
@@ -11512,7 +11540,7 @@ function registerMemoryRm(memory2) {
11512
11540
  const idx = await loadUsageIndex19(paths);
11513
11541
  if (idx.by_id[id]) {
11514
11542
  delete idx.by_id[id];
11515
- await saveUsageIndex6(paths, idx);
11543
+ await saveUsageIndex5(paths, idx);
11516
11544
  ui.info("Removed usage entry");
11517
11545
  }
11518
11546
  }
@@ -11725,7 +11753,7 @@ import {
11725
11753
  recordRejection as recordRejection4,
11726
11754
  recommendFeedbackAdjustment as recommendFeedbackAdjustment2,
11727
11755
  resolveHaivePaths as resolveHaivePaths28,
11728
- saveUsageIndex as saveUsageIndex7,
11756
+ saveUsageIndex as saveUsageIndex6,
11729
11757
  serializeMemory as serializeMemory21
11730
11758
  } from "@hiveai/core";
11731
11759
  function registerMemoryFeedback(memory2) {
@@ -11755,7 +11783,7 @@ function registerMemoryFeedback(memory2) {
11755
11783
  const outcome = opts.applied ? "applied" : "rejected";
11756
11784
  if (opts.applied) recordApplied2(index, id);
11757
11785
  else recordRejection4(index, id, opts.reason ?? null);
11758
- await saveUsageIndex7(paths, index);
11786
+ await saveUsageIndex6(paths, index);
11759
11787
  const usage = getUsage19(index, id);
11760
11788
  const adjustment = opts.rejected ? recommendFeedbackAdjustment2(target.memory.frontmatter, usage) : { action: "none", reason: "No automatic adjustment needed." };
11761
11789
  const adjustedFrontmatter = applyFeedbackAdjustment2(target.memory.frontmatter, adjustment);
@@ -13586,6 +13614,7 @@ function registerEval(program2) {
13586
13614
  root,
13587
13615
  k,
13588
13616
  spec_source: resolvedSpec.source,
13617
+ provenance: { synthesized: resolvedSpec.synthesized, authored: resolvedSpec.authored },
13589
13618
  report,
13590
13619
  gate_precision: gatePrecision,
13591
13620
  ...delta ? { delta } : {},
@@ -13594,13 +13623,18 @@ function registerEval(program2) {
13594
13623
  applyExitGates(opts, report, delta, gatePrecision, gateDelta);
13595
13624
  return;
13596
13625
  }
13626
+ if (resolvedSpec.authored === 0 && resolvedSpec.synthesized > 0) {
13627
+ ui.warn(
13628
+ `All ${resolvedSpec.synthesized} case(s) are self-synthesized from your own memories (self-referential). Add hand-labeled cases in .ai/eval/spec.json, or run \`haive eval --spec <file>\`, for an independent score.`
13629
+ );
13630
+ }
13597
13631
  if (delta) {
13598
13632
  console.log(renderDelta(delta));
13599
13633
  }
13600
13634
  if (gateDelta) {
13601
13635
  console.log(renderGateDelta(gateDelta));
13602
13636
  }
13603
- const md = renderMarkdown2(root, k, resolvedSpec.source, report, gatePrecision);
13637
+ const md = renderMarkdown2(root, k, resolvedSpec, report, gatePrecision);
13604
13638
  if (opts.out) {
13605
13639
  const outFile = path44.isAbsolute(opts.out) ? opts.out : path44.join(root, opts.out);
13606
13640
  await writeFile33(outFile, md, "utf8");
@@ -13683,30 +13717,39 @@ function renderGateDelta(delta) {
13683
13717
  lines.push(` rejections ${delta.rejections.baseline ?? "n/a"} \u2192 ${delta.rejections.current ?? "n/a"} ${ui.dim(`(${rejectionDelta})`)}`);
13684
13718
  return lines.join("\n");
13685
13719
  }
13720
+ function countCases(spec) {
13721
+ return (spec.retrieval?.length ?? 0) + (spec.sensors?.length ?? 0);
13722
+ }
13686
13723
  async function resolveSpec(opts, root, memoriesDir) {
13687
13724
  if (opts.spec) {
13688
13725
  const file = path44.resolve(opts.spec);
13689
13726
  const raw = await readFile21(file, "utf8");
13690
- return { spec: JSON.parse(raw), source: file };
13727
+ const spec = JSON.parse(raw);
13728
+ return { spec, source: file, synthesized: 0, authored: countCases(spec) };
13691
13729
  }
13692
13730
  const defaultSpec = path44.join(root, ".ai", "eval", "spec.json");
13693
13731
  if (existsSync65(defaultSpec)) {
13694
13732
  const raw = await readFile21(defaultSpec, "utf8");
13695
13733
  const explicit = JSON.parse(raw);
13696
13734
  const memories2 = await loadMemoriesFromDir27(memoriesDir);
13697
- const synthesized = synthesizeSelfEvalCases(memories2, { includeFiles: !opts.semanticOnly });
13735
+ const synthesized2 = synthesizeSelfEvalCases(memories2, { includeFiles: !opts.semanticOnly });
13698
13736
  return {
13699
13737
  spec: {
13700
- retrieval: [...synthesized, ...explicit.retrieval ?? []],
13738
+ retrieval: [...synthesized2, ...explicit.retrieval ?? []],
13701
13739
  sensors: explicit.sensors ?? []
13702
13740
  },
13703
- source: ".ai/eval/spec.json + synthesized anchored retrieval"
13741
+ source: ".ai/eval/spec.json + synthesized anchored retrieval",
13742
+ synthesized: synthesized2.length,
13743
+ authored: countCases(explicit)
13704
13744
  };
13705
13745
  }
13706
13746
  const memories = await loadMemoriesFromDir27(memoriesDir);
13747
+ const synthesized = synthesizeSelfEvalCases(memories, { includeFiles: !opts.semanticOnly });
13707
13748
  return {
13708
- spec: { retrieval: synthesizeSelfEvalCases(memories, { includeFiles: !opts.semanticOnly }) },
13709
- source: "synthesized anchored retrieval"
13749
+ spec: { retrieval: synthesized },
13750
+ source: "synthesized anchored retrieval",
13751
+ synthesized: synthesized.length,
13752
+ authored: 0
13710
13753
  };
13711
13754
  }
13712
13755
  async function runRetrieval(c, k, ctx) {
@@ -13739,12 +13782,14 @@ async function runSensorCase(c, ctx) {
13739
13782
  function pct(n) {
13740
13783
  return `${Math.round(n * 100)}%`;
13741
13784
  }
13742
- function renderMarkdown2(root, k, source, report, gatePrecision) {
13785
+ function renderMarkdown2(root, k, resolved, report, gatePrecision) {
13786
+ const provenance = resolved.authored === 0 ? `${resolved.synthesized} synthesized (self-referential \u2014 sanity floor, not ground truth)` : resolved.synthesized === 0 ? `${resolved.authored} authored (independent ground truth)` : `${resolved.authored} authored (independent) + ${resolved.synthesized} synthesized (self-referential)`;
13743
13787
  const lines = [
13744
13788
  "# hAIve eval report",
13745
13789
  "",
13746
13790
  `Project: \`${root}\` \xB7 top-k: ${k}`,
13747
- `Spec: ${source}`,
13791
+ `Spec: ${resolved.source}`,
13792
+ `Cases: ${provenance}`,
13748
13793
  "",
13749
13794
  `## Overall score: ${report.score}/100`,
13750
13795
  ""
@@ -14185,8 +14230,8 @@ function registerDoctor(program2) {
14185
14230
  fix: "haive init"
14186
14231
  });
14187
14232
  } else {
14188
- const { readFile: readFile28 } = await import("fs/promises");
14189
- const content = await readFile28(paths.projectContext, "utf8");
14233
+ const { readFile: readFile29 } = await import("fs/promises");
14234
+ const content = await readFile29(paths.projectContext, "utf8");
14190
14235
  const isTemplate = content.includes("TODO \u2014 high-level overview") || content.includes("Generated by `haive init`");
14191
14236
  if (isTemplate) {
14192
14237
  findings.push({
@@ -14360,8 +14405,8 @@ function registerDoctor(program2) {
14360
14405
  let hasClaudeEnforcement = false;
14361
14406
  if (existsSync68(claudeSettings)) {
14362
14407
  try {
14363
- const { readFile: readFile28 } = await import("fs/promises");
14364
- const raw = await readFile28(claudeSettings, "utf8");
14408
+ const { readFile: readFile29 } = await import("fs/promises");
14409
+ const raw = await readFile29(claudeSettings, "utf8");
14365
14410
  hasClaudeEnforcement = raw.includes("haive enforce session-start") && raw.includes("haive enforce pre-tool-use");
14366
14411
  } catch {
14367
14412
  hasClaudeEnforcement = false;
@@ -14384,7 +14429,7 @@ function registerDoctor(program2) {
14384
14429
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `haive init` without --manual)."
14385
14430
  });
14386
14431
  }
14387
- findings.push(...await collectInstallFindings(root, "0.21.0"));
14432
+ findings.push(...await collectInstallFindings(root, "0.23.0"));
14388
14433
  findings.push(...await collectToolchainFindings(root));
14389
14434
  try {
14390
14435
  const legacyRaw = execSync3("haive-mcp --version", {
@@ -14392,7 +14437,7 @@ function registerDoctor(program2) {
14392
14437
  timeout: 3e3,
14393
14438
  stdio: ["ignore", "pipe", "ignore"]
14394
14439
  }).trim();
14395
- const cliVersion = "0.21.0";
14440
+ const cliVersion = "0.23.0";
14396
14441
  if (legacyRaw && legacyRaw !== cliVersion) {
14397
14442
  findings.push({
14398
14443
  severity: "warn",
@@ -15465,10 +15510,11 @@ function registerMemoryConflictCandidates(memory2) {
15465
15510
  }
15466
15511
 
15467
15512
  // src/commands/enforce.ts
15468
- import { execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
15513
+ import { execFile as execFile3, execFileSync as execFileSync2, spawn as spawn6 } from "child_process";
15469
15514
  import { existsSync as existsSync75, statSync as statSync3 } from "fs";
15470
15515
  import { chmod as chmod2, mkdir as mkdir21, readFile as readFile24, readdir as readdir6, rm as rm3, writeFile as writeFile37 } from "fs/promises";
15471
15516
  import path53 from "path";
15517
+ import { promisify as promisify3 } from "util";
15472
15518
  import "commander";
15473
15519
  import {
15474
15520
  antiPatternGateParams as antiPatternGateParams2,
@@ -15482,15 +15528,18 @@ import {
15482
15528
  loadMemoriesFromDir as loadMemoriesFromDir38,
15483
15529
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths6,
15484
15530
  readRecentBriefingMarker,
15531
+ recordPreventionHits as recordPreventionHits2,
15485
15532
  resolveBriefingBudget as resolveBriefingBudget3,
15486
15533
  resolveHaivePaths as resolveHaivePaths48,
15487
15534
  runSensors as runSensors2,
15488
15535
  saveConfig as saveConfig4,
15536
+ selectCommandSensors,
15489
15537
  sensorTargetsFromDiff as sensorTargetsFromDiff2,
15490
15538
  SESSION_RECAP_TTL_MS,
15491
15539
  verifyAnchor as verifyAnchor4,
15492
15540
  writeBriefingMarker as writeBriefingMarker3
15493
15541
  } from "@hiveai/core";
15542
+ var execFileAsync2 = promisify3(execFile3);
15494
15543
  var MAX_STDIN_BYTES2 = 256 * 1024;
15495
15544
  var ENFORCE_HOOK_MARKER = "# hAIve enforcement hook";
15496
15545
  function registerEnforce(program2) {
@@ -16092,7 +16141,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
16092
16141
  findings: [{ severity: "info", code: "enforcement-off", message: "hAIve enforcement is disabled." }]
16093
16142
  });
16094
16143
  }
16095
- findings.push(...await inspectIntegrationVersions(root, "0.21.0"));
16144
+ findings.push(...await inspectIntegrationVersions(root, "0.23.0"));
16096
16145
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
16097
16146
  const hasBriefing = await hasRecentBriefingMarker2(paths, sessionId);
16098
16147
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent hAIve briefing marker exists." } : {
@@ -16344,19 +16393,19 @@ async function runSensorGate(paths, diff) {
16344
16393
  if (!diff || !existsSync75(paths.memoriesDir)) return [];
16345
16394
  try {
16346
16395
  const loaded = await loadMemoriesFromDir38(paths.memoriesDir);
16347
- const sensorMemories = loaded.map((l) => l.memory).filter((m) => {
16348
- const s = m.frontmatter.sensor;
16349
- return Boolean(s) && s.kind === "regex" && !isRetiredMemory3(m.frontmatter, m.body);
16350
- });
16351
- if (sensorMemories.length === 0) return [];
16396
+ const scannable = loaded.map((l) => l.memory).filter((m) => Boolean(m.frontmatter.sensor) && !isRetiredMemory3(m.frontmatter, m.body));
16397
+ if (scannable.length === 0) return [];
16352
16398
  const targets = sensorTargetsFromDiff2(diff).filter((t) => isSensorScannablePath(t.path));
16353
16399
  if (targets.length === 0) return [];
16354
- const hits = runSensors2(sensorMemories, targets);
16355
16400
  const findings = [];
16356
16401
  const seen = /* @__PURE__ */ new Set();
16402
+ const firedIds = /* @__PURE__ */ new Set();
16403
+ const regexSensorMemories = scannable.filter((m) => m.frontmatter.sensor.kind === "regex");
16404
+ const hits = regexSensorMemories.length > 0 ? runSensors2(regexSensorMemories, targets) : [];
16357
16405
  for (const hit of hits) {
16358
16406
  if (seen.has(hit.memory_id)) continue;
16359
16407
  seen.add(hit.memory_id);
16408
+ firedIds.add(hit.memory_id);
16360
16409
  const where = hit.file ? ` (${hit.file})` : "";
16361
16410
  if (hit.severity === "block") {
16362
16411
  findings.push({
@@ -16376,11 +16425,51 @@ async function runSensorGate(paths, diff) {
16376
16425
  });
16377
16426
  }
16378
16427
  }
16428
+ const config = await loadConfig14(paths).catch(() => null);
16429
+ if (config?.enforcement?.runCommandSensors === true) {
16430
+ const changedPaths = targets.map((t) => t.path).filter(Boolean);
16431
+ for (const spec of selectCommandSensors(scannable, changedPaths)) {
16432
+ if (seen.has(spec.memory_id)) continue;
16433
+ const failed = await runGateCommandSensor(spec, paths.root);
16434
+ if (!failed) continue;
16435
+ seen.add(spec.memory_id);
16436
+ firedIds.add(spec.memory_id);
16437
+ if (spec.severity === "block") {
16438
+ findings.push({
16439
+ severity: "error",
16440
+ code: "sensor-block",
16441
+ message: `Block command sensor fired \u2014 ${spec.memory_id}: ${spec.message} (command: ${spec.command})`,
16442
+ fix: "Fix the condition the command checks, or run `haive sensors check --commands` to inspect it.",
16443
+ impact: 45
16444
+ });
16445
+ } else {
16446
+ findings.push({
16447
+ severity: "warn",
16448
+ code: "sensor-warn",
16449
+ message: `Command sensor flagged ${spec.memory_id}: ${spec.message}`,
16450
+ fix: "Review the failing command; `haive sensors check --commands` re-runs it.",
16451
+ impact: 5
16452
+ });
16453
+ }
16454
+ }
16455
+ }
16456
+ if (firedIds.size > 0) {
16457
+ await recordPreventionHits2(paths, [...firedIds], "sensor").catch(() => {
16458
+ });
16459
+ }
16379
16460
  return findings;
16380
16461
  } catch {
16381
16462
  return [];
16382
16463
  }
16383
16464
  }
16465
+ async function runGateCommandSensor(spec, root) {
16466
+ try {
16467
+ await execFileAsync2("bash", ["-c", spec.command], { cwd: root, timeout: 12e4, maxBuffer: 8 * 1024 * 1024 });
16468
+ return false;
16469
+ } catch {
16470
+ return true;
16471
+ }
16472
+ }
16384
16473
  async function findGeneratedArtifacts(paths) {
16385
16474
  const dirty = await runCommand4("git", ["status", "--short", "--untracked-files=all"], paths.root).catch(() => "");
16386
16475
  const generated = dirty.split("\n").map((line) => line.trim()).filter(Boolean).filter(
@@ -17166,28 +17255,25 @@ function registerRun(program2) {
17166
17255
  }
17167
17256
 
17168
17257
  // src/commands/sensors.ts
17169
- import { execFile as execFile3 } from "child_process";
17258
+ import { execFile as execFile4 } from "child_process";
17170
17259
  import { existsSync as existsSync76 } from "fs";
17171
17260
  import { chmod as chmod3, mkdir as mkdir23, readFile as readFile25, writeFile as writeFile38 } from "fs/promises";
17172
17261
  import path54 from "path";
17173
- import { promisify as promisify3 } from "util";
17262
+ import { promisify as promisify4 } from "util";
17174
17263
  import "commander";
17175
17264
  import {
17176
- appendPreventionEvent as appendPreventionEvent2,
17177
17265
  findProjectRoot as findProjectRoot53,
17178
17266
  isRetiredMemory as isRetiredMemory4,
17179
17267
  loadConfig as loadConfig15,
17180
17268
  loadMemoriesFromDir as loadMemoriesFromDir39,
17181
- loadUsageIndex as loadUsageIndex31,
17182
- recordPrevention as recordPrevention2,
17269
+ recordPreventionHits as recordPreventionHits3,
17183
17270
  resolveHaivePaths as resolveHaivePaths49,
17184
17271
  runSensors as runSensors3,
17185
- saveUsageIndex as saveUsageIndex8,
17186
- selectCommandSensors,
17272
+ selectCommandSensors as selectCommandSensors2,
17187
17273
  sensorTargetsFromDiff as sensorTargetsFromDiff3,
17188
17274
  serializeMemory as serializeMemory29
17189
17275
  } from "@hiveai/core";
17190
- var exec2 = promisify3(execFile3);
17276
+ var exec2 = promisify4(execFile4);
17191
17277
  function registerSensors(program2) {
17192
17278
  const sensors = program2.command("sensors").description("Operate executable sensors derived from hAIve memories");
17193
17279
  sensors.command("list").description("List memories carrying executable sensors").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
@@ -17224,7 +17310,7 @@ function registerSensors(program2) {
17224
17310
  const runCommands = opts.commands || config.enforcement?.runCommandSensors === true;
17225
17311
  const changedPaths = targets.map((t) => t.path).filter(Boolean);
17226
17312
  const allSensorMemories = await runnableSensorMemories(paths, false);
17227
- const commandSpecs = selectCommandSensors(allSensorMemories, changedPaths);
17313
+ const commandSpecs = selectCommandSensors2(allSensorMemories, changedPaths);
17228
17314
  const commandHits = [];
17229
17315
  const commandSkipped = [];
17230
17316
  if (commandSpecs.length > 0 && runCommands) {
@@ -17243,20 +17329,7 @@ function registerSensors(program2) {
17243
17329
  for (const spec of commandSpecs) commandSkipped.push(spec.memory_id);
17244
17330
  }
17245
17331
  const firedIds = [...new Set([...hits, ...commandHits].map((hit) => hit.memory_id))];
17246
- if (firedIds.length > 0) {
17247
- const usage = await loadUsageIndex31(paths);
17248
- const recordedIds = [];
17249
- for (const id of firedIds) if (recordPrevention2(usage, id)) recordedIds.push(id);
17250
- if (recordedIds.length > 0) {
17251
- await saveUsageIndex8(paths, usage).catch(() => {
17252
- });
17253
- const at = (/* @__PURE__ */ new Date()).toISOString();
17254
- for (const id of recordedIds) {
17255
- await appendPreventionEvent2(paths, { at, id, source: "sensor" }).catch(() => {
17256
- });
17257
- }
17258
- }
17259
- }
17332
+ await recordPreventionHits3(paths, firedIds, "sensor");
17260
17333
  const output = {
17261
17334
  scanned: memories.length,
17262
17335
  hits: hits.map((hit) => ({
@@ -17623,7 +17696,7 @@ import {
17623
17696
  loadConfig as loadConfig16,
17624
17697
  loadMemoriesFromDir as loadMemoriesFromDir41,
17625
17698
  loadPreventionEvents as loadPreventionEvents4,
17626
- loadUsageIndex as loadUsageIndex33,
17699
+ loadUsageIndex as loadUsageIndex31,
17627
17700
  resolveHaivePaths as resolveHaivePaths51
17628
17701
  } from "@hiveai/core";
17629
17702
  function registerDashboard(program2) {
@@ -17638,7 +17711,7 @@ function registerDashboard(program2) {
17638
17711
  return;
17639
17712
  }
17640
17713
  const memories = existsSync78(paths.memoriesDir) ? await loadMemoriesFromDir41(paths.memoriesDir) : [];
17641
- const usage = await loadUsageIndex33(paths);
17714
+ const usage = await loadUsageIndex31(paths);
17642
17715
  const preventionEvents = await loadPreventionEvents4(paths);
17643
17716
  const config = await loadConfig16(paths);
17644
17717
  const top = Math.max(1, Number.parseInt(opts.top ?? "10", 10) || 10);
@@ -17749,14 +17822,14 @@ function warnNum(n) {
17749
17822
  }
17750
17823
 
17751
17824
  // src/commands/dev-link.ts
17752
- import { execFile as execFile4 } from "child_process";
17825
+ import { execFile as execFile5 } from "child_process";
17753
17826
  import { cp, readFile as readFile27 } from "fs/promises";
17754
17827
  import { existsSync as existsSync79 } from "fs";
17755
17828
  import path56 from "path";
17756
- import { promisify as promisify4 } from "util";
17829
+ import { promisify as promisify5 } from "util";
17757
17830
  import "commander";
17758
17831
  import { findProjectRoot as findProjectRoot56 } from "@hiveai/core";
17759
- var exec3 = promisify4(execFile4);
17832
+ var exec3 = promisify5(execFile5);
17760
17833
  function registerDevLink(program2) {
17761
17834
  const dev = program2.commands.find((c) => c.name() === "dev") ?? program2.command("dev").description("Developer utilities for working on hAIve itself.");
17762
17835
  dev.command("link").description("Hot-swap this repo's built dist into the global @hiveai install so `haive` runs your local code.").option("-d, --dir <dir>", "repo root (default: discovered from cwd)").option("--json", "emit a machine-readable summary", false).action(async (opts) => {
@@ -17812,8 +17885,41 @@ function registerDevLink(program2) {
17812
17885
  }
17813
17886
 
17814
17887
  // src/commands/coverage.ts
17888
+ import { readFile as readFile28 } from "fs/promises";
17889
+ import { existsSync as existsSync80 } from "fs";
17890
+ import path57 from "path";
17815
17891
  import "commander";
17816
- import { findCoverageGaps, findProjectRoot as findProjectRoot57, resolveHaivePaths as resolveHaivePaths52 } from "@hiveai/core";
17892
+ import {
17893
+ findCoverageGaps,
17894
+ findProjectRoot as findProjectRoot57,
17895
+ mergeHotFiles,
17896
+ resolveHaivePaths as resolveHaivePaths52,
17897
+ tallyHotFiles
17898
+ } from "@hiveai/core";
17899
+ async function readAgentHotFiles(root, cacheFile, sinceMs) {
17900
+ if (!existsSync80(cacheFile)) return [];
17901
+ const raw = await readFile28(cacheFile, "utf8").catch(() => "");
17902
+ const files = [];
17903
+ for (const line of raw.split("\n")) {
17904
+ const trimmed = line.trim();
17905
+ if (!trimmed) continue;
17906
+ try {
17907
+ const obs = JSON.parse(trimmed);
17908
+ if (sinceMs > 0 && obs.ts) {
17909
+ const t = Date.parse(obs.ts);
17910
+ if (Number.isFinite(t) && t < sinceMs) continue;
17911
+ }
17912
+ for (const f of obs.files ?? []) {
17913
+ if (typeof f !== "string" || !f) continue;
17914
+ const rel = path57.isAbsolute(f) ? path57.relative(root, f) : f;
17915
+ if (rel.startsWith("..")) continue;
17916
+ files.push(rel);
17917
+ }
17918
+ } catch {
17919
+ }
17920
+ }
17921
+ return tallyHotFiles(files, "agent");
17922
+ }
17817
17923
  function isNoisePath(p) {
17818
17924
  if (/(^|\/)(node_modules|dist|build|coverage|\.next)\//.test(p)) return true;
17819
17925
  if (p.startsWith(".ai/")) return true;
@@ -17825,29 +17931,47 @@ function isNoisePath(p) {
17825
17931
  function registerCoverage(program2) {
17826
17932
  program2.command("coverage").description(
17827
17933
  "Coverage-gap report: frequently-edited files with no covering team memory (blind spots)."
17828
- ).option("--json", "emit JSON", false).option("--min-changes <n>", "minimum git-churn count to flag a file", "3").option("--limit <n>", "max gaps to report", "20").option("--days <n>", "git-history lookback window in days", "90").option("-d, --dir <dir>", "project root").action(async (opts) => {
17934
+ ).option("--json", "emit JSON", false).option("--min-changes <n>", "minimum churn count to flag a file", "3").option("--limit <n>", "max gaps to report", "20").option("--days <n>", "lookback window in days (git history + agent edits)", "90").option("--source <which>", "heat source: git | agent | both", "both").option("-d, --dir <dir>", "project root").action(async (opts) => {
17829
17935
  const root = findProjectRoot57(opts.dir);
17830
17936
  const paths = resolveHaivePaths52(root);
17831
17937
  const minChanges = Math.max(1, parseInt(opts.minChanges ?? "3", 10) || 3);
17832
17938
  const limit = Math.max(1, parseInt(opts.limit ?? "20", 10) || 20);
17833
17939
  const days = Math.max(1, parseInt(opts.days ?? "90", 10) || 90);
17834
- const radar = await buildRadar({
17940
+ const source = (opts.source ?? "both").toLowerCase();
17941
+ if (!["git", "agent", "both"].includes(source)) {
17942
+ ui.error("--source must be one of: git | agent | both");
17943
+ process.exitCode = 1;
17944
+ return;
17945
+ }
17946
+ const useGit = source === "git" || source === "both";
17947
+ const useAgent = source === "agent" || source === "both";
17948
+ const radar = useGit ? await buildRadar({
17835
17949
  root,
17836
17950
  taskTokens: null,
17837
17951
  filePaths: [],
17838
17952
  daysBack: Math.ceil(days / 6),
17839
17953
  // getHotFiles multiplies daysBack by 6
17840
17954
  maxHotFiles: 500
17841
- });
17842
- const hotFiles = radar.hotFiles.filter((h) => !isNoisePath(h.path)).map((h) => ({ path: h.path, changes: h.changes }));
17955
+ }) : null;
17956
+ const gitHotFiles = (radar?.hotFiles ?? []).filter((h) => !isNoisePath(h.path)).map((h) => ({ path: h.path, changes: h.changes, source: "git" }));
17957
+ const sinceMs = Date.now() - days * 864e5;
17958
+ const agentHotFiles = useAgent ? (await readAgentHotFiles(root, path57.join(paths.haiveDir, ".cache", "observations.jsonl"), sinceMs)).filter((h) => !isNoisePath(h.path)) : [];
17959
+ const hotFiles = mergeHotFiles(gitHotFiles, agentHotFiles);
17843
17960
  const memories = await loadMemoriesFromDir27(paths.memoriesDir);
17844
17961
  const gaps = findCoverageGaps(hotFiles, memories, { minChanges, limit });
17845
17962
  if (opts.json) {
17846
- console.log(JSON.stringify({ root, scanned_hot_files: hotFiles.length, gaps }, null, 2));
17963
+ console.log(JSON.stringify({
17964
+ root,
17965
+ source,
17966
+ scanned_hot_files: hotFiles.length,
17967
+ git_hot_files: gitHotFiles.length,
17968
+ agent_hot_files: agentHotFiles.length,
17969
+ gaps
17970
+ }, null, 2));
17847
17971
  return;
17848
17972
  }
17849
- if (!radar.insideGitRepo) {
17850
- ui.warn("Not a git repository \u2014 coverage uses git churn to find hot files.");
17973
+ if (useGit && radar && !radar.insideGitRepo && agentHotFiles.length === 0) {
17974
+ ui.warn("Not a git repository and no agent-edit history \u2014 nothing to cross-check.");
17851
17975
  return;
17852
17976
  }
17853
17977
  if (gaps.length === 0) {
@@ -17856,7 +17980,8 @@ function registerCoverage(program2) {
17856
17980
  }
17857
17981
  console.log(ui.bold(`hAIve coverage \u2014 ${gaps.length} blind spot(s) (hot files with no covering memory)`));
17858
17982
  for (const gap of gaps) {
17859
- console.log(` ${ui.yellow("\u25CB")} ${gap.path} ${ui.dim(`(${gap.changes} change${gap.changes === 1 ? "" : "s"})`)}`);
17983
+ const src = gap.source ? ui.dim(` [${gap.source}]`) : "";
17984
+ console.log(` ${ui.yellow("\u25CB")} ${gap.path} ${ui.dim(`(${gap.changes} change${gap.changes === 1 ? "" : "s"})`)}${src}`);
17860
17985
  }
17861
17986
  console.log(
17862
17987
  ui.dim(
@@ -17868,8 +17993,8 @@ function registerCoverage(program2) {
17868
17993
 
17869
17994
  // src/commands/merge-driver.ts
17870
17995
  import { execFileSync as execFileSync3 } from "child_process";
17871
- import { readFileSync, writeFileSync, existsSync as existsSync80 } from "fs";
17872
- import path57 from "path";
17996
+ import { readFileSync, writeFileSync, existsSync as existsSync81 } from "fs";
17997
+ import path58 from "path";
17873
17998
  import "commander";
17874
17999
  import { findProjectRoot as findProjectRoot58, mergeMemoryVersions } from "@hiveai/core";
17875
18000
  var GITATTRIBUTES_MARK = "# hAIve merge driver";
@@ -17901,8 +18026,8 @@ function registerMergeDriver(program2) {
17901
18026
  process.exitCode = 1;
17902
18027
  return;
17903
18028
  }
17904
- const gaPath = path57.join(root, ".gitattributes");
17905
- let content = existsSync80(gaPath) ? readFileSync(gaPath, "utf8") : "";
18029
+ const gaPath = path58.join(root, ".gitattributes");
18030
+ let content = existsSync81(gaPath) ? readFileSync(gaPath, "utf8") : "";
17906
18031
  if (!content.includes(GITATTRIBUTES_MARK)) {
17907
18032
  if (content.length > 0 && !content.endsWith("\n")) content += "\n";
17908
18033
  content += GITATTRIBUTES_BLOCK + "\n";
@@ -17917,11 +18042,12 @@ function registerMergeDriver(program2) {
17917
18042
 
17918
18043
  // src/commands/memory-resolve-conflict.ts
17919
18044
  import { writeFile as writeFile40 } from "fs/promises";
17920
- import { existsSync as existsSync81 } from "fs";
18045
+ import { existsSync as existsSync83 } from "fs";
17921
18046
  import "commander";
17922
18047
  import {
18048
+ applyConflictResolution,
17923
18049
  findProjectRoot as findProjectRoot59,
17924
- planConflictResolution,
18050
+ planConflictResolution as planConflictResolution2,
17925
18051
  resolveHaivePaths as resolveHaivePaths53,
17926
18052
  serializeMemory as serializeMemory31
17927
18053
  } from "@hiveai/core";
@@ -17929,7 +18055,7 @@ function registerMemoryResolveConflict(memory2) {
17929
18055
  memory2.command("resolve-conflict <id_a> <id_b>").description("Resolve a contradiction: keep the stronger memory, deprecate (supersede) the other").option("--yes", "apply the resolution (without this, only previews it)", false).option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (idA, idB, opts) => {
17930
18056
  const root = findProjectRoot59(opts.dir);
17931
18057
  const paths = resolveHaivePaths53(root);
17932
- if (!existsSync81(paths.memoriesDir)) {
18058
+ if (!existsSync83(paths.memoriesDir)) {
17933
18059
  ui.error(`No .ai/memories at ${root}.`);
17934
18060
  process.exitCode = 1;
17935
18061
  return;
@@ -17942,43 +18068,53 @@ function registerMemoryResolveConflict(memory2) {
17942
18068
  process.exitCode = 1;
17943
18069
  return;
17944
18070
  }
17945
- const plan = planConflictResolution(a, b);
18071
+ const plan = planConflictResolution2(a, b);
18072
+ const winner = plan.keep_id === idA ? a : b;
17946
18073
  const loser = plan.supersede_id === idA ? a : b;
18074
+ const applied = applyConflictResolution(winner, loser, plan);
17947
18075
  if (opts.json) {
17948
- console.log(JSON.stringify({ ...plan, applied: Boolean(opts.yes) }, null, 2));
18076
+ console.log(JSON.stringify({
18077
+ ...plan,
18078
+ winner_revision_count: applied.winner.revision_count,
18079
+ topic: applied.topic,
18080
+ topic_adopted: applied.topic_adopted,
18081
+ applied: Boolean(opts.yes)
18082
+ }, null, 2));
17949
18083
  } else {
17950
18084
  console.log(ui.bold("Conflict resolution"));
17951
- console.log(` keep: ${ui.green(plan.keep_id)}`);
18085
+ console.log(` keep: ${ui.green(plan.keep_id)} ${ui.dim(`(rev ${winner.memory.frontmatter.revision_count}\u2192${applied.winner.revision_count})`)}`);
17952
18086
  console.log(` supersede: ${ui.red(plan.supersede_id)} ${ui.dim(`\u2192 deprecated`)}`);
17953
18087
  console.log(` reason: ${plan.reason}`);
18088
+ if (applied.topic) {
18089
+ console.log(` topic: ${applied.topic}${applied.topic_adopted ? ui.dim(" (adopted from superseded \u2014 future captures upsert into the winner)") : ""}`);
18090
+ }
17954
18091
  }
17955
18092
  if (!opts.yes) {
17956
18093
  if (!opts.json) ui.info("Preview only \u2014 re-run with --yes to apply.");
17957
18094
  return;
17958
18095
  }
18096
+ await writeFile40(
18097
+ winner.filePath,
18098
+ serializeMemory31({ frontmatter: applied.winner, body: winner.memory.body }),
18099
+ "utf8"
18100
+ );
17959
18101
  await writeFile40(
17960
18102
  loser.filePath,
17961
- serializeMemory31({
17962
- frontmatter: {
17963
- ...loser.memory.frontmatter,
17964
- status: "deprecated",
17965
- stale_reason: plan.stale_reason,
17966
- related_ids: [.../* @__PURE__ */ new Set([...loser.memory.frontmatter.related_ids, plan.keep_id])]
17967
- },
17968
- body: loser.memory.body
17969
- }),
18103
+ serializeMemory31({ frontmatter: applied.loser, body: loser.memory.body }),
17970
18104
  "utf8"
17971
18105
  );
17972
- if (!opts.json) ui.success(`Deprecated ${plan.supersede_id}; ${plan.keep_id} remains authoritative.`);
18106
+ if (!opts.json) {
18107
+ ui.success(`Deprecated ${plan.supersede_id}; promoted ${plan.keep_id} (rev ${applied.winner.revision_count}${applied.topic ? `, topic=${applied.topic}` : ""}).`);
18108
+ }
17973
18109
  });
17974
18110
  }
17975
18111
 
17976
18112
  // src/commands/memory-seed-git.ts
17977
- import { execFile as execFile5 } from "child_process";
18113
+ import { execFile as execFile6 } from "child_process";
17978
18114
  import { mkdir as mkdir25, writeFile as writeFile41 } from "fs/promises";
17979
- import { existsSync as existsSync83 } from "fs";
17980
- import path58 from "path";
17981
- import { promisify as promisify5 } from "util";
18115
+ import { existsSync as existsSync84 } from "fs";
18116
+ import path59 from "path";
18117
+ import { promisify as promisify6 } from "util";
17982
18118
  import "commander";
17983
18119
  import {
17984
18120
  buildFrontmatter as buildFrontmatter12,
@@ -17988,12 +18124,12 @@ import {
17988
18124
  resolveHaivePaths as resolveHaivePaths54,
17989
18125
  serializeMemory as serializeMemory33
17990
18126
  } from "@hiveai/core";
17991
- var exec4 = promisify5(execFile5);
18127
+ var exec4 = promisify6(execFile6);
17992
18128
  function registerMemorySeedGit(memory2) {
17993
18129
  memory2.command("seed-git").description("Propose draft `attempt` seeds from revert/hotfix commits in git history (cold-start)").option("--apply", "write the proposed seeds as draft memories (default: preview only)", false).option("--limit <n>", "max seeds to propose", "20").option("--days <n>", "git-history lookback window in days", "365").option("--scope <scope>", "personal | team", "team").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
17994
18130
  const root = findProjectRoot60(opts.dir);
17995
18131
  const paths = resolveHaivePaths54(root);
17996
- if (!existsSync83(paths.haiveDir)) {
18132
+ if (!existsSync84(paths.haiveDir)) {
17997
18133
  ui.error(`No .ai/ found at ${root}. Run \`haive init\` first.`);
17998
18134
  process.exitCode = 1;
17999
18135
  return;
@@ -18038,8 +18174,8 @@ function registerMemorySeedGit(memory2) {
18038
18174
  _Seeded from git ${p.kind} commit ${p.source_sha}. Review and validate (or delete) \u2014 not yet authoritative._
18039
18175
  `;
18040
18176
  const file = memoryFilePath13(paths, fm.scope, fm.id, fm.module);
18041
- if (existsSync83(file)) continue;
18042
- await mkdir25(path58.dirname(file), { recursive: true });
18177
+ if (existsSync84(file)) continue;
18178
+ await mkdir25(path59.dirname(file), { recursive: true });
18043
18179
  await writeFile41(file, serializeMemory33({ frontmatter: fm, body }), "utf8");
18044
18180
  written += 1;
18045
18181
  }
@@ -18071,8 +18207,8 @@ async function readCommits(root, days) {
18071
18207
  }
18072
18208
 
18073
18209
  // src/commands/bridges.ts
18074
- import { existsSync as existsSync84 } from "fs";
18075
- import path59 from "path";
18210
+ import { existsSync as existsSync85 } from "fs";
18211
+ import path60 from "path";
18076
18212
  import "commander";
18077
18213
  import {
18078
18214
  findProjectRoot as findProjectRoot61,
@@ -18093,7 +18229,7 @@ function registerBridges(program2) {
18093
18229
  const root = findProjectRoot61(opts.dir);
18094
18230
  const paths = resolveHaivePaths55(root);
18095
18231
  const dryRun = opts.dryRun === true;
18096
- if (!existsSync84(paths.memoriesDir)) {
18232
+ if (!existsSync85(paths.memoriesDir)) {
18097
18233
  ui.warn(`No .ai/memories at ${root}. Run \`haive init\` first.`);
18098
18234
  process.exitCode = 1;
18099
18235
  return;
@@ -18112,7 +18248,7 @@ function registerBridges(program2) {
18112
18248
  targets = BRIDGE_TARGETS3;
18113
18249
  } else {
18114
18250
  targets = BRIDGE_TARGETS3.filter(
18115
- (t) => existsSync84(path59.join(root, BRIDGE_TARGET_PATH3[t]))
18251
+ (t) => existsSync85(path60.join(root, BRIDGE_TARGET_PATH3[t]))
18116
18252
  );
18117
18253
  if (targets.length === 0) {
18118
18254
  ui.info(
@@ -18141,7 +18277,7 @@ function registerBridges(program2) {
18141
18277
  console.log(ui.bold("hAIve bridge targets:"));
18142
18278
  for (const target of BRIDGE_TARGETS3) {
18143
18279
  const relPath = BRIDGE_TARGET_PATH3[target];
18144
- const exists = existsSync84(path59.join(root, relPath));
18280
+ const exists = existsSync85(path60.join(root, relPath));
18145
18281
  const marker = exists ? ui.dim("\u2713") : ui.dim("\xB7");
18146
18282
  console.log(` ${marker} ${target.padEnd(10)} ${relPath}${exists ? "" : " (not present)"}`);
18147
18283
  }
@@ -18152,7 +18288,7 @@ function registerBridges(program2) {
18152
18288
 
18153
18289
  // src/index.ts
18154
18290
  var program = new Command64();
18155
- program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.21.0").option("--advanced", "show maintenance and experimental commands in help");
18291
+ program.name("haive").description("hAIve - repo-native memory and context policy for coding-agent harnesses").version("0.23.0").option("--advanced", "show maintenance and experimental commands in help");
18156
18292
  registerInit(program);
18157
18293
  registerWelcome(program);
18158
18294
  registerResolveProject(program);