@hivelore/cli 0.38.0 → 0.39.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
@@ -3,7 +3,7 @@ import {
3
3
  antiPatternsCheck,
4
4
  codeMapTool,
5
5
  codeSearch,
6
- detectTestFrameworkForPaths,
6
+ detectTestFrameworksForAnchors,
7
7
  getBriefing,
8
8
  getRecap,
9
9
  memRelevantTo,
@@ -11,7 +11,7 @@ import {
11
11
  preCommitCheck,
12
12
  readPresumedCorrectTargets,
13
13
  runHaiveMcpStdio
14
- } from "./chunk-UOMGIXZN.js";
14
+ } from "./chunk-YMIQAOFL.js";
15
15
  import {
16
16
  registerMemoryPending
17
17
  } from "./chunk-OYJKHD22.js";
@@ -209,7 +209,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
209
209
  if (!f) continue;
210
210
  counts.set(f, (counts.get(f) ?? 0) + 1);
211
211
  }
212
- let entries = [...counts.entries()].map(([path47, changes]) => ({ path: path47, changes }));
212
+ let entries = [...counts.entries()].map(([path48, changes]) => ({ path: path48, changes }));
213
213
  const lowerPaths = filePaths.map((p) => p.toLowerCase());
214
214
  if (lowerPaths.length > 0) {
215
215
  entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
@@ -3756,7 +3756,7 @@ ${SEED_FOOTER(stack)}` });
3756
3756
 
3757
3757
  // src/commands/init.ts
3758
3758
  var execFileAsync = promisify2(execFile2);
3759
- var HAIVE_GITHUB_ACTION_REF = `v${"0.38.0"}`;
3759
+ var HAIVE_GITHUB_ACTION_REF = `v${"0.39.1"}`;
3760
3760
  var PROJECT_CONTEXT_TEMPLATE = `# Project context
3761
3761
 
3762
3762
  > Generated by \`hivelore init\`. Run \`hivelore init --bootstrap\` to auto-fill from your codebase,
@@ -6023,7 +6023,7 @@ function parseCsv4(raw) {
6023
6023
  function registerMemoryTried(memory2) {
6024
6024
  memory2.command("tried").description(
6025
6025
  'Record a FAILED approach \u2014 prevents repeated mistakes in future sessions.\n\n This is the most valuable type of negative knowledge. It surfaces FIRST in\n get_briefing so agents can\'t miss it. Auto-validated (no approval cycle).\n\n Use this immediately when you try something and it fails.\n\n One-shot loop close: add --sensor-pattern to validate and attach the guardrail\n in the same command (equivalent to a follow-up `sensors propose`).\n\n Example:\n hivelore memory tried \\\\\n --what "importing X with ESM dynamic import" \\\\\n --why-failed "tsup bundles it as CJS, dynamic import fails at runtime" \\\\\n --instead "use static import in the entry file" \\\\\n --paths packages/cli/src/index.ts \\\\\n --sensor-pattern "await import\\\\(" --sensor-absent "static import"\n'
6026
- ).requiredOption("--what <text>", "what approach was tried (short, descriptive title)").requiredOption("--why-failed <text>", "why it failed or should NOT be used (include the exact error if possible)").option("--instead <text>", "the correct approach to use instead").option("--scope <scope>", "personal | team | module", "personal").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags").option("--paths <csv>", "anchor paths, comma-separated").option("--files <csv>", "alias for --paths (matches the MCP `files` parameter)").option("--author <author>", "author email or handle").option("--sensor-pattern <regex>", "one-shot: regex matching the FAULTY usage \u2014 validates + attaches a sensor in this call").option("--sensor-command <cmd>", "one-shot BEHAVIOUR sensor: a command (test/script) the gate runs when the diff touches --paths; non-zero exit = lesson fires").option("--sensor-kind <kind>", "with --sensor-command: shell | test (default test)").option("--sensor-timeout <ms>", "with --sensor-command: max runtime in ms (default 120000)").option("--sensor-absent <regex>", "one-shot: regex marking CORRECT usage nearby (excludes it from firing)").option("--sensor-severity <level>", "one-shot sensor severity: warn | block", "block").option("--sensor-message <text>", "one-shot: self-correction message shown when the sensor fires").option("--bad-example <code>", "one-shot: code snippet the sensor must fire on (validation)").option("-d, --dir <dir>", "project root").action(async (opts) => {
6026
+ ).requiredOption("--what <text>", "what approach was tried (short, descriptive title)").requiredOption("--why-failed <text>", "why it failed or should NOT be used (include the exact error if possible)").option("--instead <text>", "the correct approach to use instead").option("--scope <scope>", "personal | team | module (default: personal \u2014 team when a --sensor-* option arms the lesson, so the gate travels)").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags").option("--paths <csv>", "anchor paths, comma-separated").option("--files <csv>", "alias for --paths (matches the MCP `files` parameter)").option("--author <author>", "author email or handle").option("--sensor-pattern <regex>", "one-shot: regex matching the FAULTY usage \u2014 validates + attaches a sensor in this call").option("--sensor-command <cmd>", "one-shot BEHAVIOUR sensor: a command (test/script) the gate runs when the diff touches --paths; non-zero exit = lesson fires").option("--sensor-kind <kind>", "with --sensor-command: shell | test (default test)").option("--sensor-timeout <ms>", "with --sensor-command: max runtime in ms (default 120000)").option("--sensor-absent <regex>", "one-shot: regex marking CORRECT usage nearby (excludes it from firing)").option("--sensor-severity <level>", "one-shot sensor severity: warn | block", "block").option("--sensor-message <text>", "one-shot: self-correction message shown when the sensor fires").option("--bad-example <code>", "one-shot: code snippet the sensor must fire on (validation)").option("-d, --dir <dir>", "project root").action(async (opts) => {
6027
6027
  const root = findProjectRoot15(opts.dir);
6028
6028
  const paths = resolveHaivePaths14(root);
6029
6029
  if (!existsSync20(paths.haiveDir)) {
@@ -6040,7 +6040,8 @@ function registerMemoryTried(memory2) {
6040
6040
  why_failed: opts.whyFailed,
6041
6041
  instead: opts.instead,
6042
6042
  // "shared" is a legacy MemoryScope alias not accepted by mem_tried — normalize to team.
6043
- scope: opts.scope === "shared" ? "team" : opts.scope ?? "personal",
6043
+ // Undefined stays undefined: mem_tried defaults it (team when a sensor is attached).
6044
+ scope: opts.scope === "shared" ? "team" : opts.scope,
6044
6045
  module: opts.module,
6045
6046
  tags: parseCsv4(opts.tags),
6046
6047
  paths: parseCsv4(opts.paths ?? opts.files),
@@ -6073,7 +6074,7 @@ function registerMemoryTried(memory2) {
6073
6074
  return;
6074
6075
  }
6075
6076
  ui.success(`Recorded: ${path21.relative(root, result.file_path)}`);
6076
- ui.info(`id=${result.id} type=attempt status=validated (auto-approved)`);
6077
+ ui.info(`id=${result.id} type=attempt scope=${result.scope} status=validated (auto-approved)`);
6077
6078
  if (result.sensor_result) {
6078
6079
  if (result.sensor_result.accepted) {
6079
6080
  ui.success(`Loop closed: sensor attached (${result.sensor_result.severity}) \u2014 the gate now refuses a repeat deterministically.`);
@@ -8575,9 +8576,9 @@ function parseDays(input) {
8575
8576
  }
8576
8577
 
8577
8578
  // src/commands/doctor.ts
8578
- import { existsSync as existsSync39, statSync as statSync2 } from "fs";
8579
+ import { existsSync as existsSync40, statSync as statSync3 } from "fs";
8579
8580
  import { readFile as readFile16, stat, writeFile as writeFile23 } from "fs/promises";
8580
- import path36 from "path";
8581
+ import path37 from "path";
8581
8582
  import { execFileSync, execSync } from "child_process";
8582
8583
  import "commander";
8583
8584
  import {
@@ -8599,6 +8600,85 @@ import {
8599
8600
  readUsageEvents as readUsageEvents3,
8600
8601
  resolveHaivePaths as resolveHaivePaths33
8601
8602
  } from "@hivelore/core";
8603
+
8604
+ // src/utils/post-incident-scan.ts
8605
+ import { readdirSync as readdirSync4, readFileSync, statSync as statSync2 } from "fs";
8606
+ import { existsSync as existsSync39 } from "fs";
8607
+ import path36 from "path";
8608
+ import {
8609
+ assessScaffoldLoop,
8610
+ loadMemoriesFromDir as loadMemoriesFromDir14,
8611
+ SCAFFOLD_MARKER_RE
8612
+ } from "@hivelore/core";
8613
+ var PRUNED_DIRS = /* @__PURE__ */ new Set([
8614
+ "node_modules",
8615
+ ".git",
8616
+ ".ai",
8617
+ "dist",
8618
+ "build",
8619
+ "out",
8620
+ "coverage",
8621
+ ".next",
8622
+ ".venv",
8623
+ "venv",
8624
+ "__pycache__",
8625
+ "target",
8626
+ "vendor"
8627
+ ]);
8628
+ var MAX_SCAFFOLD_BYTES = 64 * 1024;
8629
+ var MAX_DEPTH = 8;
8630
+ function findPostIncidentScaffoldFiles(root) {
8631
+ const results = [];
8632
+ const walk = (dir, depth, inIncidents) => {
8633
+ if (depth > MAX_DEPTH) return;
8634
+ let entries;
8635
+ try {
8636
+ entries = readdirSync4(dir);
8637
+ } catch {
8638
+ return;
8639
+ }
8640
+ for (const entry of entries) {
8641
+ if (PRUNED_DIRS.has(entry)) continue;
8642
+ const abs = path36.join(dir, entry);
8643
+ let stat2;
8644
+ try {
8645
+ stat2 = statSync2(abs);
8646
+ } catch {
8647
+ continue;
8648
+ }
8649
+ if (stat2.isDirectory()) {
8650
+ walk(abs, depth + 1, inIncidents || entry === "incidents");
8651
+ continue;
8652
+ }
8653
+ if (!inIncidents || stat2.size > MAX_SCAFFOLD_BYTES) continue;
8654
+ try {
8655
+ const content = readFileSync(abs, "utf8");
8656
+ if (SCAFFOLD_MARKER_RE.test(content)) {
8657
+ results.push({ path: path36.relative(root, abs).split(path36.sep).join("/"), content });
8658
+ }
8659
+ } catch {
8660
+ }
8661
+ }
8662
+ };
8663
+ walk(root, 0, false);
8664
+ return results;
8665
+ }
8666
+ async function collectScaffoldLoopGaps(paths) {
8667
+ const files = findPostIncidentScaffoldFiles(paths.root);
8668
+ if (files.length === 0) return [];
8669
+ const loaded = existsSync39(paths.memoriesDir) ? await loadMemoriesFromDir14(paths.memoriesDir) : [];
8670
+ const memories = loaded.map(({ memory: memory2 }) => ({
8671
+ id: memory2.frontmatter.id,
8672
+ sensorKind: memory2.frontmatter.sensor?.kind ?? null
8673
+ }));
8674
+ return assessScaffoldLoop(files, memories);
8675
+ }
8676
+ function describeScaffoldGap(gap) {
8677
+ const state = gap.memory_missing ? "lesson deleted" : gap.pending && !gap.armed ? "pending, not armed" : gap.pending ? "armed but still pending" : "not armed";
8678
+ return `${gap.path} (${state} \u2014 ${gap.memory_id})`;
8679
+ }
8680
+
8681
+ // src/commands/doctor.ts
8602
8682
  var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
8603
8683
  function registerDoctor(program2) {
8604
8684
  program2.command("doctor").description(
@@ -8609,7 +8689,7 @@ function registerDoctor(program2) {
8609
8689
  const findings = [];
8610
8690
  const repairs = [];
8611
8691
  const config = await loadConfig10(paths);
8612
- if (!existsSync39(paths.haiveDir)) {
8692
+ if (!existsSync40(paths.haiveDir)) {
8613
8693
  if (opts.json) {
8614
8694
  console.log(JSON.stringify({
8615
8695
  initialized: false,
@@ -8634,7 +8714,7 @@ function registerDoctor(program2) {
8634
8714
  })
8635
8715
  );
8636
8716
  }
8637
- if (!existsSync39(paths.projectContext)) {
8717
+ if (!existsSync40(paths.projectContext)) {
8638
8718
  findings.push({
8639
8719
  severity: "warn",
8640
8720
  code: "no-project-context",
@@ -8654,7 +8734,7 @@ function registerDoctor(program2) {
8654
8734
  });
8655
8735
  } else {
8656
8736
  const referenced = extractReferencedPaths(content);
8657
- const missing = referenced.filter((p) => !existsSync39(path36.resolve(root, p)));
8737
+ const missing = referenced.filter((p) => !existsSync40(path37.resolve(root, p)));
8658
8738
  const grounded = referenced.length - missing.length;
8659
8739
  if (referenced.length >= 3 && grounded / referenced.length < 0.5) {
8660
8740
  findings.push({
@@ -8676,11 +8756,11 @@ function registerDoctor(program2) {
8676
8756
  });
8677
8757
  }
8678
8758
  }
8679
- const memoriesDetailed = existsSync39(paths.memoriesDir) ? await loadMemoriesFromDirDetailed(paths.memoriesDir) : { loaded: [], invalid: [] };
8759
+ const memoriesDetailed = existsSync40(paths.memoriesDir) ? await loadMemoriesFromDirDetailed(paths.memoriesDir) : { loaded: [], invalid: [] };
8680
8760
  const memories = memoriesDetailed.loaded;
8681
8761
  const now = Date.now();
8682
8762
  if (memoriesDetailed.invalid.length > 0) {
8683
- const listed = memoriesDetailed.invalid.slice(0, 5).map((f) => `${path36.relative(root, f.filePath)} (${f.error})`).join("; ");
8763
+ const listed = memoriesDetailed.invalid.slice(0, 5).map((f) => `${path37.relative(root, f.filePath)} (${f.error})`).join("; ");
8684
8764
  findings.push({
8685
8765
  severity: "warn",
8686
8766
  code: "invalid-memory-files",
@@ -8821,8 +8901,8 @@ function registerDoctor(program2) {
8821
8901
  const anchorPaths = s.paths.length > 0 ? s.paths : m.memory.frontmatter.anchor.paths;
8822
8902
  const targets = [];
8823
8903
  for (const rel of anchorPaths) {
8824
- const abs = path36.resolve(root, rel);
8825
- if (!existsSync39(abs)) continue;
8904
+ const abs = path37.resolve(root, rel);
8905
+ if (!existsSync40(abs)) continue;
8826
8906
  try {
8827
8907
  targets.push({ path: rel, content: await readFile16(abs, "utf8") });
8828
8908
  } catch {
@@ -8897,6 +8977,18 @@ function registerDoctor(program2) {
8897
8977
  }
8898
8978
  findings.push(...await collectHarnessCoverageFindings(codeMap, memories));
8899
8979
  findings.push(...await collectSemanticIndexFindings(paths, config, memories.length, codeMap));
8980
+ try {
8981
+ const scaffoldGaps = await collectScaffoldLoopGaps(paths);
8982
+ if (scaffoldGaps.length > 0) {
8983
+ findings.push({
8984
+ severity: "warn",
8985
+ code: "post-incident-test-unarmed",
8986
+ message: `${scaffoldGaps.length} post-incident test(s) are scaffolded but not armed as gates \u2014 the incident is documented, nothing deterministic guards it yet: ` + scaffoldGaps.slice(0, 5).map(describeScaffoldGap).join(", ") + (scaffoldGaps.length > 5 ? ", \u2026" : "") + ".",
8987
+ fix: "Fill the pending assertion, run it, then arm it with the `hivelore sensors propose --kind test` command in the scaffold header."
8988
+ });
8989
+ }
8990
+ } catch {
8991
+ }
8900
8992
  const events = await readUsageEvents3(paths);
8901
8993
  if (events.length === 0) {
8902
8994
  findings.push({
@@ -8933,9 +9025,9 @@ function registerDoctor(program2) {
8933
9025
  }
8934
9026
  }
8935
9027
  if (config.enforcement?.requireBriefingFirst) {
8936
- const claudeSettings = path36.join(root, ".claude", "settings.local.json");
9028
+ const claudeSettings = path37.join(root, ".claude", "settings.local.json");
8937
9029
  let hasClaudeEnforcement = false;
8938
- if (existsSync39(claudeSettings)) {
9030
+ if (existsSync40(claudeSettings)) {
8939
9031
  try {
8940
9032
  const { readFile: readFile24 } = await import("fs/promises");
8941
9033
  const raw = await readFile24(claudeSettings, "utf8");
@@ -8961,7 +9053,7 @@ function registerDoctor(program2) {
8961
9053
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `hivelore init` without --manual)."
8962
9054
  });
8963
9055
  }
8964
- findings.push(...await collectInstallFindings(root, "0.38.0"));
9056
+ findings.push(...await collectInstallFindings(root, "0.39.1"));
8965
9057
  findings.push(...await collectToolchainFindings(root));
8966
9058
  try {
8967
9059
  const legacyRaw = execSync("haive-mcp --version", {
@@ -8969,7 +9061,7 @@ function registerDoctor(program2) {
8969
9061
  timeout: 3e3,
8970
9062
  stdio: ["ignore", "pipe", "ignore"]
8971
9063
  }).trim();
8972
- const cliVersion = "0.38.0";
9064
+ const cliVersion = "0.39.1";
8973
9065
  if (legacyRaw && legacyRaw !== cliVersion) {
8974
9066
  findings.push({
8975
9067
  severity: "warn",
@@ -8985,17 +9077,17 @@ npm uninstall -g @hivelore/mcp`
8985
9077
  }
8986
9078
  {
8987
9079
  const configPaths = [
8988
- path36.join(root, ".mcp.json"),
8989
- path36.join(root, ".cursor", "mcp.json"),
8990
- path36.join(root, ".vscode", "mcp.json")
9080
+ path37.join(root, ".mcp.json"),
9081
+ path37.join(root, ".cursor", "mcp.json"),
9082
+ path37.join(root, ".vscode", "mcp.json")
8991
9083
  ];
8992
9084
  const staleConfigs = [];
8993
9085
  for (const cfgPath of configPaths) {
8994
- if (!existsSync39(cfgPath)) continue;
9086
+ if (!existsSync40(cfgPath)) continue;
8995
9087
  try {
8996
9088
  const raw = await readFile16(cfgPath, "utf8");
8997
9089
  if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
8998
- staleConfigs.push(path36.relative(root, cfgPath));
9090
+ staleConfigs.push(path37.relative(root, cfgPath));
8999
9091
  if (opts.fix && !opts.dryRun) {
9000
9092
  const updated = raw.replace(/"command"\s*:\s*"haive-mcp"/g, '"command": "hivelore"').replace(/"args"\s*:\s*\[\]/g, '"args": ["mcp", "--stdio"]');
9001
9093
  await writeFile23(cfgPath, updated, "utf8");
@@ -9087,8 +9179,11 @@ function emit(findings, opts, repairs = []) {
9087
9179
  }
9088
9180
  const actions = nextActions(classified);
9089
9181
  if (actions.length > 0) {
9090
- console.log(ui.bold("Next actions"));
9091
- for (const action of actions.slice(0, 5)) console.log(` ${ui.dim("$")} ${action}`);
9182
+ console.log(ui.bold("Suggested commands"));
9183
+ for (const action of actions.slice(0, 5)) {
9184
+ const isCommand = /^(hivelore|haive|git|npm|pnpm|npx|node|gh|rm|code|cd)\b/.test(action);
9185
+ console.log(` ${ui.dim(isCommand ? "$" : "\u2192")} ${action}`);
9186
+ }
9092
9187
  } else if (!opts.fix && classified.some((f) => f.fix)) {
9093
9188
  ui.info("Re-run with --fix to see suggested commands.");
9094
9189
  }
@@ -9288,8 +9383,8 @@ which -a hivelore haive`
9288
9383
  const missingBins = /* @__PURE__ */ new Map();
9289
9384
  const staleBins = /* @__PURE__ */ new Map();
9290
9385
  for (const rel of integrationFiles) {
9291
- const file = path36.join(root, rel);
9292
- if (!existsSync39(file)) continue;
9386
+ const file = path37.join(root, rel);
9387
+ if (!existsSync40(file)) continue;
9293
9388
  const text = await readFile16(file, "utf8").catch(() => "");
9294
9389
  for (const bin of extractAbsoluteHaiveBins(text)) {
9295
9390
  const version = versionForBinary(bin);
@@ -9322,7 +9417,7 @@ which -a hivelore haive`
9322
9417
  async function collectToolchainFindings(root) {
9323
9418
  const findings = [];
9324
9419
  const pkg = await readJson(
9325
- path36.join(root, "package.json")
9420
+ path37.join(root, "package.json")
9326
9421
  );
9327
9422
  const wantsPnpm = pkg?.packageManager?.startsWith("pnpm@") || Object.values(pkg?.scripts ?? {}).some((script) => /\bpnpm\b/.test(script));
9328
9423
  if (!wantsPnpm) return findings;
@@ -9341,10 +9436,10 @@ async function collectToolchainFindings(root) {
9341
9436
  }
9342
9437
  async function collectDistFreshnessFindings(root, expectedVersion) {
9343
9438
  const findings = [];
9344
- const isHaiveWorkspace = ["hivelore-monorepo", "haive-monorepo"].includes((await readJson(path36.join(root, "package.json")))?.name ?? "");
9439
+ const isHaiveWorkspace = ["hivelore-monorepo", "haive-monorepo"].includes((await readJson(path37.join(root, "package.json")))?.name ?? "");
9345
9440
  if (!isHaiveWorkspace) return findings;
9346
- const cliDist = path36.join(root, "packages/cli/dist/index.js");
9347
- if (!existsSync39(cliDist)) {
9441
+ const cliDist = path37.join(root, "packages/cli/dist/index.js");
9442
+ if (!existsSync40(cliDist)) {
9348
9443
  findings.push({
9349
9444
  severity: "warn",
9350
9445
  code: "workspace-dist-missing",
@@ -9368,10 +9463,10 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
9368
9463
  "packages/core/src/index.ts",
9369
9464
  "packages/mcp/src/server.ts",
9370
9465
  "packages/cli/src/index.ts"
9371
- ].map((rel) => path36.join(root, rel)).filter(existsSync39);
9466
+ ].map((rel) => path37.join(root, rel)).filter(existsSync40);
9372
9467
  if (sourceFiles.length > 0) {
9373
- const distMtime = statSync2(cliDist).mtimeMs;
9374
- const newestSource = Math.max(...sourceFiles.map((file) => statSync2(file).mtimeMs));
9468
+ const distMtime = statSync3(cliDist).mtimeMs;
9469
+ const newestSource = Math.max(...sourceFiles.map((file) => statSync3(file).mtimeMs));
9375
9470
  if (newestSource > distMtime + 1e3) {
9376
9471
  findings.push({
9377
9472
  severity: "info",
@@ -9386,7 +9481,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
9386
9481
  }
9387
9482
  async function collectWorkspaceVersionFindings(root, expectedVersion) {
9388
9483
  const findings = [];
9389
- const rootPkg = await readJson(path36.join(root, "package.json"));
9484
+ const rootPkg = await readJson(path37.join(root, "package.json"));
9390
9485
  const workspacePackages = [
9391
9486
  "packages/core/package.json",
9392
9487
  "packages/embeddings/package.json",
@@ -9395,7 +9490,7 @@ async function collectWorkspaceVersionFindings(root, expectedVersion) {
9395
9490
  ];
9396
9491
  const existing = (await Promise.all(workspacePackages.map(async (rel) => ({
9397
9492
  rel,
9398
- pkg: await readJson(path36.join(root, rel))
9493
+ pkg: await readJson(path37.join(root, rel))
9399
9494
  })))).filter((item) => item.pkg);
9400
9495
  const isHaiveWorkspace = rootPkg?.name === "hivelore-monorepo" || rootPkg?.name === "haive-monorepo" || existing.some((item) => item.pkg?.name?.startsWith("@hivelore/"));
9401
9496
  if (!isHaiveWorkspace) return findings;
@@ -9457,7 +9552,7 @@ function collectGlobalHivemoduleFindings(expectedVersion) {
9457
9552
  }
9458
9553
  }
9459
9554
  async function readJson(file) {
9460
- if (!existsSync39(file)) return null;
9555
+ if (!existsSync40(file)) return null;
9461
9556
  try {
9462
9557
  return JSON.parse(await readFile16(file, "utf8"));
9463
9558
  } catch {
@@ -9522,7 +9617,7 @@ function extractAbsoluteHaiveBins(text) {
9522
9617
  const p = match[2];
9523
9618
  if (!p) continue;
9524
9619
  try {
9525
- if (statSync2(p).isDirectory()) continue;
9620
+ if (statSync3(p).isDirectory()) continue;
9526
9621
  } catch {
9527
9622
  }
9528
9623
  out.add(p);
@@ -9686,23 +9781,23 @@ function runCommand(cmd, args, cwd) {
9686
9781
  }
9687
9782
 
9688
9783
  // src/commands/resolve-project.ts
9689
- import path37 from "path";
9784
+ import path38 from "path";
9690
9785
  import "commander";
9691
9786
  import { resolveProjectInfo } from "@hivelore/core";
9692
9787
  function registerResolveProject(program2) {
9693
9788
  program2.command("resolve-project").description(
9694
9789
  "Print JSON for Hivelore project root resolution (HAIVE_PROJECT_ROOT, markers, .ai layout)."
9695
9790
  ).option("-d, --dir <dir>", "working directory", process.cwd()).action((opts) => {
9696
- const info = resolveProjectInfo({ cwd: path37.resolve(opts.dir) });
9791
+ const info = resolveProjectInfo({ cwd: path38.resolve(opts.dir) });
9697
9792
  console.log(JSON.stringify({ ok: true, info }, null, 2));
9698
9793
  });
9699
9794
  }
9700
9795
 
9701
9796
  // src/commands/enforce.ts
9702
9797
  import { execFile as execFile5, execFileSync as execFileSync2, spawn as spawn4 } from "child_process";
9703
- import { existsSync as existsSync41, statSync as statSync3 } from "fs";
9798
+ import { existsSync as existsSync42, statSync as statSync4 } from "fs";
9704
9799
  import { chmod, mkdir as mkdir16, readFile as readFile18, readdir as readdir4, rm as rm2, writeFile as writeFile25 } from "fs/promises";
9705
- import path39 from "path";
9800
+ import path40 from "path";
9706
9801
  import { promisify as promisify5 } from "util";
9707
9802
  import "commander";
9708
9803
  import {
@@ -9711,6 +9806,7 @@ import {
9711
9806
  assessSensorHealth as assessSensorHealth3,
9712
9807
  sensorPromotedAtMap as sensorPromotedAtMap3,
9713
9808
  assessBootstrapState,
9809
+ detectSensorWeakening,
9714
9810
  isSensorScannablePath,
9715
9811
  findProjectRoot as findProjectRoot37,
9716
9812
  loadCodeMap as loadCodeMap7,
@@ -9722,7 +9818,7 @@ import {
9722
9818
  isRetiredMemory as isRetiredMemory2,
9723
9819
  loadConfig as loadConfig12,
9724
9820
  detectAgentContext,
9725
- loadMemoriesFromDir as loadMemoriesFromDir14,
9821
+ loadMemoriesFromDir as loadMemoriesFromDir15,
9726
9822
  loadSensorLedger as loadSensorLedger3,
9727
9823
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
9728
9824
  readRecentBriefingMarker,
@@ -9741,9 +9837,9 @@ import {
9741
9837
  } from "@hivelore/core";
9742
9838
 
9743
9839
  // src/utils/claude-hooks.ts
9744
- import { existsSync as existsSync40 } from "fs";
9840
+ import { existsSync as existsSync41 } from "fs";
9745
9841
  import { mkdir as mkdir15, readFile as readFile17, writeFile as writeFile24 } from "fs/promises";
9746
- import path38 from "path";
9842
+ import path39 from "path";
9747
9843
  var HAIVE_HOOK_TAG = "haive-enforcement";
9748
9844
  var POST_TOOL_USE_GROUP = {
9749
9845
  matcher: "Edit|Write|Bash",
@@ -9829,7 +9925,7 @@ function unpatchClaudeSettings(input) {
9829
9925
  async function installClaudeHooksAtPath(settingsPath) {
9830
9926
  let raw = null;
9831
9927
  let created = false;
9832
- if (existsSync40(settingsPath)) {
9928
+ if (existsSync41(settingsPath)) {
9833
9929
  try {
9834
9930
  raw = JSON.parse(await readFile17(settingsPath, "utf8"));
9835
9931
  } catch {
@@ -9839,12 +9935,12 @@ async function installClaudeHooksAtPath(settingsPath) {
9839
9935
  created = true;
9840
9936
  }
9841
9937
  const patched = patchClaudeSettings(raw);
9842
- await mkdir15(path38.dirname(settingsPath), { recursive: true });
9938
+ await mkdir15(path39.dirname(settingsPath), { recursive: true });
9843
9939
  await writeFile24(settingsPath, JSON.stringify(patched, null, 2) + "\n", "utf8");
9844
9940
  return { settingsPath, created };
9845
9941
  }
9846
9942
  async function uninstallClaudeHooksAtPath(settingsPath) {
9847
- if (!existsSync40(settingsPath)) {
9943
+ if (!existsSync41(settingsPath)) {
9848
9944
  return { settingsPath, created: false };
9849
9945
  }
9850
9946
  const raw = JSON.parse(await readFile17(settingsPath, "utf8"));
@@ -9855,9 +9951,9 @@ async function uninstallClaudeHooksAtPath(settingsPath) {
9855
9951
  function defaultClaudeSettingsPath(scope, projectRoot) {
9856
9952
  if (scope === "user") {
9857
9953
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
9858
- return path38.join(home, ".claude", "settings.json");
9954
+ return path39.join(home, ".claude", "settings.json");
9859
9955
  }
9860
- return path38.join(projectRoot, ".claude", "settings.local.json");
9956
+ return path39.join(projectRoot, ".claude", "settings.local.json");
9861
9957
  }
9862
9958
 
9863
9959
  // src/utils/command-sensors.ts
@@ -10044,7 +10140,7 @@ function registerEnforce(program2) {
10044
10140
  ui.success(`Removed Hivelore hooks from ${result.settingsPath}`);
10045
10141
  } else {
10046
10142
  const result = await installClaudeHooksAtPath(settingsPath);
10047
- ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path39.relative(root, result.settingsPath) || result.settingsPath})`);
10143
+ ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path40.relative(root, result.settingsPath) || result.settingsPath})`);
10048
10144
  }
10049
10145
  } catch (err) {
10050
10146
  ui.warn(`Claude Code hooks not ${opts.removeClaude ? "removed" : "installed"}: ${err instanceof Error ? err.message : String(err)}`);
@@ -10066,19 +10162,19 @@ function registerEnforce(program2) {
10066
10162
  enforce.command("cleanup").description("Remove generated Hivelore 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) => {
10067
10163
  const root = findProjectRoot37(opts.dir);
10068
10164
  const paths = resolveHaivePaths35(root);
10069
- const cacheDir = path39.join(paths.haiveDir, ".cache");
10070
- if (existsSync41(cacheDir)) {
10071
- if (opts.dryRun) ui.info(`would clean ${path39.relative(root, cacheDir)} (preserving .gitignore)`);
10165
+ const cacheDir = path40.join(paths.haiveDir, ".cache");
10166
+ if (existsSync42(cacheDir)) {
10167
+ if (opts.dryRun) ui.info(`would clean ${path40.relative(root, cacheDir)} (preserving .gitignore)`);
10072
10168
  else {
10073
10169
  const removed = await cleanupCacheDir(cacheDir);
10074
- ui.success(`cleaned ${path39.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
10170
+ ui.success(`cleaned ${path40.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
10075
10171
  }
10076
10172
  }
10077
- if (existsSync41(paths.runtimeDir)) {
10078
- if (opts.dryRun) ui.info(`would clean ${path39.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
10173
+ if (existsSync42(paths.runtimeDir)) {
10174
+ if (opts.dryRun) ui.info(`would clean ${path40.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
10079
10175
  else {
10080
10176
  const removed = await cleanupRuntimeDir(paths.runtimeDir);
10081
- ui.success(`cleaned ${path39.relative(root, paths.runtimeDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
10177
+ ui.success(`cleaned ${path40.relative(root, paths.runtimeDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
10082
10178
  }
10083
10179
  }
10084
10180
  });
@@ -10122,7 +10218,7 @@ function registerEnforce(program2) {
10122
10218
  const root = resolveRoot(opts.dir, payload);
10123
10219
  if (!root) return;
10124
10220
  const paths = resolveHaivePaths35(root);
10125
- if (!existsSync41(paths.haiveDir)) return;
10221
+ if (!existsSync42(paths.haiveDir)) return;
10126
10222
  await mkdir16(paths.runtimeDir, { recursive: true });
10127
10223
  const sessionId = opts.sessionId ?? payload.session_id;
10128
10224
  const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this Hivelore-initialized project.";
@@ -10185,7 +10281,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
10185
10281
  const root = resolveRoot(opts.dir, payload);
10186
10282
  if (!root) return;
10187
10283
  const paths = resolveHaivePaths35(root);
10188
- if (!existsSync41(paths.haiveDir)) return;
10284
+ if (!existsSync42(paths.haiveDir)) return;
10189
10285
  if (!isWriteLikeTool(payload)) return;
10190
10286
  const config = await loadConfig12(paths);
10191
10287
  if (config.enforcement?.requireBriefingFirst === false) return;
@@ -10247,10 +10343,26 @@ function emitPreToolUseContext(text) {
10247
10343
  })
10248
10344
  );
10249
10345
  }
10346
+ async function checkPostIncidentScaffolds(paths) {
10347
+ try {
10348
+ const gaps = await collectScaffoldLoopGaps(paths);
10349
+ if (gaps.length === 0) return [];
10350
+ return [{
10351
+ severity: "warn",
10352
+ code: "post-incident-test-unarmed",
10353
+ message: `${gaps.length} post-incident test(s) are scaffolded but not yet armed as gates: ` + gaps.slice(0, 5).map(describeScaffoldGap).join(", ") + (gaps.length > 5 ? ", \u2026" : "") + ".",
10354
+ fix: "Fill the pending assertion, run the test, then arm it: the scaffold header contains the exact `hivelore sensors propose --kind test` command.",
10355
+ memory_ids: [...new Set(gaps.map((g) => g.memory_id))].slice(0, 10),
10356
+ impact: 0
10357
+ }];
10358
+ } catch {
10359
+ return [];
10360
+ }
10361
+ }
10250
10362
  async function buildFinishReport(dir) {
10251
10363
  const root = findProjectRoot37(dir);
10252
10364
  const paths = resolveHaivePaths35(root);
10253
- const initialized = existsSync41(paths.haiveDir);
10365
+ const initialized = existsSync42(paths.haiveDir);
10254
10366
  const config = initialized ? await loadConfig12(paths) : {};
10255
10367
  const mode = config.enforcement?.mode ?? "strict";
10256
10368
  const findings = [];
@@ -10271,6 +10383,7 @@ async function buildFinishReport(dir) {
10271
10383
  });
10272
10384
  }
10273
10385
  findings.push(...await checkFailureCapture(paths, config));
10386
+ findings.push(...await checkPostIncidentScaffolds(paths));
10274
10387
  findings.push(...await checkBootstrapComplete(paths, config, true));
10275
10388
  const status = await getGitSyncStatus(root);
10276
10389
  if (!status.available) {
@@ -10442,8 +10555,8 @@ async function buildFinishReport(dir) {
10442
10555
  async function checkFailureCapture(paths, config) {
10443
10556
  const gate = config.enforcement?.failureCaptureGate ?? "warn";
10444
10557
  if (gate === "off") return [];
10445
- const obsFile = path39.join(paths.haiveDir, ".cache", "observations.jsonl");
10446
- if (!existsSync41(obsFile)) return [];
10558
+ const obsFile = path40.join(paths.haiveDir, ".cache", "observations.jsonl");
10559
+ if (!existsSync42(obsFile)) return [];
10447
10560
  const failures = [];
10448
10561
  try {
10449
10562
  const raw = await readFile18(obsFile, "utf8");
@@ -10460,7 +10573,7 @@ async function checkFailureCapture(paths, config) {
10460
10573
  return [];
10461
10574
  }
10462
10575
  if (failures.length === 0) return [];
10463
- const memories = existsSync41(paths.memoriesDir) ? await loadMemoriesFromDir14(paths.memoriesDir) : [];
10576
+ const memories = existsSync42(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
10464
10577
  const captureTimes = memories.filter(({ memory: memory2 }) => ["attempt", "gotcha"].includes(memory2.frontmatter.type)).map(({ memory: memory2 }) => memory2.frontmatter.created_at);
10465
10578
  const uncaptured = findUncapturedFailures(failures, captureTimes);
10466
10579
  if (uncaptured.length === 0) {
@@ -10495,7 +10608,7 @@ function finishReport(root, initialized, mode, findings, config) {
10495
10608
  async function runWithEnforcement(command, args, opts) {
10496
10609
  const root = findProjectRoot37(opts.dir);
10497
10610
  const paths = resolveHaivePaths35(root);
10498
- if (!existsSync41(paths.haiveDir)) {
10611
+ if (!existsSync42(paths.haiveDir)) {
10499
10612
  ui.error(`No .ai/ found at ${root}. Run \`hivelore init\` first.`);
10500
10613
  process.exit(1);
10501
10614
  }
@@ -10514,7 +10627,7 @@ async function runWithEnforcement(command, args, opts) {
10514
10627
  process.exit(2);
10515
10628
  }
10516
10629
  ui.info(`Hivelore briefing marker created for wrapped agent session: ${sessionId}`);
10517
- ui.info(`Briefing written to ${path39.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
10630
+ ui.info(`Briefing written to ${path40.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
10518
10631
  const child = spawn4(command, args, {
10519
10632
  cwd: root,
10520
10633
  stdio: "inherit",
@@ -10566,9 +10679,9 @@ async function writeWrapperBriefing(paths, sessionId, task) {
10566
10679
  source: "haive-run",
10567
10680
  memoryIds: briefing.memories.map((m) => m.id)
10568
10681
  });
10569
- const dir = path39.join(paths.runtimeDir, "enforcement", "briefings");
10682
+ const dir = path40.join(paths.runtimeDir, "enforcement", "briefings");
10570
10683
  await mkdir16(dir, { recursive: true });
10571
- const file = path39.join(dir, `${sessionId}.md`);
10684
+ const file = path40.join(dir, `${sessionId}.md`);
10572
10685
  const parts = [
10573
10686
  "# Hivelore Briefing",
10574
10687
  "",
@@ -10606,7 +10719,7 @@ async function checkBootstrapComplete(paths, config, productionCodeChanged) {
10606
10719
  projectContextRaw = await readFile18(paths.projectContext, "utf8");
10607
10720
  } catch {
10608
10721
  }
10609
- const memories = existsSync41(paths.memoriesDir) ? await loadMemoriesFromDir14(paths.memoriesDir) : [];
10722
+ const memories = existsSync42(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
10610
10723
  const codeMap = await loadCodeMap7(paths);
10611
10724
  const codeFiles = codeMap ? Object.keys(codeMap.files) : [];
10612
10725
  let existingModules = [];
@@ -10638,7 +10751,7 @@ ${renderBootstrapChecklist(assessment)}`,
10638
10751
  async function buildEnforcementReport(dir, stage, sessionId) {
10639
10752
  const root = findProjectRoot37(dir);
10640
10753
  const paths = resolveHaivePaths35(root);
10641
- const initialized = existsSync41(paths.haiveDir);
10754
+ const initialized = existsSync42(paths.haiveDir);
10642
10755
  const config = initialized ? await loadConfig12(paths) : {};
10643
10756
  if (initialized) {
10644
10757
  await applyLightweightRepairs(root, paths);
@@ -10672,7 +10785,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
10672
10785
  findings: [{ severity: "info", code: "enforcement-off", message: "Hivelore enforcement is disabled." }]
10673
10786
  });
10674
10787
  }
10675
- findings.push(...await inspectIntegrationVersions(root, "0.38.0"));
10788
+ findings.push(...await inspectIntegrationVersions(root, "0.39.1"));
10676
10789
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
10677
10790
  const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
10678
10791
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent Hivelore briefing marker exists." } : {
@@ -10743,10 +10856,14 @@ async function buildEnforcementReport(dir, stage, sessionId) {
10743
10856
  }
10744
10857
  const score = buildScore(effectiveFindings, config.enforcement?.scoreThreshold);
10745
10858
  if (score.score < score.threshold) {
10859
+ const topPenalties = effectiveFindings.map((f) => ({
10860
+ code: f.code,
10861
+ penalty: f.severity === "error" ? f.impact ?? 25 : f.severity === "warn" ? f.impact ?? 8 : 0
10862
+ })).filter((p) => p.penalty > 0).sort((a, b) => b.penalty - a.penalty).slice(0, 3);
10746
10863
  effectiveFindings = [...effectiveFindings, {
10747
10864
  severity: "error",
10748
10865
  code: "enforcement-score-below-threshold",
10749
- message: `Enforcement score ${score.score}% is below required threshold ${score.threshold}%.`,
10866
+ message: `Enforcement score ${score.score}% is below required threshold ${score.threshold}%` + (topPenalties.length > 0 ? ` \u2014 top penalties: ${topPenalties.map((p) => `${p.code} (\u2212${p.penalty})`).join(", ")}` : "") + ".",
10750
10867
  fix: "Load the relevant briefing, address policy findings, then rerun `hivelore enforce check`.",
10751
10868
  impact: 0
10752
10869
  }];
@@ -10787,8 +10904,8 @@ function withCategories(report) {
10787
10904
  async function hasRecentSessionRecap(paths) {
10788
10905
  const handoffAge = await handoffAgeMs(paths.root);
10789
10906
  if (handoffAge !== null && handoffAge < SESSION_RECAP_TTL_MS) return true;
10790
- if (!existsSync41(paths.memoriesDir)) return false;
10791
- const all = await loadMemoriesFromDir14(paths.memoriesDir);
10907
+ if (!existsSync42(paths.memoriesDir)) return false;
10908
+ const all = await loadMemoriesFromDir15(paths.memoriesDir);
10792
10909
  return all.some(({ memory: memory2 }) => {
10793
10910
  const fm = memory2.frontmatter;
10794
10911
  const freshnessDate = fm.verified_at ?? fm.created_at;
@@ -10796,8 +10913,8 @@ async function hasRecentSessionRecap(paths) {
10796
10913
  });
10797
10914
  }
10798
10915
  async function verifyMemoryPolicy(paths, config) {
10799
- if (!existsSync41(paths.memoriesDir)) return [];
10800
- const all = await loadMemoriesFromDir14(paths.memoriesDir);
10916
+ if (!existsSync42(paths.memoriesDir)) return [];
10917
+ const all = await loadMemoriesFromDir15(paths.memoriesDir);
10801
10918
  const findings = [];
10802
10919
  const staleImportant = [];
10803
10920
  let verified = 0;
@@ -10834,12 +10951,12 @@ async function verifyMemoryPolicy(paths, config) {
10834
10951
  return findings;
10835
10952
  }
10836
10953
  async function verifyDecisionCoverage(paths, stage, sessionId) {
10837
- if (!existsSync41(paths.memoriesDir)) return [];
10954
+ if (!existsSync42(paths.memoriesDir)) return [];
10838
10955
  const changedFiles = (await getChangedFiles(paths.root, stage)).filter((f) => !isGeneratedArtifact(f));
10839
10956
  if (changedFiles.length === 0) {
10840
10957
  return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
10841
10958
  }
10842
- const all = await loadMemoriesFromDir14(paths.memoriesDir);
10959
+ const all = await loadMemoriesFromDir15(paths.memoriesDir);
10843
10960
  const changedSet = new Set(changedFiles);
10844
10961
  const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention"]);
10845
10962
  const relevant = all.filter(({ memory: memory2 }) => {
@@ -10868,7 +10985,7 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
10868
10985
  const consulted = new Set(marker?.memory_ids ?? []);
10869
10986
  const missing = relevant.filter(({ memory: memory2, filePath }) => {
10870
10987
  if (consulted.has(memory2.frontmatter.id)) return false;
10871
- if (changedSet.has(path39.relative(paths.root, filePath))) return false;
10988
+ if (changedSet.has(path40.relative(paths.root, filePath))) return false;
10872
10989
  return true;
10873
10990
  }).map(({ memory: memory2 }) => memory2);
10874
10991
  if (missing.length === 0) {
@@ -10910,15 +11027,27 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
10910
11027
  }];
10911
11028
  }
10912
11029
  async function runPrecommitPolicy(paths, gate, stage) {
11030
+ const snapshot = await getPolicyDiffSnapshot(paths.root, stage);
11031
+ const weakenings = detectSensorWeakening(snapshot.diff);
11032
+ const weakeningFindings = weakenings.length > 0 ? [{
11033
+ severity: "warn",
11034
+ code: "sensor-weakened",
11035
+ message: `This diff weakens the enforcement surface \u2014 ${weakenings.length} sensor change(s) need review: ` + weakenings.slice(0, 5).map((w) => `${w.memory_id} (${w.change}: ${w.detail})`).join(", ") + (weakenings.length > 5 ? ", \u2026" : "") + ".",
11036
+ fix: "If the demotion/removal is intentional, say so in the commit message; otherwise restore the sensor (`hivelore sensors list` shows the current state).",
11037
+ memory_ids: [...new Set(weakenings.map((w) => w.memory_id))].slice(0, 10),
11038
+ impact: 8
11039
+ }] : [];
10913
11040
  if (gate === "off") {
10914
- return [{ severity: "info", code: "precommit-policy-off", message: "Anti-pattern gate is disabled (enforcement.antiPatternGate=off)." }];
11041
+ return [
11042
+ { severity: "info", code: "precommit-policy-off", message: "Anti-pattern gate is disabled (enforcement.antiPatternGate=off)." },
11043
+ ...weakeningFindings
11044
+ ];
10915
11045
  }
10916
- const snapshot = await getPolicyDiffSnapshot(paths.root, stage);
10917
11046
  const touchedPaths = snapshot.paths;
10918
11047
  if (touchedPaths.length === 0) {
10919
11048
  const code = stage === "ci" ? "no-ci-diff-changes" : "no-staged-changes";
10920
11049
  const message = stage === "ci" ? "No changed files found for CI policy diff." : "No staged changes found for pre-commit policy.";
10921
- return [{ severity: "info", code, message }];
11050
+ return [{ severity: "info", code, message }, ...weakeningFindings];
10922
11051
  }
10923
11052
  const { block_on, anchored_blocks } = antiPatternGateParams2(gate);
10924
11053
  const result = await preCommitCheck({
@@ -10933,7 +11062,7 @@ async function runPrecommitPolicy(paths, gate, stage) {
10933
11062
  (w) => w.level === "review" && !w.reasons.includes("sensor")
10934
11063
  );
10935
11064
  const REVIEW_SEEN_TTL_MS = 24 * 60 * 60 * 1e3;
10936
- const reviewSeenFile = path39.join(paths.runtimeDir, "enforcement", "review-seen.json");
11065
+ const reviewSeenFile = path40.join(paths.runtimeDir, "enforcement", "review-seen.json");
10937
11066
  let reviewSeen = {};
10938
11067
  try {
10939
11068
  reviewSeen = JSON.parse(await readFile18(reviewSeenFile, "utf8"));
@@ -10960,7 +11089,7 @@ async function runPrecommitPolicy(paths, gate, stage) {
10960
11089
  for (const [id, at] of Object.entries(reviewSeen)) {
10961
11090
  if (!Number.isFinite(Date.parse(at)) || now - Date.parse(at) >= REVIEW_SEEN_TTL_MS) delete reviewSeen[id];
10962
11091
  }
10963
- await mkdir16(path39.dirname(reviewSeenFile), { recursive: true });
11092
+ await mkdir16(path40.dirname(reviewSeenFile), { recursive: true });
10964
11093
  await writeFile25(reviewSeenFile, JSON.stringify(reviewSeen, null, 2), "utf8");
10965
11094
  } catch {
10966
11095
  }
@@ -10973,7 +11102,8 @@ async function runPrecommitPolicy(paths, gate, stage) {
10973
11102
  message: `${stage === "ci" ? "CI" : "Pre-commit"} policy passed for ${touchedPaths.length} changed file(s).`
10974
11103
  },
10975
11104
  ...reviewFinding,
10976
- ...sensorFindings
11105
+ ...sensorFindings,
11106
+ ...weakeningFindings
10977
11107
  ];
10978
11108
  }
10979
11109
  const blockingWarnings = result.warnings.filter((w) => w.level === "blocking");
@@ -10988,13 +11118,14 @@ async function runPrecommitPolicy(paths, gate, stage) {
10988
11118
  impact: 45
10989
11119
  },
10990
11120
  ...reviewFinding,
10991
- ...sensorFindings
11121
+ ...sensorFindings,
11122
+ ...weakeningFindings
10992
11123
  ];
10993
11124
  }
10994
11125
  async function runSensorGate(paths, diff, stage) {
10995
- if (!diff || !existsSync41(paths.memoriesDir)) return [];
11126
+ if (!diff || !existsSync42(paths.memoriesDir)) return [];
10996
11127
  try {
10997
- const loaded = await loadMemoriesFromDir14(paths.memoriesDir);
11128
+ const loaded = await loadMemoriesFromDir15(paths.memoriesDir);
10998
11129
  const scannable = loaded.map((l) => l.memory).filter((m) => Boolean(m.frontmatter.sensor) && !isRetiredMemory2(m.frontmatter, m.body));
10999
11130
  if (scannable.length === 0) return [];
11000
11131
  const targets = sensorTargetsFromDiff(diff).filter((t) => isSensorScannablePath(t.path));
@@ -11126,8 +11257,14 @@ command: ${run.command} (exit ${run.exit_code}, ${run.duration_ms}ms)${outputBlo
11126
11257
  });
11127
11258
  }
11128
11259
  return findings;
11129
- } catch {
11130
- return [];
11260
+ } catch (err) {
11261
+ return [{
11262
+ severity: "warn",
11263
+ code: "sensor-gate-errored",
11264
+ message: `The sensor gate itself errored, so NO sensors were evaluated on this diff: ` + `${err instanceof Error ? err.message : String(err)}`.slice(0, 400),
11265
+ fix: "Run `hivelore sensors check` to reproduce, and `hivelore doctor` for setup drift. The lessons' protection is OFF until this is fixed.",
11266
+ impact: 5
11267
+ }];
11131
11268
  }
11132
11269
  }
11133
11270
  async function findGeneratedArtifacts(paths) {
@@ -11160,16 +11297,16 @@ async function cleanupRuntimeDir(runtimeDir) {
11160
11297
  for (const entry of entries) {
11161
11298
  if (entry.name === ".gitignore" || entry.name === "README.md") continue;
11162
11299
  if (entry.name === "enforcement") {
11163
- removed += await cleanupEnforcementDir(path39.join(runtimeDir, entry.name));
11300
+ removed += await cleanupEnforcementDir(path40.join(runtimeDir, entry.name));
11164
11301
  continue;
11165
11302
  }
11166
- await rm2(path39.join(runtimeDir, entry.name), { recursive: true, force: true });
11303
+ await rm2(path40.join(runtimeDir, entry.name), { recursive: true, force: true });
11167
11304
  removed++;
11168
11305
  }
11169
- await writeFile25(path39.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
11170
- if (!existsSync41(path39.join(runtimeDir, "README.md"))) {
11306
+ await writeFile25(path40.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
11307
+ if (!existsSync42(path40.join(runtimeDir, "README.md"))) {
11171
11308
  await writeFile25(
11172
- path39.join(runtimeDir, "README.md"),
11309
+ path40.join(runtimeDir, "README.md"),
11173
11310
  "# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. Hivelore cleanup preserves briefing markers so enforcement state remains valid.\n",
11174
11311
  "utf8"
11175
11312
  );
@@ -11182,10 +11319,10 @@ async function cleanupCacheDir(cacheDir) {
11182
11319
  const entries = await readdir4(cacheDir, { withFileTypes: true }).catch(() => []);
11183
11320
  for (const entry of entries) {
11184
11321
  if (entry.name === ".gitignore") continue;
11185
- await rm2(path39.join(cacheDir, entry.name), { recursive: true, force: true });
11322
+ await rm2(path40.join(cacheDir, entry.name), { recursive: true, force: true });
11186
11323
  removed++;
11187
11324
  }
11188
- await writeFile25(path39.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
11325
+ await writeFile25(path40.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
11189
11326
  return removed;
11190
11327
  }
11191
11328
  async function cleanupEnforcementDir(enforcementDir) {
@@ -11193,7 +11330,7 @@ async function cleanupEnforcementDir(enforcementDir) {
11193
11330
  const entries = await readdir4(enforcementDir, { withFileTypes: true }).catch(() => []);
11194
11331
  for (const entry of entries) {
11195
11332
  if (entry.name === "briefings") continue;
11196
- await rm2(path39.join(enforcementDir, entry.name), { recursive: true, force: true });
11333
+ await rm2(path40.join(enforcementDir, entry.name), { recursive: true, force: true });
11197
11334
  removed++;
11198
11335
  }
11199
11336
  return removed;
@@ -11211,8 +11348,8 @@ async function inspectIntegrationVersions(root, expectedVersion) {
11211
11348
  const missingBins = /* @__PURE__ */ new Map();
11212
11349
  const staleBins = /* @__PURE__ */ new Map();
11213
11350
  for (const rel of files) {
11214
- const file = path39.join(root, rel);
11215
- if (!existsSync41(file)) continue;
11351
+ const file = path40.join(root, rel);
11352
+ if (!existsSync42(file)) continue;
11216
11353
  const text = await readFile18(file, "utf8").catch(() => "");
11217
11354
  for (const bin of extractAbsoluteHaiveBins2(text)) {
11218
11355
  const version = versionForBinary2(bin);
@@ -11259,7 +11396,7 @@ function extractAbsoluteHaiveBins2(text) {
11259
11396
  const p = match[2];
11260
11397
  if (!p) continue;
11261
11398
  try {
11262
- if (statSync3(p).isDirectory()) continue;
11399
+ if (statSync4(p).isDirectory()) continue;
11263
11400
  } catch {
11264
11401
  }
11265
11402
  out.add(p);
@@ -11329,7 +11466,7 @@ async function resolveCiDiffRange(root) {
11329
11466
  }
11330
11467
  async function resolveGithubEventRange(root) {
11331
11468
  const eventPath = process.env.GITHUB_EVENT_PATH;
11332
- if (!eventPath || !existsSync41(eventPath)) return null;
11469
+ if (!eventPath || !existsSync42(eventPath)) return null;
11333
11470
  try {
11334
11471
  const event = JSON.parse(await readFile18(eventPath, "utf8"));
11335
11472
  const prBase = cleanGitSha(event.pull_request?.base?.sha);
@@ -11434,7 +11571,7 @@ function isShippablePath(file) {
11434
11571
  }
11435
11572
  var CI_SKIP_DIRECTIVE = /\[skip ci\]|\[ci skip\]|\[no ci\]|\[skip actions\]|\*\*\*NO_CI\*\*\*|skip-checks: *true/i;
11436
11573
  async function checkCommitMessageSkipCi(root, msgfile) {
11437
- const file = path39.isAbsolute(msgfile) ? msgfile : path39.join(root, msgfile);
11574
+ const file = path40.isAbsolute(msgfile) ? msgfile : path40.join(root, msgfile);
11438
11575
  const raw = await readFile18(file, "utf8").catch(() => "");
11439
11576
  const cleaned = raw.split("\n").filter((line) => !line.startsWith("#")).join("\n");
11440
11577
  if (!CI_SKIP_DIRECTIVE.test(cleaned)) return { block: false, message: "" };
@@ -11463,7 +11600,7 @@ async function inspectReleaseVersionState(root, upstream) {
11463
11600
  }
11464
11601
  async function readPackageVersion(root, relPath) {
11465
11602
  try {
11466
- const data = JSON.parse(await readFile18(path39.join(root, relPath), "utf8"));
11603
+ const data = JSON.parse(await readFile18(path40.join(root, relPath), "utf8"));
11467
11604
  return typeof data.version === "string" ? data.version : void 0;
11468
11605
  } catch {
11469
11606
  return void 0;
@@ -11651,8 +11788,8 @@ function buildScore(findings, threshold = 80) {
11651
11788
  };
11652
11789
  }
11653
11790
  async function installGitEnforcement(root) {
11654
- const hooksDir = path39.join(root, ".git", "hooks");
11655
- if (!existsSync41(path39.join(root, ".git"))) {
11791
+ const hooksDir = path40.join(root, ".git", "hooks");
11792
+ if (!existsSync42(path40.join(root, ".git"))) {
11656
11793
  ui.warn("No .git directory found; git enforcement hooks skipped.");
11657
11794
  return;
11658
11795
  }
@@ -11708,8 +11845,8 @@ _hivelore sync --quiet --since ORIG_HEAD || true
11708
11845
  }
11709
11846
  ];
11710
11847
  for (const hook of hooks) {
11711
- const file = path39.join(hooksDir, hook.name);
11712
- if (existsSync41(file)) {
11848
+ const file = path40.join(hooksDir, hook.name);
11849
+ if (existsSync42(file)) {
11713
11850
  const current = await readFile18(file, "utf8").catch(() => "");
11714
11851
  if (current.includes(ENFORCE_HOOK_MARKER)) {
11715
11852
  await writeFile25(file, hook.body, "utf8");
@@ -11726,10 +11863,10 @@ ${hook.body}`, "utf8");
11726
11863
  ui.success("Installed git hooks: pre-commit, pre-push, commit-msg (blocking) + post-merge, post-rewrite (sync)");
11727
11864
  }
11728
11865
  async function installCiEnforcement(root) {
11729
- const workflowPath = path39.join(root, ".github", "workflows", "haive-enforcement.yml");
11730
- await mkdir16(path39.dirname(workflowPath), { recursive: true });
11866
+ const workflowPath = path40.join(root, ".github", "workflows", "haive-enforcement.yml");
11867
+ await mkdir16(path40.dirname(workflowPath), { recursive: true });
11731
11868
  const workflow = renderCiEnforcementWorkflow();
11732
- if (existsSync41(workflowPath)) {
11869
+ if (existsSync42(workflowPath)) {
11733
11870
  const existing = await readFile18(workflowPath, "utf8");
11734
11871
  const start = "# haive:enforcement-workflow:start";
11735
11872
  const end = "# haive:enforcement-workflow:end";
@@ -11737,14 +11874,14 @@ async function installCiEnforcement(root) {
11737
11874
  const endAt = existing.indexOf(end);
11738
11875
  if (startAt >= 0 && endAt > startAt) {
11739
11876
  await writeFile25(workflowPath, existing.slice(0, startAt) + workflow + existing.slice(endAt + end.length), "utf8");
11740
- ui.success(`Updated ${path39.relative(root, workflowPath)} managed block`);
11877
+ ui.success(`Updated ${path40.relative(root, workflowPath)} managed block`);
11741
11878
  } else {
11742
11879
  ui.info("GitHub Actions enforcement workflow already exists without Hivelore markers \u2014 preserved");
11743
11880
  }
11744
11881
  return;
11745
11882
  }
11746
11883
  await writeFile25(workflowPath, workflow, "utf8");
11747
- ui.success(`Created ${path39.relative(root, workflowPath)}`);
11884
+ ui.success(`Created ${path40.relative(root, workflowPath)}`);
11748
11885
  }
11749
11886
  function renderCiEnforcementWorkflow() {
11750
11887
  return `# haive:enforcement-workflow:start
@@ -11947,15 +12084,15 @@ function extractToolPaths(payload, root) {
11947
12084
  }
11948
12085
  function normalizeToolPath(file, root) {
11949
12086
  const normalized = file.replace(/\\/g, "/");
11950
- if (!path39.isAbsolute(normalized)) return normalized.replace(/^\.\//, "");
11951
- return path39.relative(root, normalized).replace(/\\/g, "/");
12087
+ if (!path40.isAbsolute(normalized)) return normalized.replace(/^\.\//, "");
12088
+ return path40.relative(root, normalized).replace(/\\/g, "/");
11952
12089
  }
11953
12090
  async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
11954
- if (!existsSync41(paths.memoriesDir)) return [];
12091
+ if (!existsSync42(paths.memoriesDir)) return [];
11955
12092
  const marker = await readRecentBriefingMarker(paths, sessionId);
11956
12093
  const consulted = new Set(marker?.memory_ids ?? []);
11957
12094
  const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention", "attempt"]);
11958
- const all = await loadMemoriesFromDir14(paths.memoriesDir);
12095
+ const all = await loadMemoriesFromDir15(paths.memoriesDir);
11959
12096
  return all.filter(({ memory: memory2 }) => {
11960
12097
  const fm = memory2.frontmatter;
11961
12098
  if (!policyTypes.has(fm.type)) return false;
@@ -11996,7 +12133,7 @@ async function readStdin2(maxBytes) {
11996
12133
  }
11997
12134
  var ATOMIC_STAGE_EXCLUDE = ["/.usage/", "/.runtime/", "/.cache/"];
11998
12135
  async function stageResyncedArtifacts(root, paths) {
11999
- const aiRel = path39.relative(root, paths.haiveDir);
12136
+ const aiRel = path40.relative(root, paths.haiveDir);
12000
12137
  const out = await runCommand2("git", ["diff", "--name-only", "--", aiRel], root).catch(() => "");
12001
12138
  const toStage = out.split("\n").map((line) => line.trim()).filter(Boolean).filter((file) => !ATOMIC_STAGE_EXCLUDE.some((excl) => `/${file}`.includes(excl)));
12002
12139
  if (toStage.length === 0) return;
@@ -12023,9 +12160,9 @@ function runCommand2(cmd, args, cwd) {
12023
12160
  }
12024
12161
 
12025
12162
  // src/commands/release.ts
12026
- import { existsSync as existsSync42 } from "fs";
12163
+ import { existsSync as existsSync43 } from "fs";
12027
12164
  import { readFile as readFile19, writeFile as writeFile26 } from "fs/promises";
12028
- import path40 from "path";
12165
+ import path41 from "path";
12029
12166
  import "commander";
12030
12167
  import { findProjectRoot as findProjectRoot38 } from "@hivelore/core";
12031
12168
  import { execFile as execFile6 } from "child_process";
@@ -12039,7 +12176,7 @@ var VERSION_FILES2 = [
12039
12176
  "packages/embeddings/package.json"
12040
12177
  ];
12041
12178
  async function readCurrentVersion(root) {
12042
- const pkg = JSON.parse(await readFile19(path40.join(root, "package.json"), "utf8"));
12179
+ const pkg = JSON.parse(await readFile19(path41.join(root, "package.json"), "utf8"));
12043
12180
  if (!pkg.version) throw new Error("Root package.json has no version field.");
12044
12181
  return pkg.version;
12045
12182
  }
@@ -12060,8 +12197,8 @@ function registerRelease(program2) {
12060
12197
  const current = await readCurrentVersion(root);
12061
12198
  const next = nextVersion(current, spec);
12062
12199
  for (const rel of VERSION_FILES2) {
12063
- const file = path40.join(root, rel);
12064
- if (!existsSync42(file)) {
12200
+ const file = path41.join(root, rel);
12201
+ if (!existsSync43(file)) {
12065
12202
  ui.warn(`skip ${rel} (missing)`);
12066
12203
  continue;
12067
12204
  }
@@ -12074,8 +12211,8 @@ function registerRelease(program2) {
12074
12211
  }
12075
12212
  await writeFile26(file, updated, "utf8");
12076
12213
  }
12077
- const changelog = path40.join(root, "CHANGELOG.md");
12078
- if (existsSync42(changelog)) {
12214
+ const changelog = path41.join(root, "CHANGELOG.md");
12215
+ if (existsSync43(changelog)) {
12079
12216
  const raw = await readFile19(changelog, "utf8");
12080
12217
  const heading = `## [${next}]${opts.title ? ` \u2014 ${opts.title}` : ""}`;
12081
12218
  if (!raw.includes(`## [${next}]`)) {
@@ -12098,8 +12235,8 @@ ${heading}
12098
12235
  const root = findProjectRoot38(opts.dir);
12099
12236
  const version = await readCurrentVersion(root);
12100
12237
  for (const rel of VERSION_FILES2.slice(1)) {
12101
- const file = path40.join(root, rel);
12102
- if (!existsSync42(file)) continue;
12238
+ const file = path41.join(root, rel);
12239
+ if (!existsSync43(file)) continue;
12103
12240
  const v = JSON.parse(await readFile19(file, "utf8")).version;
12104
12241
  if (v !== version) {
12105
12242
  ui.error(`${rel} is at ${v}, root at ${version} \u2014 lockstep broken; run \`hivelore release bump\` first.`);
@@ -12145,9 +12282,9 @@ function registerRun(program2) {
12145
12282
 
12146
12283
  // src/commands/sensors.ts
12147
12284
  import { execFile as execFile7 } from "child_process";
12148
- import { existsSync as existsSync43 } from "fs";
12285
+ import { existsSync as existsSync44 } from "fs";
12149
12286
  import { chmod as chmod2, mkdir as mkdir17, readFile as readFile20, writeFile as writeFile27 } from "fs/promises";
12150
- import path41 from "path";
12287
+ import path42 from "path";
12151
12288
  import { promisify as promisify7 } from "util";
12152
12289
  import "commander";
12153
12290
  import {
@@ -12160,12 +12297,13 @@ import {
12160
12297
  judgeProposedSensor,
12161
12298
  loadConfig as loadConfig13,
12162
12299
  loadSensorLedger as loadSensorLedger4,
12163
- loadMemoriesFromDir as loadMemoriesFromDir15,
12300
+ loadMemoriesFromDir as loadMemoriesFromDir16,
12164
12301
  normalizeFramework,
12165
12302
  parseLessonFields,
12166
12303
  recordPreventionHits as recordPreventionHits2,
12167
12304
  resolveHaivePaths as resolveHaivePaths36,
12168
12305
  runSensors as runSensors2,
12306
+ buildProposeCommand,
12169
12307
  scaffoldPostIncidentTest,
12170
12308
  selectCommandSensors as selectCommandSensors2,
12171
12309
  TEST_FRAMEWORKS,
@@ -12212,7 +12350,7 @@ function registerSensors(program2) {
12212
12350
  const root = findProjectRoot39(opts.dir);
12213
12351
  const paths = resolveHaivePaths36(root);
12214
12352
  const memories = await runnableSensorMemories(paths);
12215
- const diff = opts.diffFile ? await readFile20(path41.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
12353
+ const diff = opts.diffFile ? await readFile20(path42.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
12216
12354
  const targets = scannableSensorTargets(diff);
12217
12355
  const hits = runSensors2(memories, targets);
12218
12356
  const config = await loadConfig13(paths);
@@ -12344,7 +12482,7 @@ function registerSensors(program2) {
12344
12482
  }
12345
12483
  const root = findProjectRoot39(opts.dir);
12346
12484
  const paths = resolveHaivePaths36(root);
12347
- const loaded = existsSync43(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
12485
+ const loaded = existsSync44(paths.memoriesDir) ? await loadMemoriesFromDir16(paths.memoriesDir) : [];
12348
12486
  const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
12349
12487
  if (!found) {
12350
12488
  ui.error(`No memory found with id ${id}`);
@@ -12411,7 +12549,7 @@ function registerSensors(program2) {
12411
12549
  return;
12412
12550
  }
12413
12551
  const root2 = findProjectRoot39(opts.dir);
12414
- const { proposeSensor } = await import("./server-HG2K3WOQ.js");
12552
+ const { proposeSensor } = await import("./server-X6S6KTYJ.js");
12415
12553
  const out = await proposeSensor(
12416
12554
  {
12417
12555
  memory_id: id,
@@ -12455,7 +12593,7 @@ function registerSensors(program2) {
12455
12593
  }
12456
12594
  const root = findProjectRoot39(opts.dir);
12457
12595
  const paths = resolveHaivePaths36(root);
12458
- const loaded = existsSync43(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
12596
+ const loaded = existsSync44(paths.memoriesDir) ? await loadMemoriesFromDir16(paths.memoriesDir) : [];
12459
12597
  const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
12460
12598
  if (!found) {
12461
12599
  ui.error(`No memory found with id ${id}`);
@@ -12499,13 +12637,18 @@ function registerSensors(program2) {
12499
12637
  ui.info(
12500
12638
  `self-check: silent on current=${verdict.self_check.silent_on_current}` + (verdict.self_check.fires_on_bad === null ? "; no bad example tested" : `; fires on bad=${verdict.self_check.fires_on_bad}`)
12501
12639
  );
12640
+ if (found.memory.frontmatter.scope === "personal") {
12641
+ ui.warn(
12642
+ `This lesson is personal-scoped \u2014 the sensor guards only YOUR machine (personal memories are gitignored). Promote it so the gate travels with the repo: hivelore memory promote ${id}`
12643
+ );
12644
+ }
12502
12645
  });
12503
12646
  sensors.command("scaffold").description(
12504
12647
  "Generate a PENDING post-incident test from a lesson (mem_tried/attempt/gotcha) \u2014 the on-ramp to\n a command sensor. Writes a test stub carrying the incident's provenance, then prints the exact\n `sensors propose --kind test` line to arm it once you've written the assertion. It never arms a\n sensor itself \u2014 propose_sensor stays the sole validated writer.\n\n Example:\n hivelore sensors scaffold 2026-07-03-attempt-refund-exceeds-capture --framework vitest"
12505
12648
  ).argument("<memory-id>", "lesson id to scaffold a test from").option("--framework <fw>", `test framework: ${TEST_FRAMEWORKS.join(" | ")} (auto-detected when omitted)`).option("--out <path>", "override the generated test file path (project-relative)").option("--stdout", "print the test to stdout instead of writing a file", false).option("--force", "overwrite an existing file at the target path", false).option("-d, --dir <dir>", "project root").action(async (id, opts) => {
12506
12649
  const root = findProjectRoot39(opts.dir);
12507
12650
  const paths = resolveHaivePaths36(root);
12508
- const loaded = existsSync43(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
12651
+ const loaded = existsSync44(paths.memoriesDir) ? await loadMemoriesFromDir16(paths.memoriesDir) : [];
12509
12652
  const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
12510
12653
  if (!found) {
12511
12654
  ui.error(`No memory found with id ${id}`);
@@ -12513,43 +12656,59 @@ function registerSensors(program2) {
12513
12656
  return;
12514
12657
  }
12515
12658
  const fm = found.memory.frontmatter;
12516
- const detected = await detectTestFrameworkForPaths(root, fm.anchor.paths ?? []);
12517
- const framework = opts.framework ? normalizeFramework(opts.framework) : detected.framework;
12518
- if (!framework) {
12659
+ const forced = opts.framework ? normalizeFramework(opts.framework) : null;
12660
+ if (opts.framework && !forced) {
12519
12661
  ui.error(`Unknown --framework "${opts.framework}". Use one of: ${TEST_FRAMEWORKS.join(", ")}.`);
12520
12662
  process.exitCode = 1;
12521
12663
  return;
12522
12664
  }
12665
+ const allGroups = await detectTestFrameworksForAnchors(root, fm.anchor.paths ?? []);
12666
+ const groups = opts.out ? allGroups.slice(0, 1) : allGroups;
12523
12667
  const fields = parseLessonFields(found.memory.body);
12524
- const scaffold = scaffoldPostIncidentTest(
12525
- {
12526
- memoryId: id,
12527
- title: fields.title || id,
12528
- whyFailed: fields.whyFailed,
12529
- instead: fields.instead,
12530
- incident: fm.sensor?.incident,
12531
- paths: fm.anchor.paths
12532
- },
12533
- { framework, outPath: opts.out, baseDir: detected.baseDir }
12668
+ const lesson = {
12669
+ memoryId: id,
12670
+ title: fields.title || id,
12671
+ whyFailed: fields.whyFailed,
12672
+ instead: fields.instead,
12673
+ incident: fm.sensor?.incident,
12674
+ paths: fm.anchor.paths
12675
+ };
12676
+ let scaffolds = groups.map(
12677
+ (g) => scaffoldPostIncidentTest(lesson, { framework: forced ?? g.framework, outPath: opts.out, baseDir: g.baseDir })
12534
12678
  );
12679
+ let proposeCmd = scaffolds[0].proposeCommand;
12680
+ if (scaffolds.length > 1) {
12681
+ proposeCmd = buildProposeCommand(lesson, scaffolds.map((s) => s.runCommand).join(" && "));
12682
+ scaffolds = groups.map(
12683
+ (g) => scaffoldPostIncidentTest(lesson, { framework: forced ?? g.framework, baseDir: g.baseDir, proposeCommandOverride: proposeCmd })
12684
+ );
12685
+ }
12535
12686
  if (opts.stdout) {
12536
- console.log(scaffold.content);
12537
- ui.info(`Arm it once written: ${scaffold.proposeCommand}`);
12687
+ for (const scaffold of scaffolds) {
12688
+ if (scaffolds.length > 1) ui.info(`--- ${scaffold.relPath} ---`);
12689
+ console.log(scaffold.content);
12690
+ }
12691
+ ui.info(`Arm ${scaffolds.length > 1 ? "them" : "it"} once written: ${proposeCmd}`);
12538
12692
  return;
12539
12693
  }
12540
- const abs = path41.isAbsolute(scaffold.relPath) ? scaffold.relPath : path41.resolve(root, scaffold.relPath);
12541
- if (existsSync43(abs) && !opts.force) {
12542
- ui.error(`${scaffold.relPath} already exists \u2014 pass --force to overwrite, or --out <path> for a different file.`);
12543
- process.exitCode = 1;
12544
- return;
12694
+ for (const scaffold of scaffolds) {
12695
+ const abs = path42.isAbsolute(scaffold.relPath) ? scaffold.relPath : path42.resolve(root, scaffold.relPath);
12696
+ if (existsSync44(abs) && !opts.force) {
12697
+ ui.error(`${scaffold.relPath} already exists \u2014 pass --force to overwrite, or --out <path> for a different file.`);
12698
+ process.exitCode = 1;
12699
+ return;
12700
+ }
12701
+ await mkdir17(path42.dirname(abs), { recursive: true });
12702
+ await writeFile27(abs, scaffold.content, "utf8");
12703
+ ui.success(`Wrote ${scaffold.framework} post-incident test \u2192 ${scaffold.relPath}`);
12704
+ }
12705
+ if (scaffolds.length > 1) {
12706
+ ui.info(` Lesson spans ${scaffolds.length} packages \u2014 one pending test per owning package, ONE sensor arms them all.`);
12545
12707
  }
12546
- await mkdir17(path41.dirname(abs), { recursive: true });
12547
- await writeFile27(abs, scaffold.content, "utf8");
12548
- ui.success(`Wrote ${framework} post-incident test \u2192 ${scaffold.relPath}`);
12549
12708
  ui.info(" 1. Fill in the assertion (RED on the incident, GREEN once fixed).");
12550
- ui.info(` 2. Run it: ${scaffold.runCommand}`);
12709
+ ui.info(` 2. Run ${scaffolds.length > 1 ? "them" : "it"}: ${scaffolds.map((s) => s.runCommand).join(" && ")}`);
12551
12710
  ui.info(" 3. Arm it as a deterministic gate:");
12552
- console.log(` ${scaffold.proposeCommand}`);
12711
+ console.log(` ${proposeCmd}`);
12553
12712
  });
12554
12713
  sensors.command("export").description("Export regex sensors into .ai/generated for external toolchains").option("--format <format>", "grep | eslint", "grep").option("--out-dir <dir>", "output directory", ".ai/generated").option("-d, --dir <dir>", "project root").action(async (opts) => {
12555
12714
  const format = opts.format ?? "grep";
@@ -12561,13 +12720,13 @@ function registerSensors(program2) {
12561
12720
  const root = findProjectRoot39(opts.dir);
12562
12721
  const paths = resolveHaivePaths36(root);
12563
12722
  const rows = await sensorRows(paths);
12564
- const outDir = path41.resolve(root, opts.outDir ?? ".ai/generated");
12723
+ const outDir = path42.resolve(root, opts.outDir ?? ".ai/generated");
12565
12724
  await mkdir17(outDir, { recursive: true });
12566
- const outPath = path41.join(outDir, format === "grep" ? "haive-sensors-grep.sh" : "haive-sensors-eslint.json");
12725
+ const outPath = path42.join(outDir, format === "grep" ? "haive-sensors-grep.sh" : "haive-sensors-eslint.json");
12567
12726
  const content = format === "grep" ? renderGrepScript(rows) : JSON.stringify({ sensors: rows }, null, 2) + "\n";
12568
12727
  await writeFile27(outPath, content, "utf8");
12569
12728
  if (format === "grep") await chmod2(outPath, 493);
12570
- ui.success(`Exported ${rows.length} sensor(s): ${path41.relative(root, outPath)}`);
12729
+ ui.success(`Exported ${rows.length} sensor(s): ${path42.relative(root, outPath)}`);
12571
12730
  });
12572
12731
  }
12573
12732
  function deriveProposedMessage(body, pattern, absent) {
@@ -12600,8 +12759,8 @@ async function sensorRows(paths) {
12600
12759
  });
12601
12760
  }
12602
12761
  async function runnableSensorMemories(paths, regexOnly = true) {
12603
- if (!existsSync43(paths.memoriesDir)) return [];
12604
- const loaded = await loadMemoriesFromDir15(paths.memoriesDir);
12762
+ if (!existsSync44(paths.memoriesDir)) return [];
12763
+ const loaded = await loadMemoriesFromDir16(paths.memoriesDir);
12605
12764
  return loaded.map(({ memory: memory2 }) => memory2).filter((memory2) => {
12606
12765
  const sensor = memory2.frontmatter.sensor;
12607
12766
  if (!sensor) return false;
@@ -12642,15 +12801,15 @@ function shellQuote(value) {
12642
12801
  }
12643
12802
 
12644
12803
  // src/commands/ingest.ts
12645
- import { existsSync as existsSync44 } from "fs";
12804
+ import { existsSync as existsSync45 } from "fs";
12646
12805
  import { mkdir as mkdir18, readFile as readFile21, writeFile as writeFile28 } from "fs/promises";
12647
- import path42 from "path";
12806
+ import path43 from "path";
12648
12807
  import "commander";
12649
12808
  import {
12650
12809
  draftsFromFindings,
12651
12810
  filterNewDrafts,
12652
12811
  findProjectRoot as findProjectRoot40,
12653
- loadMemoriesFromDir as loadMemoriesFromDir16,
12812
+ loadMemoriesFromDir as loadMemoriesFromDir17,
12654
12813
  memoryFilePath as memoryFilePath7,
12655
12814
  parseFindings,
12656
12815
  resolveHaivePaths as resolveHaivePaths37,
@@ -12680,7 +12839,7 @@ function registerIngest(program2) {
12680
12839
  }
12681
12840
  const root = findProjectRoot40(opts.dir);
12682
12841
  const paths = resolveHaivePaths37(root);
12683
- if (!existsSync44(paths.haiveDir)) {
12842
+ if (!existsSync45(paths.haiveDir)) {
12684
12843
  ui.error(`No .ai/ found at ${root}. Run \`hivelore init\` first.`);
12685
12844
  process.exitCode = 1;
12686
12845
  return;
@@ -12701,8 +12860,8 @@ function registerIngest(program2) {
12701
12860
  process.exitCode = 1;
12702
12861
  return;
12703
12862
  }
12704
- const reportPath = path42.resolve(root, file);
12705
- if (!existsSync44(reportPath)) {
12863
+ const reportPath = path43.resolve(root, file);
12864
+ if (!existsSync45(reportPath)) {
12706
12865
  ui.error(`Report file not found: ${reportPath}`);
12707
12866
  process.exitCode = 1;
12708
12867
  return;
@@ -12734,7 +12893,7 @@ function registerIngest(program2) {
12734
12893
  process.exitCode = 1;
12735
12894
  return;
12736
12895
  }
12737
- const existing = existsSync44(paths.memoriesDir) ? await loadMemoriesFromDir16(paths.memoriesDir) : [];
12896
+ const existing = existsSync45(paths.memoriesDir) ? await loadMemoriesFromDir17(paths.memoriesDir) : [];
12738
12897
  const existingTopics = new Set(
12739
12898
  existing.map(({ memory: memory2 }) => memory2.frontmatter.topic).filter((t) => Boolean(t))
12740
12899
  );
@@ -12796,13 +12955,13 @@ function registerIngest(program2) {
12796
12955
  await writeDraft(paths, draft);
12797
12956
  created++;
12798
12957
  }
12799
- ui.success(`Created ${created} proposed memory(ies) under ${path42.relative(root, paths.memoriesDir)}/`);
12958
+ ui.success(`Created ${created} proposed memory(ies) under ${path43.relative(root, paths.memoriesDir)}/`);
12800
12959
  ui.info("Review with `hivelore memory pending`; promote sensors with `hivelore sensors promote <id> --yes`.");
12801
12960
  });
12802
12961
  }
12803
12962
  async function writeDraft(paths, draft) {
12804
12963
  const file = memoryFilePath7(paths, draft.frontmatter.scope, draft.frontmatter.id, draft.frontmatter.module);
12805
- await mkdir18(path42.dirname(file), { recursive: true });
12964
+ await mkdir18(path43.dirname(file), { recursive: true });
12806
12965
  await writeFile28(file, serializeMemory16({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
12807
12966
  return file;
12808
12967
  }
@@ -12844,13 +13003,13 @@ async function fetchSonarIssues(opts) {
12844
13003
  }
12845
13004
 
12846
13005
  // src/commands/dashboard.ts
12847
- import { existsSync as existsSync45 } from "fs";
13006
+ import { existsSync as existsSync46 } from "fs";
12848
13007
  import "commander";
12849
13008
  import {
12850
13009
  buildDashboard,
12851
13010
  findProjectRoot as findProjectRoot41,
12852
13011
  loadConfig as loadConfig14,
12853
- loadMemoriesFromDir as loadMemoriesFromDir17,
13012
+ loadMemoriesFromDir as loadMemoriesFromDir18,
12854
13013
  loadPreventionEvents as loadPreventionEvents4,
12855
13014
  loadUsageIndex as loadUsageIndex16,
12856
13015
  resolveHaivePaths as resolveHaivePaths38
@@ -12861,12 +13020,12 @@ function registerDashboard(program2) {
12861
13020
  ).option("--json", "emit the full report as JSON", false).option("--top <n>", "rows per top-list", "10").option("--dormant-days <n>", "dormancy window for impact scoring").option("-d, --dir <dir>", "project root").action(async (opts) => {
12862
13021
  const root = findProjectRoot41(opts.dir);
12863
13022
  const paths = resolveHaivePaths38(root);
12864
- if (!existsSync45(paths.haiveDir)) {
13023
+ if (!existsSync46(paths.haiveDir)) {
12865
13024
  ui.error(`No .ai/ found at ${root}. Run \`hivelore init\` first.`);
12866
13025
  process.exitCode = 1;
12867
13026
  return;
12868
13027
  }
12869
- const memories = existsSync45(paths.memoriesDir) ? await loadMemoriesFromDir17(paths.memoriesDir) : [];
13028
+ const memories = existsSync46(paths.memoriesDir) ? await loadMemoriesFromDir18(paths.memoriesDir) : [];
12870
13029
  const usage = await loadUsageIndex16(paths);
12871
13030
  const preventionEvents = await loadPreventionEvents4(paths);
12872
13031
  const config = await loadConfig14(paths);
@@ -12992,8 +13151,8 @@ function warnNum(n) {
12992
13151
  // src/commands/dev-link.ts
12993
13152
  import { execFile as execFile8 } from "child_process";
12994
13153
  import { cp, readFile as readFile22 } from "fs/promises";
12995
- import { existsSync as existsSync46 } from "fs";
12996
- import path43 from "path";
13154
+ import { existsSync as existsSync47 } from "fs";
13155
+ import path44 from "path";
12997
13156
  import { promisify as promisify8 } from "util";
12998
13157
  import "commander";
12999
13158
  import { findProjectRoot as findProjectRoot42 } from "@hivelore/core";
@@ -13002,7 +13161,7 @@ function registerDevLink(program2) {
13002
13161
  const dev = program2.commands.find((c) => c.name() === "dev") ?? program2.command("dev").description("Developer utilities for working on Hivelore itself.");
13003
13162
  dev.command("link").description("Hot-swap this repo's built dist into the global @hivelore (or legacy @hiveai) install so the global binary 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) => {
13004
13163
  const root = findProjectRoot42(opts.dir);
13005
- if (!existsSync46(path43.join(root, "packages", "cli", "dist", "index.js"))) {
13164
+ if (!existsSync47(path44.join(root, "packages", "cli", "dist", "index.js"))) {
13006
13165
  ui.error(`Not the Hivelore monorepo (no packages/cli/dist) at ${root}. Run \`pnpm -r build\` first, or pass --dir.`);
13007
13166
  process.exitCode = 1;
13008
13167
  return;
@@ -13011,9 +13170,9 @@ function registerDevLink(program2) {
13011
13170
  try {
13012
13171
  globalModules = (await exec6("npm", ["root", "-g"])).stdout.trim();
13013
13172
  } catch {
13014
- globalModules = path43.join(path43.dirname(path43.dirname(process.execPath)), "lib", "node_modules");
13173
+ globalModules = path44.join(path44.dirname(path44.dirname(process.execPath)), "lib", "node_modules");
13015
13174
  }
13016
- const scopeDirs = ["@hivelore", "@hiveai"].map((scope) => path43.join(globalModules, scope)).filter((dir) => existsSync46(dir));
13175
+ const scopeDirs = ["@hivelore", "@hiveai"].map((scope) => path44.join(globalModules, scope)).filter((dir) => existsSync47(dir));
13017
13176
  if (scopeDirs.length === 0) {
13018
13177
  ui.error(`No global @hivelore (or legacy @hiveai) install under ${globalModules}. Install once with \`npm i -g @hivelore/cli\`, then re-run.`);
13019
13178
  process.exitCode = 1;
@@ -13021,24 +13180,24 @@ function registerDevLink(program2) {
13021
13180
  }
13022
13181
  const linked = [];
13023
13182
  const copyDist = async (fromPkg, toDistDir) => {
13024
- const from = path43.join(root, "packages", fromPkg, "dist");
13025
- if (!existsSync46(from) || !existsSync46(path43.dirname(toDistDir))) return;
13183
+ const from = path44.join(root, "packages", fromPkg, "dist");
13184
+ if (!existsSync47(from) || !existsSync47(path44.dirname(toDistDir))) return;
13026
13185
  await cp(from, toDistDir, { recursive: true });
13027
- linked.push(path43.relative(globalModules, toDistDir));
13186
+ linked.push(path44.relative(globalModules, toDistDir));
13028
13187
  };
13029
13188
  for (const globalHive of scopeDirs) {
13030
- const nestedScope = path43.basename(globalHive);
13189
+ const nestedScope = path44.basename(globalHive);
13031
13190
  for (const pkg of ["cli", "mcp"]) {
13032
- await copyDist(pkg, path43.join(globalHive, pkg, "dist"));
13191
+ await copyDist(pkg, path44.join(globalHive, pkg, "dist"));
13033
13192
  for (const nested of ["core", "embeddings"]) {
13034
- await copyDist(nested, path43.join(globalHive, pkg, "node_modules", nestedScope, nested, "dist"));
13193
+ await copyDist(nested, path44.join(globalHive, pkg, "node_modules", nestedScope, nested, "dist"));
13035
13194
  }
13036
13195
  }
13037
- await copyDist("core", path43.join(globalHive, "core", "dist"));
13196
+ await copyDist("core", path44.join(globalHive, "core", "dist"));
13038
13197
  }
13039
13198
  let version = "unknown";
13040
13199
  try {
13041
- version = JSON.parse(await readFile22(path43.join(root, "package.json"), "utf8")).version ?? "unknown";
13200
+ version = JSON.parse(await readFile22(path44.join(root, "package.json"), "utf8")).version ?? "unknown";
13042
13201
  } catch {
13043
13202
  }
13044
13203
  if (opts.json) {
@@ -13049,7 +13208,7 @@ function registerDevLink(program2) {
13049
13208
  ui.warn("Nothing linked \u2014 no matching dist targets were found in the global install.");
13050
13209
  return;
13051
13210
  }
13052
- ui.success(`Linked local dist (v${version}) into the global install(s): ${scopeDirs.map((d) => path43.basename(d)).join(", ")}`);
13211
+ ui.success(`Linked local dist (v${version}) into the global install(s): ${scopeDirs.map((d) => path44.basename(d)).join(", ")}`);
13053
13212
  for (const t of linked) console.log(` ${ui.dim("\u2192")} ${t}`);
13054
13213
  console.log(ui.dim("The global binary now runs your local build (git hooks + MCP included)."));
13055
13214
  });
@@ -13057,8 +13216,8 @@ function registerDevLink(program2) {
13057
13216
 
13058
13217
  // src/commands/coverage.ts
13059
13218
  import { readFile as readFile23 } from "fs/promises";
13060
- import { existsSync as existsSync47 } from "fs";
13061
- import path44 from "path";
13219
+ import { existsSync as existsSync48 } from "fs";
13220
+ import path45 from "path";
13062
13221
  import "commander";
13063
13222
  import {
13064
13223
  findCoverageGaps,
@@ -13068,7 +13227,7 @@ import {
13068
13227
  tallyHotFiles
13069
13228
  } from "@hivelore/core";
13070
13229
  async function readAgentHotFiles(root, cacheFile, sinceMs) {
13071
- if (!existsSync47(cacheFile)) return [];
13230
+ if (!existsSync48(cacheFile)) return [];
13072
13231
  const raw = await readFile23(cacheFile, "utf8").catch(() => "");
13073
13232
  const files = [];
13074
13233
  for (const line of raw.split("\n")) {
@@ -13082,7 +13241,7 @@ async function readAgentHotFiles(root, cacheFile, sinceMs) {
13082
13241
  }
13083
13242
  for (const f of obs.files ?? []) {
13084
13243
  if (typeof f !== "string" || !f) continue;
13085
- const rel = path44.isAbsolute(f) ? path44.relative(root, f) : f;
13244
+ const rel = path45.isAbsolute(f) ? path45.relative(root, f) : f;
13086
13245
  if (rel.startsWith("..")) continue;
13087
13246
  files.push(rel);
13088
13247
  }
@@ -13126,7 +13285,7 @@ function registerCoverage(program2) {
13126
13285
  }) : null;
13127
13286
  const gitHotFiles = (radar?.hotFiles ?? []).filter((h) => !isNoisePath(h.path)).map((h) => ({ path: h.path, changes: h.changes, source: "git" }));
13128
13287
  const sinceMs = Date.now() - days * 864e5;
13129
- const agentHotFiles = useAgent ? (await readAgentHotFiles(root, path44.join(paths.haiveDir, ".cache", "observations.jsonl"), sinceMs)).filter((h) => !isNoisePath(h.path)) : [];
13288
+ const agentHotFiles = useAgent ? (await readAgentHotFiles(root, path45.join(paths.haiveDir, ".cache", "observations.jsonl"), sinceMs)).filter((h) => !isNoisePath(h.path)) : [];
13130
13289
  const hotFiles = mergeHotFiles(gitHotFiles, agentHotFiles);
13131
13290
  const memories = await loadMemoriesFromDir(paths.memoriesDir);
13132
13291
  const gaps = findCoverageGaps(hotFiles, memories, { minChanges, limit });
@@ -13164,8 +13323,8 @@ function registerCoverage(program2) {
13164
13323
 
13165
13324
  // src/commands/merge-driver.ts
13166
13325
  import { execFileSync as execFileSync3 } from "child_process";
13167
- import { readFileSync, writeFileSync, existsSync as existsSync48 } from "fs";
13168
- import path45 from "path";
13326
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync49 } from "fs";
13327
+ import path46 from "path";
13169
13328
  import "commander";
13170
13329
  import { findProjectRoot as findProjectRoot44, mergeMemoryVersions } from "@hivelore/core";
13171
13330
  var GITATTRIBUTES_MARK = "# Hivelore merge driver";
@@ -13178,8 +13337,8 @@ function registerMergeDriver(program2) {
13178
13337
  const cmd = program2.command("merge-driver").description("Deterministic git merge driver for Hivelore memory files (kills .ai/ conflict markers)");
13179
13338
  cmd.command("run <base> <ours> <theirs>").description("Git merge-driver entrypoint: resolve ours/theirs by frontmatter order, write into <ours>").action((base, ours, theirs) => {
13180
13339
  try {
13181
- const oursContent = readFileSync(ours, "utf8");
13182
- const theirsContent = readFileSync(theirs, "utf8");
13340
+ const oursContent = readFileSync2(ours, "utf8");
13341
+ const theirsContent = readFileSync2(theirs, "utf8");
13183
13342
  const result = mergeMemoryVersions(oursContent, theirsContent);
13184
13343
  if (result.content !== oursContent) writeFileSync(ours, result.content, "utf8");
13185
13344
  process.exit(0);
@@ -13197,8 +13356,8 @@ function registerMergeDriver(program2) {
13197
13356
  process.exitCode = 1;
13198
13357
  return;
13199
13358
  }
13200
- const gaPath = path45.join(root, ".gitattributes");
13201
- let content = existsSync48(gaPath) ? readFileSync(gaPath, "utf8") : "";
13359
+ const gaPath = path46.join(root, ".gitattributes");
13360
+ let content = existsSync49(gaPath) ? readFileSync2(gaPath, "utf8") : "";
13202
13361
  if (!content.includes(GITATTRIBUTES_MARK)) {
13203
13362
  if (content.length > 0 && !content.endsWith("\n")) content += "\n";
13204
13363
  content += GITATTRIBUTES_BLOCK + "\n";
@@ -13212,8 +13371,8 @@ function registerMergeDriver(program2) {
13212
13371
  }
13213
13372
 
13214
13373
  // src/commands/bridges.ts
13215
- import { existsSync as existsSync49 } from "fs";
13216
- import path46 from "path";
13374
+ import { existsSync as existsSync50 } from "fs";
13375
+ import path47 from "path";
13217
13376
  import "commander";
13218
13377
  import {
13219
13378
  findProjectRoot as findProjectRoot45,
@@ -13234,7 +13393,7 @@ function registerBridges(program2) {
13234
13393
  const root = findProjectRoot45(opts.dir);
13235
13394
  const paths = resolveHaivePaths40(root);
13236
13395
  const dryRun = opts.dryRun === true;
13237
- if (!existsSync49(paths.memoriesDir)) {
13396
+ if (!existsSync50(paths.memoriesDir)) {
13238
13397
  ui.warn(`No .ai/memories at ${root}. Run \`hivelore init\` first.`);
13239
13398
  process.exitCode = 1;
13240
13399
  return;
@@ -13253,7 +13412,7 @@ function registerBridges(program2) {
13253
13412
  targets = BRIDGE_TARGETS4;
13254
13413
  } else {
13255
13414
  targets = BRIDGE_TARGETS4.filter(
13256
- (t) => existsSync49(path46.join(root, BRIDGE_TARGET_PATH3[t]))
13415
+ (t) => existsSync50(path47.join(root, BRIDGE_TARGET_PATH3[t]))
13257
13416
  );
13258
13417
  if (targets.length === 0) {
13259
13418
  ui.info(
@@ -13301,7 +13460,7 @@ function registerBridges(program2) {
13301
13460
 
13302
13461
  // src/index.ts
13303
13462
  var program = new Command48();
13304
- program.name("hivelore").description("Hivelore - the deterministic policy gate for agent-written code (rules live as repo-native team memory)").version("0.38.0").option("--advanced", "show maintenance and experimental commands in help").showSuggestionAfterError(true);
13463
+ program.name("hivelore").description("Hivelore - the deterministic policy gate for agent-written code (rules live as repo-native team memory)").version("0.39.1").option("--advanced", "show maintenance and experimental commands in help").showSuggestionAfterError(true);
13305
13464
  registerInit(program);
13306
13465
  registerResolveProject(program);
13307
13466
  registerEnforce(program);