@hivelore/cli 0.37.0 → 0.39.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
@@ -3,6 +3,7 @@ import {
3
3
  antiPatternsCheck,
4
4
  codeMapTool,
5
5
  codeSearch,
6
+ detectTestFrameworksForAnchors,
6
7
  getBriefing,
7
8
  getRecap,
8
9
  memRelevantTo,
@@ -10,7 +11,7 @@ import {
10
11
  preCommitCheck,
11
12
  readPresumedCorrectTargets,
12
13
  runHaiveMcpStdio
13
- } from "./chunk-VLRQ4MRO.js";
14
+ } from "./chunk-I4VELI5K.js";
14
15
  import {
15
16
  registerMemoryPending
16
17
  } from "./chunk-OYJKHD22.js";
@@ -208,7 +209,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
208
209
  if (!f) continue;
209
210
  counts.set(f, (counts.get(f) ?? 0) + 1);
210
211
  }
211
- let entries = [...counts.entries()].map(([path47, changes]) => ({ path: path47, changes }));
212
+ let entries = [...counts.entries()].map(([path48, changes]) => ({ path: path48, changes }));
212
213
  const lowerPaths = filePaths.map((p) => p.toLowerCase());
213
214
  if (lowerPaths.length > 0) {
214
215
  entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
@@ -3755,7 +3756,7 @@ ${SEED_FOOTER(stack)}` });
3755
3756
 
3756
3757
  // src/commands/init.ts
3757
3758
  var execFileAsync = promisify2(execFile2);
3758
- var HAIVE_GITHUB_ACTION_REF = `v${"0.37.0"}`;
3759
+ var HAIVE_GITHUB_ACTION_REF = `v${"0.39.0"}`;
3759
3760
  var PROJECT_CONTEXT_TEMPLATE = `# Project context
3760
3761
 
3761
3762
  > Generated by \`hivelore init\`. Run \`hivelore init --bootstrap\` to auto-fill from your codebase,
@@ -6022,7 +6023,7 @@ function parseCsv4(raw) {
6022
6023
  function registerMemoryTried(memory2) {
6023
6024
  memory2.command("tried").description(
6024
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'
6025
- ).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) => {
6026
6027
  const root = findProjectRoot15(opts.dir);
6027
6028
  const paths = resolveHaivePaths14(root);
6028
6029
  if (!existsSync20(paths.haiveDir)) {
@@ -6039,7 +6040,8 @@ function registerMemoryTried(memory2) {
6039
6040
  why_failed: opts.whyFailed,
6040
6041
  instead: opts.instead,
6041
6042
  // "shared" is a legacy MemoryScope alias not accepted by mem_tried — normalize to team.
6042
- 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,
6043
6045
  module: opts.module,
6044
6046
  tags: parseCsv4(opts.tags),
6045
6047
  paths: parseCsv4(opts.paths ?? opts.files),
@@ -6072,7 +6074,7 @@ function registerMemoryTried(memory2) {
6072
6074
  return;
6073
6075
  }
6074
6076
  ui.success(`Recorded: ${path21.relative(root, result.file_path)}`);
6075
- 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)`);
6076
6078
  if (result.sensor_result) {
6077
6079
  if (result.sensor_result.accepted) {
6078
6080
  ui.success(`Loop closed: sensor attached (${result.sensor_result.severity}) \u2014 the gate now refuses a repeat deterministically.`);
@@ -8574,9 +8576,9 @@ function parseDays(input) {
8574
8576
  }
8575
8577
 
8576
8578
  // src/commands/doctor.ts
8577
- import { existsSync as existsSync39, statSync as statSync2 } from "fs";
8579
+ import { existsSync as existsSync40, statSync as statSync3 } from "fs";
8578
8580
  import { readFile as readFile16, stat, writeFile as writeFile23 } from "fs/promises";
8579
- import path36 from "path";
8581
+ import path37 from "path";
8580
8582
  import { execFileSync, execSync } from "child_process";
8581
8583
  import "commander";
8582
8584
  import {
@@ -8598,6 +8600,85 @@ import {
8598
8600
  readUsageEvents as readUsageEvents3,
8599
8601
  resolveHaivePaths as resolveHaivePaths33
8600
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
8601
8682
  var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
8602
8683
  function registerDoctor(program2) {
8603
8684
  program2.command("doctor").description(
@@ -8608,7 +8689,7 @@ function registerDoctor(program2) {
8608
8689
  const findings = [];
8609
8690
  const repairs = [];
8610
8691
  const config = await loadConfig10(paths);
8611
- if (!existsSync39(paths.haiveDir)) {
8692
+ if (!existsSync40(paths.haiveDir)) {
8612
8693
  if (opts.json) {
8613
8694
  console.log(JSON.stringify({
8614
8695
  initialized: false,
@@ -8633,7 +8714,7 @@ function registerDoctor(program2) {
8633
8714
  })
8634
8715
  );
8635
8716
  }
8636
- if (!existsSync39(paths.projectContext)) {
8717
+ if (!existsSync40(paths.projectContext)) {
8637
8718
  findings.push({
8638
8719
  severity: "warn",
8639
8720
  code: "no-project-context",
@@ -8653,7 +8734,7 @@ function registerDoctor(program2) {
8653
8734
  });
8654
8735
  } else {
8655
8736
  const referenced = extractReferencedPaths(content);
8656
- const missing = referenced.filter((p) => !existsSync39(path36.resolve(root, p)));
8737
+ const missing = referenced.filter((p) => !existsSync40(path37.resolve(root, p)));
8657
8738
  const grounded = referenced.length - missing.length;
8658
8739
  if (referenced.length >= 3 && grounded / referenced.length < 0.5) {
8659
8740
  findings.push({
@@ -8675,11 +8756,11 @@ function registerDoctor(program2) {
8675
8756
  });
8676
8757
  }
8677
8758
  }
8678
- const memoriesDetailed = existsSync39(paths.memoriesDir) ? await loadMemoriesFromDirDetailed(paths.memoriesDir) : { loaded: [], invalid: [] };
8759
+ const memoriesDetailed = existsSync40(paths.memoriesDir) ? await loadMemoriesFromDirDetailed(paths.memoriesDir) : { loaded: [], invalid: [] };
8679
8760
  const memories = memoriesDetailed.loaded;
8680
8761
  const now = Date.now();
8681
8762
  if (memoriesDetailed.invalid.length > 0) {
8682
- 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("; ");
8683
8764
  findings.push({
8684
8765
  severity: "warn",
8685
8766
  code: "invalid-memory-files",
@@ -8820,8 +8901,8 @@ function registerDoctor(program2) {
8820
8901
  const anchorPaths = s.paths.length > 0 ? s.paths : m.memory.frontmatter.anchor.paths;
8821
8902
  const targets = [];
8822
8903
  for (const rel of anchorPaths) {
8823
- const abs = path36.resolve(root, rel);
8824
- if (!existsSync39(abs)) continue;
8904
+ const abs = path37.resolve(root, rel);
8905
+ if (!existsSync40(abs)) continue;
8825
8906
  try {
8826
8907
  targets.push({ path: rel, content: await readFile16(abs, "utf8") });
8827
8908
  } catch {
@@ -8896,6 +8977,18 @@ function registerDoctor(program2) {
8896
8977
  }
8897
8978
  findings.push(...await collectHarnessCoverageFindings(codeMap, memories));
8898
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
+ }
8899
8992
  const events = await readUsageEvents3(paths);
8900
8993
  if (events.length === 0) {
8901
8994
  findings.push({
@@ -8932,9 +9025,9 @@ function registerDoctor(program2) {
8932
9025
  }
8933
9026
  }
8934
9027
  if (config.enforcement?.requireBriefingFirst) {
8935
- const claudeSettings = path36.join(root, ".claude", "settings.local.json");
9028
+ const claudeSettings = path37.join(root, ".claude", "settings.local.json");
8936
9029
  let hasClaudeEnforcement = false;
8937
- if (existsSync39(claudeSettings)) {
9030
+ if (existsSync40(claudeSettings)) {
8938
9031
  try {
8939
9032
  const { readFile: readFile24 } = await import("fs/promises");
8940
9033
  const raw = await readFile24(claudeSettings, "utf8");
@@ -8960,7 +9053,7 @@ function registerDoctor(program2) {
8960
9053
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `hivelore init` without --manual)."
8961
9054
  });
8962
9055
  }
8963
- findings.push(...await collectInstallFindings(root, "0.37.0"));
9056
+ findings.push(...await collectInstallFindings(root, "0.39.0"));
8964
9057
  findings.push(...await collectToolchainFindings(root));
8965
9058
  try {
8966
9059
  const legacyRaw = execSync("haive-mcp --version", {
@@ -8968,7 +9061,7 @@ function registerDoctor(program2) {
8968
9061
  timeout: 3e3,
8969
9062
  stdio: ["ignore", "pipe", "ignore"]
8970
9063
  }).trim();
8971
- const cliVersion = "0.37.0";
9064
+ const cliVersion = "0.39.0";
8972
9065
  if (legacyRaw && legacyRaw !== cliVersion) {
8973
9066
  findings.push({
8974
9067
  severity: "warn",
@@ -8984,17 +9077,17 @@ npm uninstall -g @hivelore/mcp`
8984
9077
  }
8985
9078
  {
8986
9079
  const configPaths = [
8987
- path36.join(root, ".mcp.json"),
8988
- path36.join(root, ".cursor", "mcp.json"),
8989
- 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")
8990
9083
  ];
8991
9084
  const staleConfigs = [];
8992
9085
  for (const cfgPath of configPaths) {
8993
- if (!existsSync39(cfgPath)) continue;
9086
+ if (!existsSync40(cfgPath)) continue;
8994
9087
  try {
8995
9088
  const raw = await readFile16(cfgPath, "utf8");
8996
9089
  if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
8997
- staleConfigs.push(path36.relative(root, cfgPath));
9090
+ staleConfigs.push(path37.relative(root, cfgPath));
8998
9091
  if (opts.fix && !opts.dryRun) {
8999
9092
  const updated = raw.replace(/"command"\s*:\s*"haive-mcp"/g, '"command": "hivelore"').replace(/"args"\s*:\s*\[\]/g, '"args": ["mcp", "--stdio"]');
9000
9093
  await writeFile23(cfgPath, updated, "utf8");
@@ -9287,8 +9380,8 @@ which -a hivelore haive`
9287
9380
  const missingBins = /* @__PURE__ */ new Map();
9288
9381
  const staleBins = /* @__PURE__ */ new Map();
9289
9382
  for (const rel of integrationFiles) {
9290
- const file = path36.join(root, rel);
9291
- if (!existsSync39(file)) continue;
9383
+ const file = path37.join(root, rel);
9384
+ if (!existsSync40(file)) continue;
9292
9385
  const text = await readFile16(file, "utf8").catch(() => "");
9293
9386
  for (const bin of extractAbsoluteHaiveBins(text)) {
9294
9387
  const version = versionForBinary(bin);
@@ -9321,7 +9414,7 @@ which -a hivelore haive`
9321
9414
  async function collectToolchainFindings(root) {
9322
9415
  const findings = [];
9323
9416
  const pkg = await readJson(
9324
- path36.join(root, "package.json")
9417
+ path37.join(root, "package.json")
9325
9418
  );
9326
9419
  const wantsPnpm = pkg?.packageManager?.startsWith("pnpm@") || Object.values(pkg?.scripts ?? {}).some((script) => /\bpnpm\b/.test(script));
9327
9420
  if (!wantsPnpm) return findings;
@@ -9340,10 +9433,10 @@ async function collectToolchainFindings(root) {
9340
9433
  }
9341
9434
  async function collectDistFreshnessFindings(root, expectedVersion) {
9342
9435
  const findings = [];
9343
- const isHaiveWorkspace = ["hivelore-monorepo", "haive-monorepo"].includes((await readJson(path36.join(root, "package.json")))?.name ?? "");
9436
+ const isHaiveWorkspace = ["hivelore-monorepo", "haive-monorepo"].includes((await readJson(path37.join(root, "package.json")))?.name ?? "");
9344
9437
  if (!isHaiveWorkspace) return findings;
9345
- const cliDist = path36.join(root, "packages/cli/dist/index.js");
9346
- if (!existsSync39(cliDist)) {
9438
+ const cliDist = path37.join(root, "packages/cli/dist/index.js");
9439
+ if (!existsSync40(cliDist)) {
9347
9440
  findings.push({
9348
9441
  severity: "warn",
9349
9442
  code: "workspace-dist-missing",
@@ -9367,10 +9460,10 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
9367
9460
  "packages/core/src/index.ts",
9368
9461
  "packages/mcp/src/server.ts",
9369
9462
  "packages/cli/src/index.ts"
9370
- ].map((rel) => path36.join(root, rel)).filter(existsSync39);
9463
+ ].map((rel) => path37.join(root, rel)).filter(existsSync40);
9371
9464
  if (sourceFiles.length > 0) {
9372
- const distMtime = statSync2(cliDist).mtimeMs;
9373
- const newestSource = Math.max(...sourceFiles.map((file) => statSync2(file).mtimeMs));
9465
+ const distMtime = statSync3(cliDist).mtimeMs;
9466
+ const newestSource = Math.max(...sourceFiles.map((file) => statSync3(file).mtimeMs));
9374
9467
  if (newestSource > distMtime + 1e3) {
9375
9468
  findings.push({
9376
9469
  severity: "info",
@@ -9385,7 +9478,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
9385
9478
  }
9386
9479
  async function collectWorkspaceVersionFindings(root, expectedVersion) {
9387
9480
  const findings = [];
9388
- const rootPkg = await readJson(path36.join(root, "package.json"));
9481
+ const rootPkg = await readJson(path37.join(root, "package.json"));
9389
9482
  const workspacePackages = [
9390
9483
  "packages/core/package.json",
9391
9484
  "packages/embeddings/package.json",
@@ -9394,7 +9487,7 @@ async function collectWorkspaceVersionFindings(root, expectedVersion) {
9394
9487
  ];
9395
9488
  const existing = (await Promise.all(workspacePackages.map(async (rel) => ({
9396
9489
  rel,
9397
- pkg: await readJson(path36.join(root, rel))
9490
+ pkg: await readJson(path37.join(root, rel))
9398
9491
  })))).filter((item) => item.pkg);
9399
9492
  const isHaiveWorkspace = rootPkg?.name === "hivelore-monorepo" || rootPkg?.name === "haive-monorepo" || existing.some((item) => item.pkg?.name?.startsWith("@hivelore/"));
9400
9493
  if (!isHaiveWorkspace) return findings;
@@ -9456,7 +9549,7 @@ function collectGlobalHivemoduleFindings(expectedVersion) {
9456
9549
  }
9457
9550
  }
9458
9551
  async function readJson(file) {
9459
- if (!existsSync39(file)) return null;
9552
+ if (!existsSync40(file)) return null;
9460
9553
  try {
9461
9554
  return JSON.parse(await readFile16(file, "utf8"));
9462
9555
  } catch {
@@ -9521,7 +9614,7 @@ function extractAbsoluteHaiveBins(text) {
9521
9614
  const p = match[2];
9522
9615
  if (!p) continue;
9523
9616
  try {
9524
- if (statSync2(p).isDirectory()) continue;
9617
+ if (statSync3(p).isDirectory()) continue;
9525
9618
  } catch {
9526
9619
  }
9527
9620
  out.add(p);
@@ -9685,23 +9778,23 @@ function runCommand(cmd, args, cwd) {
9685
9778
  }
9686
9779
 
9687
9780
  // src/commands/resolve-project.ts
9688
- import path37 from "path";
9781
+ import path38 from "path";
9689
9782
  import "commander";
9690
9783
  import { resolveProjectInfo } from "@hivelore/core";
9691
9784
  function registerResolveProject(program2) {
9692
9785
  program2.command("resolve-project").description(
9693
9786
  "Print JSON for Hivelore project root resolution (HAIVE_PROJECT_ROOT, markers, .ai layout)."
9694
9787
  ).option("-d, --dir <dir>", "working directory", process.cwd()).action((opts) => {
9695
- const info = resolveProjectInfo({ cwd: path37.resolve(opts.dir) });
9788
+ const info = resolveProjectInfo({ cwd: path38.resolve(opts.dir) });
9696
9789
  console.log(JSON.stringify({ ok: true, info }, null, 2));
9697
9790
  });
9698
9791
  }
9699
9792
 
9700
9793
  // src/commands/enforce.ts
9701
9794
  import { execFile as execFile5, execFileSync as execFileSync2, spawn as spawn4 } from "child_process";
9702
- import { existsSync as existsSync41, statSync as statSync3 } from "fs";
9795
+ import { existsSync as existsSync42, statSync as statSync4 } from "fs";
9703
9796
  import { chmod, mkdir as mkdir16, readFile as readFile18, readdir as readdir4, rm as rm2, writeFile as writeFile25 } from "fs/promises";
9704
- import path39 from "path";
9797
+ import path40 from "path";
9705
9798
  import { promisify as promisify5 } from "util";
9706
9799
  import "commander";
9707
9800
  import {
@@ -9710,6 +9803,7 @@ import {
9710
9803
  assessSensorHealth as assessSensorHealth3,
9711
9804
  sensorPromotedAtMap as sensorPromotedAtMap3,
9712
9805
  assessBootstrapState,
9806
+ detectSensorWeakening,
9713
9807
  isSensorScannablePath,
9714
9808
  findProjectRoot as findProjectRoot37,
9715
9809
  loadCodeMap as loadCodeMap7,
@@ -9721,7 +9815,7 @@ import {
9721
9815
  isRetiredMemory as isRetiredMemory2,
9722
9816
  loadConfig as loadConfig12,
9723
9817
  detectAgentContext,
9724
- loadMemoriesFromDir as loadMemoriesFromDir14,
9818
+ loadMemoriesFromDir as loadMemoriesFromDir15,
9725
9819
  loadSensorLedger as loadSensorLedger3,
9726
9820
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
9727
9821
  readRecentBriefingMarker,
@@ -9740,9 +9834,9 @@ import {
9740
9834
  } from "@hivelore/core";
9741
9835
 
9742
9836
  // src/utils/claude-hooks.ts
9743
- import { existsSync as existsSync40 } from "fs";
9837
+ import { existsSync as existsSync41 } from "fs";
9744
9838
  import { mkdir as mkdir15, readFile as readFile17, writeFile as writeFile24 } from "fs/promises";
9745
- import path38 from "path";
9839
+ import path39 from "path";
9746
9840
  var HAIVE_HOOK_TAG = "haive-enforcement";
9747
9841
  var POST_TOOL_USE_GROUP = {
9748
9842
  matcher: "Edit|Write|Bash",
@@ -9828,7 +9922,7 @@ function unpatchClaudeSettings(input) {
9828
9922
  async function installClaudeHooksAtPath(settingsPath) {
9829
9923
  let raw = null;
9830
9924
  let created = false;
9831
- if (existsSync40(settingsPath)) {
9925
+ if (existsSync41(settingsPath)) {
9832
9926
  try {
9833
9927
  raw = JSON.parse(await readFile17(settingsPath, "utf8"));
9834
9928
  } catch {
@@ -9838,12 +9932,12 @@ async function installClaudeHooksAtPath(settingsPath) {
9838
9932
  created = true;
9839
9933
  }
9840
9934
  const patched = patchClaudeSettings(raw);
9841
- await mkdir15(path38.dirname(settingsPath), { recursive: true });
9935
+ await mkdir15(path39.dirname(settingsPath), { recursive: true });
9842
9936
  await writeFile24(settingsPath, JSON.stringify(patched, null, 2) + "\n", "utf8");
9843
9937
  return { settingsPath, created };
9844
9938
  }
9845
9939
  async function uninstallClaudeHooksAtPath(settingsPath) {
9846
- if (!existsSync40(settingsPath)) {
9940
+ if (!existsSync41(settingsPath)) {
9847
9941
  return { settingsPath, created: false };
9848
9942
  }
9849
9943
  const raw = JSON.parse(await readFile17(settingsPath, "utf8"));
@@ -9854,9 +9948,9 @@ async function uninstallClaudeHooksAtPath(settingsPath) {
9854
9948
  function defaultClaudeSettingsPath(scope, projectRoot) {
9855
9949
  if (scope === "user") {
9856
9950
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
9857
- return path38.join(home, ".claude", "settings.json");
9951
+ return path39.join(home, ".claude", "settings.json");
9858
9952
  }
9859
- return path38.join(projectRoot, ".claude", "settings.local.json");
9953
+ return path39.join(projectRoot, ".claude", "settings.local.json");
9860
9954
  }
9861
9955
 
9862
9956
  // src/utils/command-sensors.ts
@@ -10043,7 +10137,7 @@ function registerEnforce(program2) {
10043
10137
  ui.success(`Removed Hivelore hooks from ${result.settingsPath}`);
10044
10138
  } else {
10045
10139
  const result = await installClaudeHooksAtPath(settingsPath);
10046
- ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path39.relative(root, result.settingsPath) || result.settingsPath})`);
10140
+ ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path40.relative(root, result.settingsPath) || result.settingsPath})`);
10047
10141
  }
10048
10142
  } catch (err) {
10049
10143
  ui.warn(`Claude Code hooks not ${opts.removeClaude ? "removed" : "installed"}: ${err instanceof Error ? err.message : String(err)}`);
@@ -10065,19 +10159,19 @@ function registerEnforce(program2) {
10065
10159
  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) => {
10066
10160
  const root = findProjectRoot37(opts.dir);
10067
10161
  const paths = resolveHaivePaths35(root);
10068
- const cacheDir = path39.join(paths.haiveDir, ".cache");
10069
- if (existsSync41(cacheDir)) {
10070
- if (opts.dryRun) ui.info(`would clean ${path39.relative(root, cacheDir)} (preserving .gitignore)`);
10162
+ const cacheDir = path40.join(paths.haiveDir, ".cache");
10163
+ if (existsSync42(cacheDir)) {
10164
+ if (opts.dryRun) ui.info(`would clean ${path40.relative(root, cacheDir)} (preserving .gitignore)`);
10071
10165
  else {
10072
10166
  const removed = await cleanupCacheDir(cacheDir);
10073
- ui.success(`cleaned ${path39.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
10167
+ ui.success(`cleaned ${path40.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
10074
10168
  }
10075
10169
  }
10076
- if (existsSync41(paths.runtimeDir)) {
10077
- if (opts.dryRun) ui.info(`would clean ${path39.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
10170
+ if (existsSync42(paths.runtimeDir)) {
10171
+ if (opts.dryRun) ui.info(`would clean ${path40.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
10078
10172
  else {
10079
10173
  const removed = await cleanupRuntimeDir(paths.runtimeDir);
10080
- ui.success(`cleaned ${path39.relative(root, paths.runtimeDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
10174
+ ui.success(`cleaned ${path40.relative(root, paths.runtimeDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
10081
10175
  }
10082
10176
  }
10083
10177
  });
@@ -10121,7 +10215,7 @@ function registerEnforce(program2) {
10121
10215
  const root = resolveRoot(opts.dir, payload);
10122
10216
  if (!root) return;
10123
10217
  const paths = resolveHaivePaths35(root);
10124
- if (!existsSync41(paths.haiveDir)) return;
10218
+ if (!existsSync42(paths.haiveDir)) return;
10125
10219
  await mkdir16(paths.runtimeDir, { recursive: true });
10126
10220
  const sessionId = opts.sessionId ?? payload.session_id;
10127
10221
  const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this Hivelore-initialized project.";
@@ -10184,7 +10278,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
10184
10278
  const root = resolveRoot(opts.dir, payload);
10185
10279
  if (!root) return;
10186
10280
  const paths = resolveHaivePaths35(root);
10187
- if (!existsSync41(paths.haiveDir)) return;
10281
+ if (!existsSync42(paths.haiveDir)) return;
10188
10282
  if (!isWriteLikeTool(payload)) return;
10189
10283
  const config = await loadConfig12(paths);
10190
10284
  if (config.enforcement?.requireBriefingFirst === false) return;
@@ -10246,10 +10340,26 @@ function emitPreToolUseContext(text) {
10246
10340
  })
10247
10341
  );
10248
10342
  }
10343
+ async function checkPostIncidentScaffolds(paths) {
10344
+ try {
10345
+ const gaps = await collectScaffoldLoopGaps(paths);
10346
+ if (gaps.length === 0) return [];
10347
+ return [{
10348
+ severity: "warn",
10349
+ code: "post-incident-test-unarmed",
10350
+ 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" : "") + ".",
10351
+ fix: "Fill the pending assertion, run the test, then arm it: the scaffold header contains the exact `hivelore sensors propose --kind test` command.",
10352
+ memory_ids: [...new Set(gaps.map((g) => g.memory_id))].slice(0, 10),
10353
+ impact: 0
10354
+ }];
10355
+ } catch {
10356
+ return [];
10357
+ }
10358
+ }
10249
10359
  async function buildFinishReport(dir) {
10250
10360
  const root = findProjectRoot37(dir);
10251
10361
  const paths = resolveHaivePaths35(root);
10252
- const initialized = existsSync41(paths.haiveDir);
10362
+ const initialized = existsSync42(paths.haiveDir);
10253
10363
  const config = initialized ? await loadConfig12(paths) : {};
10254
10364
  const mode = config.enforcement?.mode ?? "strict";
10255
10365
  const findings = [];
@@ -10270,6 +10380,7 @@ async function buildFinishReport(dir) {
10270
10380
  });
10271
10381
  }
10272
10382
  findings.push(...await checkFailureCapture(paths, config));
10383
+ findings.push(...await checkPostIncidentScaffolds(paths));
10273
10384
  findings.push(...await checkBootstrapComplete(paths, config, true));
10274
10385
  const status = await getGitSyncStatus(root);
10275
10386
  if (!status.available) {
@@ -10441,8 +10552,8 @@ async function buildFinishReport(dir) {
10441
10552
  async function checkFailureCapture(paths, config) {
10442
10553
  const gate = config.enforcement?.failureCaptureGate ?? "warn";
10443
10554
  if (gate === "off") return [];
10444
- const obsFile = path39.join(paths.haiveDir, ".cache", "observations.jsonl");
10445
- if (!existsSync41(obsFile)) return [];
10555
+ const obsFile = path40.join(paths.haiveDir, ".cache", "observations.jsonl");
10556
+ if (!existsSync42(obsFile)) return [];
10446
10557
  const failures = [];
10447
10558
  try {
10448
10559
  const raw = await readFile18(obsFile, "utf8");
@@ -10459,7 +10570,7 @@ async function checkFailureCapture(paths, config) {
10459
10570
  return [];
10460
10571
  }
10461
10572
  if (failures.length === 0) return [];
10462
- const memories = existsSync41(paths.memoriesDir) ? await loadMemoriesFromDir14(paths.memoriesDir) : [];
10573
+ const memories = existsSync42(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
10463
10574
  const captureTimes = memories.filter(({ memory: memory2 }) => ["attempt", "gotcha"].includes(memory2.frontmatter.type)).map(({ memory: memory2 }) => memory2.frontmatter.created_at);
10464
10575
  const uncaptured = findUncapturedFailures(failures, captureTimes);
10465
10576
  if (uncaptured.length === 0) {
@@ -10494,7 +10605,7 @@ function finishReport(root, initialized, mode, findings, config) {
10494
10605
  async function runWithEnforcement(command, args, opts) {
10495
10606
  const root = findProjectRoot37(opts.dir);
10496
10607
  const paths = resolveHaivePaths35(root);
10497
- if (!existsSync41(paths.haiveDir)) {
10608
+ if (!existsSync42(paths.haiveDir)) {
10498
10609
  ui.error(`No .ai/ found at ${root}. Run \`hivelore init\` first.`);
10499
10610
  process.exit(1);
10500
10611
  }
@@ -10513,7 +10624,7 @@ async function runWithEnforcement(command, args, opts) {
10513
10624
  process.exit(2);
10514
10625
  }
10515
10626
  ui.info(`Hivelore briefing marker created for wrapped agent session: ${sessionId}`);
10516
- ui.info(`Briefing written to ${path39.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
10627
+ ui.info(`Briefing written to ${path40.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
10517
10628
  const child = spawn4(command, args, {
10518
10629
  cwd: root,
10519
10630
  stdio: "inherit",
@@ -10565,9 +10676,9 @@ async function writeWrapperBriefing(paths, sessionId, task) {
10565
10676
  source: "haive-run",
10566
10677
  memoryIds: briefing.memories.map((m) => m.id)
10567
10678
  });
10568
- const dir = path39.join(paths.runtimeDir, "enforcement", "briefings");
10679
+ const dir = path40.join(paths.runtimeDir, "enforcement", "briefings");
10569
10680
  await mkdir16(dir, { recursive: true });
10570
- const file = path39.join(dir, `${sessionId}.md`);
10681
+ const file = path40.join(dir, `${sessionId}.md`);
10571
10682
  const parts = [
10572
10683
  "# Hivelore Briefing",
10573
10684
  "",
@@ -10605,7 +10716,7 @@ async function checkBootstrapComplete(paths, config, productionCodeChanged) {
10605
10716
  projectContextRaw = await readFile18(paths.projectContext, "utf8");
10606
10717
  } catch {
10607
10718
  }
10608
- const memories = existsSync41(paths.memoriesDir) ? await loadMemoriesFromDir14(paths.memoriesDir) : [];
10719
+ const memories = existsSync42(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
10609
10720
  const codeMap = await loadCodeMap7(paths);
10610
10721
  const codeFiles = codeMap ? Object.keys(codeMap.files) : [];
10611
10722
  let existingModules = [];
@@ -10637,7 +10748,7 @@ ${renderBootstrapChecklist(assessment)}`,
10637
10748
  async function buildEnforcementReport(dir, stage, sessionId) {
10638
10749
  const root = findProjectRoot37(dir);
10639
10750
  const paths = resolveHaivePaths35(root);
10640
- const initialized = existsSync41(paths.haiveDir);
10751
+ const initialized = existsSync42(paths.haiveDir);
10641
10752
  const config = initialized ? await loadConfig12(paths) : {};
10642
10753
  if (initialized) {
10643
10754
  await applyLightweightRepairs(root, paths);
@@ -10671,7 +10782,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
10671
10782
  findings: [{ severity: "info", code: "enforcement-off", message: "Hivelore enforcement is disabled." }]
10672
10783
  });
10673
10784
  }
10674
- findings.push(...await inspectIntegrationVersions(root, "0.37.0"));
10785
+ findings.push(...await inspectIntegrationVersions(root, "0.39.0"));
10675
10786
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
10676
10787
  const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
10677
10788
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent Hivelore briefing marker exists." } : {
@@ -10742,10 +10853,14 @@ async function buildEnforcementReport(dir, stage, sessionId) {
10742
10853
  }
10743
10854
  const score = buildScore(effectiveFindings, config.enforcement?.scoreThreshold);
10744
10855
  if (score.score < score.threshold) {
10856
+ const topPenalties = effectiveFindings.map((f) => ({
10857
+ code: f.code,
10858
+ penalty: f.severity === "error" ? f.impact ?? 25 : f.severity === "warn" ? f.impact ?? 8 : 0
10859
+ })).filter((p) => p.penalty > 0).sort((a, b) => b.penalty - a.penalty).slice(0, 3);
10745
10860
  effectiveFindings = [...effectiveFindings, {
10746
10861
  severity: "error",
10747
10862
  code: "enforcement-score-below-threshold",
10748
- message: `Enforcement score ${score.score}% is below required threshold ${score.threshold}%.`,
10863
+ 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(", ")}` : "") + ".",
10749
10864
  fix: "Load the relevant briefing, address policy findings, then rerun `hivelore enforce check`.",
10750
10865
  impact: 0
10751
10866
  }];
@@ -10786,8 +10901,8 @@ function withCategories(report) {
10786
10901
  async function hasRecentSessionRecap(paths) {
10787
10902
  const handoffAge = await handoffAgeMs(paths.root);
10788
10903
  if (handoffAge !== null && handoffAge < SESSION_RECAP_TTL_MS) return true;
10789
- if (!existsSync41(paths.memoriesDir)) return false;
10790
- const all = await loadMemoriesFromDir14(paths.memoriesDir);
10904
+ if (!existsSync42(paths.memoriesDir)) return false;
10905
+ const all = await loadMemoriesFromDir15(paths.memoriesDir);
10791
10906
  return all.some(({ memory: memory2 }) => {
10792
10907
  const fm = memory2.frontmatter;
10793
10908
  const freshnessDate = fm.verified_at ?? fm.created_at;
@@ -10795,8 +10910,8 @@ async function hasRecentSessionRecap(paths) {
10795
10910
  });
10796
10911
  }
10797
10912
  async function verifyMemoryPolicy(paths, config) {
10798
- if (!existsSync41(paths.memoriesDir)) return [];
10799
- const all = await loadMemoriesFromDir14(paths.memoriesDir);
10913
+ if (!existsSync42(paths.memoriesDir)) return [];
10914
+ const all = await loadMemoriesFromDir15(paths.memoriesDir);
10800
10915
  const findings = [];
10801
10916
  const staleImportant = [];
10802
10917
  let verified = 0;
@@ -10833,12 +10948,12 @@ async function verifyMemoryPolicy(paths, config) {
10833
10948
  return findings;
10834
10949
  }
10835
10950
  async function verifyDecisionCoverage(paths, stage, sessionId) {
10836
- if (!existsSync41(paths.memoriesDir)) return [];
10951
+ if (!existsSync42(paths.memoriesDir)) return [];
10837
10952
  const changedFiles = (await getChangedFiles(paths.root, stage)).filter((f) => !isGeneratedArtifact(f));
10838
10953
  if (changedFiles.length === 0) {
10839
10954
  return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
10840
10955
  }
10841
- const all = await loadMemoriesFromDir14(paths.memoriesDir);
10956
+ const all = await loadMemoriesFromDir15(paths.memoriesDir);
10842
10957
  const changedSet = new Set(changedFiles);
10843
10958
  const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention"]);
10844
10959
  const relevant = all.filter(({ memory: memory2 }) => {
@@ -10867,7 +10982,7 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
10867
10982
  const consulted = new Set(marker?.memory_ids ?? []);
10868
10983
  const missing = relevant.filter(({ memory: memory2, filePath }) => {
10869
10984
  if (consulted.has(memory2.frontmatter.id)) return false;
10870
- if (changedSet.has(path39.relative(paths.root, filePath))) return false;
10985
+ if (changedSet.has(path40.relative(paths.root, filePath))) return false;
10871
10986
  return true;
10872
10987
  }).map(({ memory: memory2 }) => memory2);
10873
10988
  if (missing.length === 0) {
@@ -10909,15 +11024,27 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
10909
11024
  }];
10910
11025
  }
10911
11026
  async function runPrecommitPolicy(paths, gate, stage) {
11027
+ const snapshot = await getPolicyDiffSnapshot(paths.root, stage);
11028
+ const weakenings = detectSensorWeakening(snapshot.diff);
11029
+ const weakeningFindings = weakenings.length > 0 ? [{
11030
+ severity: "warn",
11031
+ code: "sensor-weakened",
11032
+ 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" : "") + ".",
11033
+ fix: "If the demotion/removal is intentional, say so in the commit message; otherwise restore the sensor (`hivelore sensors list` shows the current state).",
11034
+ memory_ids: [...new Set(weakenings.map((w) => w.memory_id))].slice(0, 10),
11035
+ impact: 8
11036
+ }] : [];
10912
11037
  if (gate === "off") {
10913
- return [{ severity: "info", code: "precommit-policy-off", message: "Anti-pattern gate is disabled (enforcement.antiPatternGate=off)." }];
11038
+ return [
11039
+ { severity: "info", code: "precommit-policy-off", message: "Anti-pattern gate is disabled (enforcement.antiPatternGate=off)." },
11040
+ ...weakeningFindings
11041
+ ];
10914
11042
  }
10915
- const snapshot = await getPolicyDiffSnapshot(paths.root, stage);
10916
11043
  const touchedPaths = snapshot.paths;
10917
11044
  if (touchedPaths.length === 0) {
10918
11045
  const code = stage === "ci" ? "no-ci-diff-changes" : "no-staged-changes";
10919
11046
  const message = stage === "ci" ? "No changed files found for CI policy diff." : "No staged changes found for pre-commit policy.";
10920
- return [{ severity: "info", code, message }];
11047
+ return [{ severity: "info", code, message }, ...weakeningFindings];
10921
11048
  }
10922
11049
  const { block_on, anchored_blocks } = antiPatternGateParams2(gate);
10923
11050
  const result = await preCommitCheck({
@@ -10932,7 +11059,7 @@ async function runPrecommitPolicy(paths, gate, stage) {
10932
11059
  (w) => w.level === "review" && !w.reasons.includes("sensor")
10933
11060
  );
10934
11061
  const REVIEW_SEEN_TTL_MS = 24 * 60 * 60 * 1e3;
10935
- const reviewSeenFile = path39.join(paths.runtimeDir, "enforcement", "review-seen.json");
11062
+ const reviewSeenFile = path40.join(paths.runtimeDir, "enforcement", "review-seen.json");
10936
11063
  let reviewSeen = {};
10937
11064
  try {
10938
11065
  reviewSeen = JSON.parse(await readFile18(reviewSeenFile, "utf8"));
@@ -10959,7 +11086,7 @@ async function runPrecommitPolicy(paths, gate, stage) {
10959
11086
  for (const [id, at] of Object.entries(reviewSeen)) {
10960
11087
  if (!Number.isFinite(Date.parse(at)) || now - Date.parse(at) >= REVIEW_SEEN_TTL_MS) delete reviewSeen[id];
10961
11088
  }
10962
- await mkdir16(path39.dirname(reviewSeenFile), { recursive: true });
11089
+ await mkdir16(path40.dirname(reviewSeenFile), { recursive: true });
10963
11090
  await writeFile25(reviewSeenFile, JSON.stringify(reviewSeen, null, 2), "utf8");
10964
11091
  } catch {
10965
11092
  }
@@ -10972,7 +11099,8 @@ async function runPrecommitPolicy(paths, gate, stage) {
10972
11099
  message: `${stage === "ci" ? "CI" : "Pre-commit"} policy passed for ${touchedPaths.length} changed file(s).`
10973
11100
  },
10974
11101
  ...reviewFinding,
10975
- ...sensorFindings
11102
+ ...sensorFindings,
11103
+ ...weakeningFindings
10976
11104
  ];
10977
11105
  }
10978
11106
  const blockingWarnings = result.warnings.filter((w) => w.level === "blocking");
@@ -10987,13 +11115,14 @@ async function runPrecommitPolicy(paths, gate, stage) {
10987
11115
  impact: 45
10988
11116
  },
10989
11117
  ...reviewFinding,
10990
- ...sensorFindings
11118
+ ...sensorFindings,
11119
+ ...weakeningFindings
10991
11120
  ];
10992
11121
  }
10993
11122
  async function runSensorGate(paths, diff, stage) {
10994
- if (!diff || !existsSync41(paths.memoriesDir)) return [];
11123
+ if (!diff || !existsSync42(paths.memoriesDir)) return [];
10995
11124
  try {
10996
- const loaded = await loadMemoriesFromDir14(paths.memoriesDir);
11125
+ const loaded = await loadMemoriesFromDir15(paths.memoriesDir);
10997
11126
  const scannable = loaded.map((l) => l.memory).filter((m) => Boolean(m.frontmatter.sensor) && !isRetiredMemory2(m.frontmatter, m.body));
10998
11127
  if (scannable.length === 0) return [];
10999
11128
  const targets = sensorTargetsFromDiff(diff).filter((t) => isSensorScannablePath(t.path));
@@ -11125,8 +11254,14 @@ command: ${run.command} (exit ${run.exit_code}, ${run.duration_ms}ms)${outputBlo
11125
11254
  });
11126
11255
  }
11127
11256
  return findings;
11128
- } catch {
11129
- return [];
11257
+ } catch (err) {
11258
+ return [{
11259
+ severity: "warn",
11260
+ code: "sensor-gate-errored",
11261
+ message: `The sensor gate itself errored, so NO sensors were evaluated on this diff: ` + `${err instanceof Error ? err.message : String(err)}`.slice(0, 400),
11262
+ fix: "Run `hivelore sensors check` to reproduce, and `hivelore doctor` for setup drift. The lessons' protection is OFF until this is fixed.",
11263
+ impact: 5
11264
+ }];
11130
11265
  }
11131
11266
  }
11132
11267
  async function findGeneratedArtifacts(paths) {
@@ -11159,16 +11294,16 @@ async function cleanupRuntimeDir(runtimeDir) {
11159
11294
  for (const entry of entries) {
11160
11295
  if (entry.name === ".gitignore" || entry.name === "README.md") continue;
11161
11296
  if (entry.name === "enforcement") {
11162
- removed += await cleanupEnforcementDir(path39.join(runtimeDir, entry.name));
11297
+ removed += await cleanupEnforcementDir(path40.join(runtimeDir, entry.name));
11163
11298
  continue;
11164
11299
  }
11165
- await rm2(path39.join(runtimeDir, entry.name), { recursive: true, force: true });
11300
+ await rm2(path40.join(runtimeDir, entry.name), { recursive: true, force: true });
11166
11301
  removed++;
11167
11302
  }
11168
- await writeFile25(path39.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
11169
- if (!existsSync41(path39.join(runtimeDir, "README.md"))) {
11303
+ await writeFile25(path40.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
11304
+ if (!existsSync42(path40.join(runtimeDir, "README.md"))) {
11170
11305
  await writeFile25(
11171
- path39.join(runtimeDir, "README.md"),
11306
+ path40.join(runtimeDir, "README.md"),
11172
11307
  "# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. Hivelore cleanup preserves briefing markers so enforcement state remains valid.\n",
11173
11308
  "utf8"
11174
11309
  );
@@ -11181,10 +11316,10 @@ async function cleanupCacheDir(cacheDir) {
11181
11316
  const entries = await readdir4(cacheDir, { withFileTypes: true }).catch(() => []);
11182
11317
  for (const entry of entries) {
11183
11318
  if (entry.name === ".gitignore") continue;
11184
- await rm2(path39.join(cacheDir, entry.name), { recursive: true, force: true });
11319
+ await rm2(path40.join(cacheDir, entry.name), { recursive: true, force: true });
11185
11320
  removed++;
11186
11321
  }
11187
- await writeFile25(path39.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
11322
+ await writeFile25(path40.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
11188
11323
  return removed;
11189
11324
  }
11190
11325
  async function cleanupEnforcementDir(enforcementDir) {
@@ -11192,7 +11327,7 @@ async function cleanupEnforcementDir(enforcementDir) {
11192
11327
  const entries = await readdir4(enforcementDir, { withFileTypes: true }).catch(() => []);
11193
11328
  for (const entry of entries) {
11194
11329
  if (entry.name === "briefings") continue;
11195
- await rm2(path39.join(enforcementDir, entry.name), { recursive: true, force: true });
11330
+ await rm2(path40.join(enforcementDir, entry.name), { recursive: true, force: true });
11196
11331
  removed++;
11197
11332
  }
11198
11333
  return removed;
@@ -11210,8 +11345,8 @@ async function inspectIntegrationVersions(root, expectedVersion) {
11210
11345
  const missingBins = /* @__PURE__ */ new Map();
11211
11346
  const staleBins = /* @__PURE__ */ new Map();
11212
11347
  for (const rel of files) {
11213
- const file = path39.join(root, rel);
11214
- if (!existsSync41(file)) continue;
11348
+ const file = path40.join(root, rel);
11349
+ if (!existsSync42(file)) continue;
11215
11350
  const text = await readFile18(file, "utf8").catch(() => "");
11216
11351
  for (const bin of extractAbsoluteHaiveBins2(text)) {
11217
11352
  const version = versionForBinary2(bin);
@@ -11258,7 +11393,7 @@ function extractAbsoluteHaiveBins2(text) {
11258
11393
  const p = match[2];
11259
11394
  if (!p) continue;
11260
11395
  try {
11261
- if (statSync3(p).isDirectory()) continue;
11396
+ if (statSync4(p).isDirectory()) continue;
11262
11397
  } catch {
11263
11398
  }
11264
11399
  out.add(p);
@@ -11328,7 +11463,7 @@ async function resolveCiDiffRange(root) {
11328
11463
  }
11329
11464
  async function resolveGithubEventRange(root) {
11330
11465
  const eventPath = process.env.GITHUB_EVENT_PATH;
11331
- if (!eventPath || !existsSync41(eventPath)) return null;
11466
+ if (!eventPath || !existsSync42(eventPath)) return null;
11332
11467
  try {
11333
11468
  const event = JSON.parse(await readFile18(eventPath, "utf8"));
11334
11469
  const prBase = cleanGitSha(event.pull_request?.base?.sha);
@@ -11433,7 +11568,7 @@ function isShippablePath(file) {
11433
11568
  }
11434
11569
  var CI_SKIP_DIRECTIVE = /\[skip ci\]|\[ci skip\]|\[no ci\]|\[skip actions\]|\*\*\*NO_CI\*\*\*|skip-checks: *true/i;
11435
11570
  async function checkCommitMessageSkipCi(root, msgfile) {
11436
- const file = path39.isAbsolute(msgfile) ? msgfile : path39.join(root, msgfile);
11571
+ const file = path40.isAbsolute(msgfile) ? msgfile : path40.join(root, msgfile);
11437
11572
  const raw = await readFile18(file, "utf8").catch(() => "");
11438
11573
  const cleaned = raw.split("\n").filter((line) => !line.startsWith("#")).join("\n");
11439
11574
  if (!CI_SKIP_DIRECTIVE.test(cleaned)) return { block: false, message: "" };
@@ -11462,7 +11597,7 @@ async function inspectReleaseVersionState(root, upstream) {
11462
11597
  }
11463
11598
  async function readPackageVersion(root, relPath) {
11464
11599
  try {
11465
- const data = JSON.parse(await readFile18(path39.join(root, relPath), "utf8"));
11600
+ const data = JSON.parse(await readFile18(path40.join(root, relPath), "utf8"));
11466
11601
  return typeof data.version === "string" ? data.version : void 0;
11467
11602
  } catch {
11468
11603
  return void 0;
@@ -11650,8 +11785,8 @@ function buildScore(findings, threshold = 80) {
11650
11785
  };
11651
11786
  }
11652
11787
  async function installGitEnforcement(root) {
11653
- const hooksDir = path39.join(root, ".git", "hooks");
11654
- if (!existsSync41(path39.join(root, ".git"))) {
11788
+ const hooksDir = path40.join(root, ".git", "hooks");
11789
+ if (!existsSync42(path40.join(root, ".git"))) {
11655
11790
  ui.warn("No .git directory found; git enforcement hooks skipped.");
11656
11791
  return;
11657
11792
  }
@@ -11707,8 +11842,8 @@ _hivelore sync --quiet --since ORIG_HEAD || true
11707
11842
  }
11708
11843
  ];
11709
11844
  for (const hook of hooks) {
11710
- const file = path39.join(hooksDir, hook.name);
11711
- if (existsSync41(file)) {
11845
+ const file = path40.join(hooksDir, hook.name);
11846
+ if (existsSync42(file)) {
11712
11847
  const current = await readFile18(file, "utf8").catch(() => "");
11713
11848
  if (current.includes(ENFORCE_HOOK_MARKER)) {
11714
11849
  await writeFile25(file, hook.body, "utf8");
@@ -11725,10 +11860,10 @@ ${hook.body}`, "utf8");
11725
11860
  ui.success("Installed git hooks: pre-commit, pre-push, commit-msg (blocking) + post-merge, post-rewrite (sync)");
11726
11861
  }
11727
11862
  async function installCiEnforcement(root) {
11728
- const workflowPath = path39.join(root, ".github", "workflows", "haive-enforcement.yml");
11729
- await mkdir16(path39.dirname(workflowPath), { recursive: true });
11863
+ const workflowPath = path40.join(root, ".github", "workflows", "haive-enforcement.yml");
11864
+ await mkdir16(path40.dirname(workflowPath), { recursive: true });
11730
11865
  const workflow = renderCiEnforcementWorkflow();
11731
- if (existsSync41(workflowPath)) {
11866
+ if (existsSync42(workflowPath)) {
11732
11867
  const existing = await readFile18(workflowPath, "utf8");
11733
11868
  const start = "# haive:enforcement-workflow:start";
11734
11869
  const end = "# haive:enforcement-workflow:end";
@@ -11736,14 +11871,14 @@ async function installCiEnforcement(root) {
11736
11871
  const endAt = existing.indexOf(end);
11737
11872
  if (startAt >= 0 && endAt > startAt) {
11738
11873
  await writeFile25(workflowPath, existing.slice(0, startAt) + workflow + existing.slice(endAt + end.length), "utf8");
11739
- ui.success(`Updated ${path39.relative(root, workflowPath)} managed block`);
11874
+ ui.success(`Updated ${path40.relative(root, workflowPath)} managed block`);
11740
11875
  } else {
11741
11876
  ui.info("GitHub Actions enforcement workflow already exists without Hivelore markers \u2014 preserved");
11742
11877
  }
11743
11878
  return;
11744
11879
  }
11745
11880
  await writeFile25(workflowPath, workflow, "utf8");
11746
- ui.success(`Created ${path39.relative(root, workflowPath)}`);
11881
+ ui.success(`Created ${path40.relative(root, workflowPath)}`);
11747
11882
  }
11748
11883
  function renderCiEnforcementWorkflow() {
11749
11884
  return `# haive:enforcement-workflow:start
@@ -11946,15 +12081,15 @@ function extractToolPaths(payload, root) {
11946
12081
  }
11947
12082
  function normalizeToolPath(file, root) {
11948
12083
  const normalized = file.replace(/\\/g, "/");
11949
- if (!path39.isAbsolute(normalized)) return normalized.replace(/^\.\//, "");
11950
- return path39.relative(root, normalized).replace(/\\/g, "/");
12084
+ if (!path40.isAbsolute(normalized)) return normalized.replace(/^\.\//, "");
12085
+ return path40.relative(root, normalized).replace(/\\/g, "/");
11951
12086
  }
11952
12087
  async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
11953
- if (!existsSync41(paths.memoriesDir)) return [];
12088
+ if (!existsSync42(paths.memoriesDir)) return [];
11954
12089
  const marker = await readRecentBriefingMarker(paths, sessionId);
11955
12090
  const consulted = new Set(marker?.memory_ids ?? []);
11956
12091
  const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention", "attempt"]);
11957
- const all = await loadMemoriesFromDir14(paths.memoriesDir);
12092
+ const all = await loadMemoriesFromDir15(paths.memoriesDir);
11958
12093
  return all.filter(({ memory: memory2 }) => {
11959
12094
  const fm = memory2.frontmatter;
11960
12095
  if (!policyTypes.has(fm.type)) return false;
@@ -11995,7 +12130,7 @@ async function readStdin2(maxBytes) {
11995
12130
  }
11996
12131
  var ATOMIC_STAGE_EXCLUDE = ["/.usage/", "/.runtime/", "/.cache/"];
11997
12132
  async function stageResyncedArtifacts(root, paths) {
11998
- const aiRel = path39.relative(root, paths.haiveDir);
12133
+ const aiRel = path40.relative(root, paths.haiveDir);
11999
12134
  const out = await runCommand2("git", ["diff", "--name-only", "--", aiRel], root).catch(() => "");
12000
12135
  const toStage = out.split("\n").map((line) => line.trim()).filter(Boolean).filter((file) => !ATOMIC_STAGE_EXCLUDE.some((excl) => `/${file}`.includes(excl)));
12001
12136
  if (toStage.length === 0) return;
@@ -12022,9 +12157,9 @@ function runCommand2(cmd, args, cwd) {
12022
12157
  }
12023
12158
 
12024
12159
  // src/commands/release.ts
12025
- import { existsSync as existsSync42 } from "fs";
12160
+ import { existsSync as existsSync43 } from "fs";
12026
12161
  import { readFile as readFile19, writeFile as writeFile26 } from "fs/promises";
12027
- import path40 from "path";
12162
+ import path41 from "path";
12028
12163
  import "commander";
12029
12164
  import { findProjectRoot as findProjectRoot38 } from "@hivelore/core";
12030
12165
  import { execFile as execFile6 } from "child_process";
@@ -12038,7 +12173,7 @@ var VERSION_FILES2 = [
12038
12173
  "packages/embeddings/package.json"
12039
12174
  ];
12040
12175
  async function readCurrentVersion(root) {
12041
- const pkg = JSON.parse(await readFile19(path40.join(root, "package.json"), "utf8"));
12176
+ const pkg = JSON.parse(await readFile19(path41.join(root, "package.json"), "utf8"));
12042
12177
  if (!pkg.version) throw new Error("Root package.json has no version field.");
12043
12178
  return pkg.version;
12044
12179
  }
@@ -12059,8 +12194,8 @@ function registerRelease(program2) {
12059
12194
  const current = await readCurrentVersion(root);
12060
12195
  const next = nextVersion(current, spec);
12061
12196
  for (const rel of VERSION_FILES2) {
12062
- const file = path40.join(root, rel);
12063
- if (!existsSync42(file)) {
12197
+ const file = path41.join(root, rel);
12198
+ if (!existsSync43(file)) {
12064
12199
  ui.warn(`skip ${rel} (missing)`);
12065
12200
  continue;
12066
12201
  }
@@ -12073,8 +12208,8 @@ function registerRelease(program2) {
12073
12208
  }
12074
12209
  await writeFile26(file, updated, "utf8");
12075
12210
  }
12076
- const changelog = path40.join(root, "CHANGELOG.md");
12077
- if (existsSync42(changelog)) {
12211
+ const changelog = path41.join(root, "CHANGELOG.md");
12212
+ if (existsSync43(changelog)) {
12078
12213
  const raw = await readFile19(changelog, "utf8");
12079
12214
  const heading = `## [${next}]${opts.title ? ` \u2014 ${opts.title}` : ""}`;
12080
12215
  if (!raw.includes(`## [${next}]`)) {
@@ -12097,8 +12232,8 @@ ${heading}
12097
12232
  const root = findProjectRoot38(opts.dir);
12098
12233
  const version = await readCurrentVersion(root);
12099
12234
  for (const rel of VERSION_FILES2.slice(1)) {
12100
- const file = path40.join(root, rel);
12101
- if (!existsSync42(file)) continue;
12235
+ const file = path41.join(root, rel);
12236
+ if (!existsSync43(file)) continue;
12102
12237
  const v = JSON.parse(await readFile19(file, "utf8")).version;
12103
12238
  if (v !== version) {
12104
12239
  ui.error(`${rel} is at ${v}, root at ${version} \u2014 lockstep broken; run \`hivelore release bump\` first.`);
@@ -12144,9 +12279,9 @@ function registerRun(program2) {
12144
12279
 
12145
12280
  // src/commands/sensors.ts
12146
12281
  import { execFile as execFile7 } from "child_process";
12147
- import { existsSync as existsSync43 } from "fs";
12282
+ import { existsSync as existsSync44 } from "fs";
12148
12283
  import { chmod as chmod2, mkdir as mkdir17, readFile as readFile20, writeFile as writeFile27 } from "fs/promises";
12149
- import path41 from "path";
12284
+ import path42 from "path";
12150
12285
  import { promisify as promisify7 } from "util";
12151
12286
  import "commander";
12152
12287
  import {
@@ -12159,11 +12294,13 @@ import {
12159
12294
  judgeProposedSensor,
12160
12295
  loadConfig as loadConfig13,
12161
12296
  loadSensorLedger as loadSensorLedger4,
12162
- loadMemoriesFromDir as loadMemoriesFromDir15,
12297
+ loadMemoriesFromDir as loadMemoriesFromDir16,
12298
+ normalizeFramework,
12163
12299
  parseLessonFields,
12164
12300
  recordPreventionHits as recordPreventionHits2,
12165
12301
  resolveHaivePaths as resolveHaivePaths36,
12166
12302
  runSensors as runSensors2,
12303
+ buildProposeCommand,
12167
12304
  scaffoldPostIncidentTest,
12168
12305
  selectCommandSensors as selectCommandSensors2,
12169
12306
  TEST_FRAMEWORKS,
@@ -12210,7 +12347,7 @@ function registerSensors(program2) {
12210
12347
  const root = findProjectRoot39(opts.dir);
12211
12348
  const paths = resolveHaivePaths36(root);
12212
12349
  const memories = await runnableSensorMemories(paths);
12213
- const diff = opts.diffFile ? await readFile20(path41.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
12350
+ const diff = opts.diffFile ? await readFile20(path42.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
12214
12351
  const targets = scannableSensorTargets(diff);
12215
12352
  const hits = runSensors2(memories, targets);
12216
12353
  const config = await loadConfig13(paths);
@@ -12342,7 +12479,7 @@ function registerSensors(program2) {
12342
12479
  }
12343
12480
  const root = findProjectRoot39(opts.dir);
12344
12481
  const paths = resolveHaivePaths36(root);
12345
- const loaded = existsSync43(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
12482
+ const loaded = existsSync44(paths.memoriesDir) ? await loadMemoriesFromDir16(paths.memoriesDir) : [];
12346
12483
  const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
12347
12484
  if (!found) {
12348
12485
  ui.error(`No memory found with id ${id}`);
@@ -12409,7 +12546,7 @@ function registerSensors(program2) {
12409
12546
  return;
12410
12547
  }
12411
12548
  const root2 = findProjectRoot39(opts.dir);
12412
- const { proposeSensor } = await import("./server-J6TDFG2C.js");
12549
+ const { proposeSensor } = await import("./server-47VOVJJT.js");
12413
12550
  const out = await proposeSensor(
12414
12551
  {
12415
12552
  memory_id: id,
@@ -12453,7 +12590,7 @@ function registerSensors(program2) {
12453
12590
  }
12454
12591
  const root = findProjectRoot39(opts.dir);
12455
12592
  const paths = resolveHaivePaths36(root);
12456
- const loaded = existsSync43(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
12593
+ const loaded = existsSync44(paths.memoriesDir) ? await loadMemoriesFromDir16(paths.memoriesDir) : [];
12457
12594
  const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
12458
12595
  if (!found) {
12459
12596
  ui.error(`No memory found with id ${id}`);
@@ -12497,56 +12634,78 @@ function registerSensors(program2) {
12497
12634
  ui.info(
12498
12635
  `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}`)
12499
12636
  );
12637
+ if (found.memory.frontmatter.scope === "personal") {
12638
+ ui.warn(
12639
+ `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}`
12640
+ );
12641
+ }
12500
12642
  });
12501
12643
  sensors.command("scaffold").description(
12502
12644
  "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"
12503
12645
  ).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) => {
12504
12646
  const root = findProjectRoot39(opts.dir);
12505
12647
  const paths = resolveHaivePaths36(root);
12506
- const loaded = existsSync43(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
12648
+ const loaded = existsSync44(paths.memoriesDir) ? await loadMemoriesFromDir16(paths.memoriesDir) : [];
12507
12649
  const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
12508
12650
  if (!found) {
12509
12651
  ui.error(`No memory found with id ${id}`);
12510
12652
  process.exitCode = 1;
12511
12653
  return;
12512
12654
  }
12513
- const framework = opts.framework ? normalizeFramework(opts.framework) : await detectTestFramework(root);
12514
- if (!framework) {
12655
+ const fm = found.memory.frontmatter;
12656
+ const forced = opts.framework ? normalizeFramework(opts.framework) : null;
12657
+ if (opts.framework && !forced) {
12515
12658
  ui.error(`Unknown --framework "${opts.framework}". Use one of: ${TEST_FRAMEWORKS.join(", ")}.`);
12516
12659
  process.exitCode = 1;
12517
12660
  return;
12518
12661
  }
12662
+ const allGroups = await detectTestFrameworksForAnchors(root, fm.anchor.paths ?? []);
12663
+ const groups = opts.out ? allGroups.slice(0, 1) : allGroups;
12519
12664
  const fields = parseLessonFields(found.memory.body);
12520
- const fm = found.memory.frontmatter;
12521
- const scaffold = scaffoldPostIncidentTest(
12522
- {
12523
- memoryId: id,
12524
- title: fields.title || id,
12525
- whyFailed: fields.whyFailed,
12526
- instead: fields.instead,
12527
- incident: fm.sensor?.incident,
12528
- paths: fm.anchor.paths
12529
- },
12530
- { framework, outPath: opts.out }
12665
+ const lesson = {
12666
+ memoryId: id,
12667
+ title: fields.title || id,
12668
+ whyFailed: fields.whyFailed,
12669
+ instead: fields.instead,
12670
+ incident: fm.sensor?.incident,
12671
+ paths: fm.anchor.paths
12672
+ };
12673
+ let scaffolds = groups.map(
12674
+ (g) => scaffoldPostIncidentTest(lesson, { framework: forced ?? g.framework, outPath: opts.out, baseDir: g.baseDir })
12531
12675
  );
12676
+ let proposeCmd = scaffolds[0].proposeCommand;
12677
+ if (scaffolds.length > 1) {
12678
+ proposeCmd = buildProposeCommand(lesson, scaffolds.map((s) => s.runCommand).join(" && "));
12679
+ scaffolds = groups.map(
12680
+ (g) => scaffoldPostIncidentTest(lesson, { framework: forced ?? g.framework, baseDir: g.baseDir, proposeCommandOverride: proposeCmd })
12681
+ );
12682
+ }
12532
12683
  if (opts.stdout) {
12533
- console.log(scaffold.content);
12534
- ui.info(`Arm it once written: ${scaffold.proposeCommand}`);
12684
+ for (const scaffold of scaffolds) {
12685
+ if (scaffolds.length > 1) ui.info(`--- ${scaffold.relPath} ---`);
12686
+ console.log(scaffold.content);
12687
+ }
12688
+ ui.info(`Arm ${scaffolds.length > 1 ? "them" : "it"} once written: ${proposeCmd}`);
12535
12689
  return;
12536
12690
  }
12537
- const abs = path41.isAbsolute(scaffold.relPath) ? scaffold.relPath : path41.resolve(root, scaffold.relPath);
12538
- if (existsSync43(abs) && !opts.force) {
12539
- ui.error(`${scaffold.relPath} already exists \u2014 pass --force to overwrite, or --out <path> for a different file.`);
12540
- process.exitCode = 1;
12541
- return;
12691
+ for (const scaffold of scaffolds) {
12692
+ const abs = path42.isAbsolute(scaffold.relPath) ? scaffold.relPath : path42.resolve(root, scaffold.relPath);
12693
+ if (existsSync44(abs) && !opts.force) {
12694
+ ui.error(`${scaffold.relPath} already exists \u2014 pass --force to overwrite, or --out <path> for a different file.`);
12695
+ process.exitCode = 1;
12696
+ return;
12697
+ }
12698
+ await mkdir17(path42.dirname(abs), { recursive: true });
12699
+ await writeFile27(abs, scaffold.content, "utf8");
12700
+ ui.success(`Wrote ${scaffold.framework} post-incident test \u2192 ${scaffold.relPath}`);
12701
+ }
12702
+ if (scaffolds.length > 1) {
12703
+ ui.info(` Lesson spans ${scaffolds.length} packages \u2014 one pending test per owning package, ONE sensor arms them all.`);
12542
12704
  }
12543
- await mkdir17(path41.dirname(abs), { recursive: true });
12544
- await writeFile27(abs, scaffold.content, "utf8");
12545
- ui.success(`Wrote ${framework} post-incident test \u2192 ${scaffold.relPath}`);
12546
12705
  ui.info(" 1. Fill in the assertion (RED on the incident, GREEN once fixed).");
12547
- ui.info(` 2. Run it: ${scaffold.runCommand}`);
12706
+ ui.info(` 2. Run ${scaffolds.length > 1 ? "them" : "it"}: ${scaffolds.map((s) => s.runCommand).join(" && ")}`);
12548
12707
  ui.info(" 3. Arm it as a deterministic gate:");
12549
- console.log(` ${scaffold.proposeCommand}`);
12708
+ console.log(` ${proposeCmd}`);
12550
12709
  });
12551
12710
  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) => {
12552
12711
  const format = opts.format ?? "grep";
@@ -12558,13 +12717,13 @@ function registerSensors(program2) {
12558
12717
  const root = findProjectRoot39(opts.dir);
12559
12718
  const paths = resolveHaivePaths36(root);
12560
12719
  const rows = await sensorRows(paths);
12561
- const outDir = path41.resolve(root, opts.outDir ?? ".ai/generated");
12720
+ const outDir = path42.resolve(root, opts.outDir ?? ".ai/generated");
12562
12721
  await mkdir17(outDir, { recursive: true });
12563
- const outPath = path41.join(outDir, format === "grep" ? "haive-sensors-grep.sh" : "haive-sensors-eslint.json");
12722
+ const outPath = path42.join(outDir, format === "grep" ? "haive-sensors-grep.sh" : "haive-sensors-eslint.json");
12564
12723
  const content = format === "grep" ? renderGrepScript(rows) : JSON.stringify({ sensors: rows }, null, 2) + "\n";
12565
12724
  await writeFile27(outPath, content, "utf8");
12566
12725
  if (format === "grep") await chmod2(outPath, 493);
12567
- ui.success(`Exported ${rows.length} sensor(s): ${path41.relative(root, outPath)}`);
12726
+ ui.success(`Exported ${rows.length} sensor(s): ${path42.relative(root, outPath)}`);
12568
12727
  });
12569
12728
  }
12570
12729
  function deriveProposedMessage(body, pattern, absent) {
@@ -12597,8 +12756,8 @@ async function sensorRows(paths) {
12597
12756
  });
12598
12757
  }
12599
12758
  async function runnableSensorMemories(paths, regexOnly = true) {
12600
- if (!existsSync43(paths.memoriesDir)) return [];
12601
- const loaded = await loadMemoriesFromDir15(paths.memoriesDir);
12759
+ if (!existsSync44(paths.memoriesDir)) return [];
12760
+ const loaded = await loadMemoriesFromDir16(paths.memoriesDir);
12602
12761
  return loaded.map(({ memory: memory2 }) => memory2).filter((memory2) => {
12603
12762
  const sensor = memory2.frontmatter.sensor;
12604
12763
  if (!sensor) return false;
@@ -12637,42 +12796,17 @@ function renderGrepScript(rows) {
12637
12796
  function shellQuote(value) {
12638
12797
  return `'${value.replace(/'/g, "'\\''")}'`;
12639
12798
  }
12640
- function normalizeFramework(input) {
12641
- const v = input.trim().toLowerCase();
12642
- if (v === "vitest") return "vitest";
12643
- if (v === "jest") return "jest";
12644
- if (v === "pytest" || v === "py" || v === "python") return "pytest";
12645
- if (v === "go" || v === "gotest" || v === "go-test") return "gotest";
12646
- return null;
12647
- }
12648
- async function detectTestFramework(root) {
12649
- try {
12650
- const pkgPath = path41.join(root, "package.json");
12651
- if (existsSync43(pkgPath)) {
12652
- const pkg = JSON.parse(await readFile20(pkgPath, "utf8"));
12653
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
12654
- if (deps.vitest) return "vitest";
12655
- if (deps.jest || deps["ts-jest"]) return "jest";
12656
- }
12657
- } catch {
12658
- }
12659
- if (existsSync43(path41.join(root, "go.mod"))) return "gotest";
12660
- for (const signal of ["pyproject.toml", "setup.py", "pytest.ini", "requirements.txt", "tox.ini"]) {
12661
- if (existsSync43(path41.join(root, signal))) return "pytest";
12662
- }
12663
- return "vitest";
12664
- }
12665
12799
 
12666
12800
  // src/commands/ingest.ts
12667
- import { existsSync as existsSync44 } from "fs";
12801
+ import { existsSync as existsSync45 } from "fs";
12668
12802
  import { mkdir as mkdir18, readFile as readFile21, writeFile as writeFile28 } from "fs/promises";
12669
- import path42 from "path";
12803
+ import path43 from "path";
12670
12804
  import "commander";
12671
12805
  import {
12672
12806
  draftsFromFindings,
12673
12807
  filterNewDrafts,
12674
12808
  findProjectRoot as findProjectRoot40,
12675
- loadMemoriesFromDir as loadMemoriesFromDir16,
12809
+ loadMemoriesFromDir as loadMemoriesFromDir17,
12676
12810
  memoryFilePath as memoryFilePath7,
12677
12811
  parseFindings,
12678
12812
  resolveHaivePaths as resolveHaivePaths37,
@@ -12702,7 +12836,7 @@ function registerIngest(program2) {
12702
12836
  }
12703
12837
  const root = findProjectRoot40(opts.dir);
12704
12838
  const paths = resolveHaivePaths37(root);
12705
- if (!existsSync44(paths.haiveDir)) {
12839
+ if (!existsSync45(paths.haiveDir)) {
12706
12840
  ui.error(`No .ai/ found at ${root}. Run \`hivelore init\` first.`);
12707
12841
  process.exitCode = 1;
12708
12842
  return;
@@ -12723,8 +12857,8 @@ function registerIngest(program2) {
12723
12857
  process.exitCode = 1;
12724
12858
  return;
12725
12859
  }
12726
- const reportPath = path42.resolve(root, file);
12727
- if (!existsSync44(reportPath)) {
12860
+ const reportPath = path43.resolve(root, file);
12861
+ if (!existsSync45(reportPath)) {
12728
12862
  ui.error(`Report file not found: ${reportPath}`);
12729
12863
  process.exitCode = 1;
12730
12864
  return;
@@ -12756,7 +12890,7 @@ function registerIngest(program2) {
12756
12890
  process.exitCode = 1;
12757
12891
  return;
12758
12892
  }
12759
- const existing = existsSync44(paths.memoriesDir) ? await loadMemoriesFromDir16(paths.memoriesDir) : [];
12893
+ const existing = existsSync45(paths.memoriesDir) ? await loadMemoriesFromDir17(paths.memoriesDir) : [];
12760
12894
  const existingTopics = new Set(
12761
12895
  existing.map(({ memory: memory2 }) => memory2.frontmatter.topic).filter((t) => Boolean(t))
12762
12896
  );
@@ -12818,13 +12952,13 @@ function registerIngest(program2) {
12818
12952
  await writeDraft(paths, draft);
12819
12953
  created++;
12820
12954
  }
12821
- ui.success(`Created ${created} proposed memory(ies) under ${path42.relative(root, paths.memoriesDir)}/`);
12955
+ ui.success(`Created ${created} proposed memory(ies) under ${path43.relative(root, paths.memoriesDir)}/`);
12822
12956
  ui.info("Review with `hivelore memory pending`; promote sensors with `hivelore sensors promote <id> --yes`.");
12823
12957
  });
12824
12958
  }
12825
12959
  async function writeDraft(paths, draft) {
12826
12960
  const file = memoryFilePath7(paths, draft.frontmatter.scope, draft.frontmatter.id, draft.frontmatter.module);
12827
- await mkdir18(path42.dirname(file), { recursive: true });
12961
+ await mkdir18(path43.dirname(file), { recursive: true });
12828
12962
  await writeFile28(file, serializeMemory16({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
12829
12963
  return file;
12830
12964
  }
@@ -12866,13 +13000,13 @@ async function fetchSonarIssues(opts) {
12866
13000
  }
12867
13001
 
12868
13002
  // src/commands/dashboard.ts
12869
- import { existsSync as existsSync45 } from "fs";
13003
+ import { existsSync as existsSync46 } from "fs";
12870
13004
  import "commander";
12871
13005
  import {
12872
13006
  buildDashboard,
12873
13007
  findProjectRoot as findProjectRoot41,
12874
13008
  loadConfig as loadConfig14,
12875
- loadMemoriesFromDir as loadMemoriesFromDir17,
13009
+ loadMemoriesFromDir as loadMemoriesFromDir18,
12876
13010
  loadPreventionEvents as loadPreventionEvents4,
12877
13011
  loadUsageIndex as loadUsageIndex16,
12878
13012
  resolveHaivePaths as resolveHaivePaths38
@@ -12883,12 +13017,12 @@ function registerDashboard(program2) {
12883
13017
  ).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) => {
12884
13018
  const root = findProjectRoot41(opts.dir);
12885
13019
  const paths = resolveHaivePaths38(root);
12886
- if (!existsSync45(paths.haiveDir)) {
13020
+ if (!existsSync46(paths.haiveDir)) {
12887
13021
  ui.error(`No .ai/ found at ${root}. Run \`hivelore init\` first.`);
12888
13022
  process.exitCode = 1;
12889
13023
  return;
12890
13024
  }
12891
- const memories = existsSync45(paths.memoriesDir) ? await loadMemoriesFromDir17(paths.memoriesDir) : [];
13025
+ const memories = existsSync46(paths.memoriesDir) ? await loadMemoriesFromDir18(paths.memoriesDir) : [];
12892
13026
  const usage = await loadUsageIndex16(paths);
12893
13027
  const preventionEvents = await loadPreventionEvents4(paths);
12894
13028
  const config = await loadConfig14(paths);
@@ -13014,8 +13148,8 @@ function warnNum(n) {
13014
13148
  // src/commands/dev-link.ts
13015
13149
  import { execFile as execFile8 } from "child_process";
13016
13150
  import { cp, readFile as readFile22 } from "fs/promises";
13017
- import { existsSync as existsSync46 } from "fs";
13018
- import path43 from "path";
13151
+ import { existsSync as existsSync47 } from "fs";
13152
+ import path44 from "path";
13019
13153
  import { promisify as promisify8 } from "util";
13020
13154
  import "commander";
13021
13155
  import { findProjectRoot as findProjectRoot42 } from "@hivelore/core";
@@ -13024,7 +13158,7 @@ function registerDevLink(program2) {
13024
13158
  const dev = program2.commands.find((c) => c.name() === "dev") ?? program2.command("dev").description("Developer utilities for working on Hivelore itself.");
13025
13159
  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) => {
13026
13160
  const root = findProjectRoot42(opts.dir);
13027
- if (!existsSync46(path43.join(root, "packages", "cli", "dist", "index.js"))) {
13161
+ if (!existsSync47(path44.join(root, "packages", "cli", "dist", "index.js"))) {
13028
13162
  ui.error(`Not the Hivelore monorepo (no packages/cli/dist) at ${root}. Run \`pnpm -r build\` first, or pass --dir.`);
13029
13163
  process.exitCode = 1;
13030
13164
  return;
@@ -13033,9 +13167,9 @@ function registerDevLink(program2) {
13033
13167
  try {
13034
13168
  globalModules = (await exec6("npm", ["root", "-g"])).stdout.trim();
13035
13169
  } catch {
13036
- globalModules = path43.join(path43.dirname(path43.dirname(process.execPath)), "lib", "node_modules");
13170
+ globalModules = path44.join(path44.dirname(path44.dirname(process.execPath)), "lib", "node_modules");
13037
13171
  }
13038
- const scopeDirs = ["@hivelore", "@hiveai"].map((scope) => path43.join(globalModules, scope)).filter((dir) => existsSync46(dir));
13172
+ const scopeDirs = ["@hivelore", "@hiveai"].map((scope) => path44.join(globalModules, scope)).filter((dir) => existsSync47(dir));
13039
13173
  if (scopeDirs.length === 0) {
13040
13174
  ui.error(`No global @hivelore (or legacy @hiveai) install under ${globalModules}. Install once with \`npm i -g @hivelore/cli\`, then re-run.`);
13041
13175
  process.exitCode = 1;
@@ -13043,24 +13177,24 @@ function registerDevLink(program2) {
13043
13177
  }
13044
13178
  const linked = [];
13045
13179
  const copyDist = async (fromPkg, toDistDir) => {
13046
- const from = path43.join(root, "packages", fromPkg, "dist");
13047
- if (!existsSync46(from) || !existsSync46(path43.dirname(toDistDir))) return;
13180
+ const from = path44.join(root, "packages", fromPkg, "dist");
13181
+ if (!existsSync47(from) || !existsSync47(path44.dirname(toDistDir))) return;
13048
13182
  await cp(from, toDistDir, { recursive: true });
13049
- linked.push(path43.relative(globalModules, toDistDir));
13183
+ linked.push(path44.relative(globalModules, toDistDir));
13050
13184
  };
13051
13185
  for (const globalHive of scopeDirs) {
13052
- const nestedScope = path43.basename(globalHive);
13186
+ const nestedScope = path44.basename(globalHive);
13053
13187
  for (const pkg of ["cli", "mcp"]) {
13054
- await copyDist(pkg, path43.join(globalHive, pkg, "dist"));
13188
+ await copyDist(pkg, path44.join(globalHive, pkg, "dist"));
13055
13189
  for (const nested of ["core", "embeddings"]) {
13056
- await copyDist(nested, path43.join(globalHive, pkg, "node_modules", nestedScope, nested, "dist"));
13190
+ await copyDist(nested, path44.join(globalHive, pkg, "node_modules", nestedScope, nested, "dist"));
13057
13191
  }
13058
13192
  }
13059
- await copyDist("core", path43.join(globalHive, "core", "dist"));
13193
+ await copyDist("core", path44.join(globalHive, "core", "dist"));
13060
13194
  }
13061
13195
  let version = "unknown";
13062
13196
  try {
13063
- version = JSON.parse(await readFile22(path43.join(root, "package.json"), "utf8")).version ?? "unknown";
13197
+ version = JSON.parse(await readFile22(path44.join(root, "package.json"), "utf8")).version ?? "unknown";
13064
13198
  } catch {
13065
13199
  }
13066
13200
  if (opts.json) {
@@ -13071,7 +13205,7 @@ function registerDevLink(program2) {
13071
13205
  ui.warn("Nothing linked \u2014 no matching dist targets were found in the global install.");
13072
13206
  return;
13073
13207
  }
13074
- ui.success(`Linked local dist (v${version}) into the global install(s): ${scopeDirs.map((d) => path43.basename(d)).join(", ")}`);
13208
+ ui.success(`Linked local dist (v${version}) into the global install(s): ${scopeDirs.map((d) => path44.basename(d)).join(", ")}`);
13075
13209
  for (const t of linked) console.log(` ${ui.dim("\u2192")} ${t}`);
13076
13210
  console.log(ui.dim("The global binary now runs your local build (git hooks + MCP included)."));
13077
13211
  });
@@ -13079,8 +13213,8 @@ function registerDevLink(program2) {
13079
13213
 
13080
13214
  // src/commands/coverage.ts
13081
13215
  import { readFile as readFile23 } from "fs/promises";
13082
- import { existsSync as existsSync47 } from "fs";
13083
- import path44 from "path";
13216
+ import { existsSync as existsSync48 } from "fs";
13217
+ import path45 from "path";
13084
13218
  import "commander";
13085
13219
  import {
13086
13220
  findCoverageGaps,
@@ -13090,7 +13224,7 @@ import {
13090
13224
  tallyHotFiles
13091
13225
  } from "@hivelore/core";
13092
13226
  async function readAgentHotFiles(root, cacheFile, sinceMs) {
13093
- if (!existsSync47(cacheFile)) return [];
13227
+ if (!existsSync48(cacheFile)) return [];
13094
13228
  const raw = await readFile23(cacheFile, "utf8").catch(() => "");
13095
13229
  const files = [];
13096
13230
  for (const line of raw.split("\n")) {
@@ -13104,7 +13238,7 @@ async function readAgentHotFiles(root, cacheFile, sinceMs) {
13104
13238
  }
13105
13239
  for (const f of obs.files ?? []) {
13106
13240
  if (typeof f !== "string" || !f) continue;
13107
- const rel = path44.isAbsolute(f) ? path44.relative(root, f) : f;
13241
+ const rel = path45.isAbsolute(f) ? path45.relative(root, f) : f;
13108
13242
  if (rel.startsWith("..")) continue;
13109
13243
  files.push(rel);
13110
13244
  }
@@ -13148,7 +13282,7 @@ function registerCoverage(program2) {
13148
13282
  }) : null;
13149
13283
  const gitHotFiles = (radar?.hotFiles ?? []).filter((h) => !isNoisePath(h.path)).map((h) => ({ path: h.path, changes: h.changes, source: "git" }));
13150
13284
  const sinceMs = Date.now() - days * 864e5;
13151
- const agentHotFiles = useAgent ? (await readAgentHotFiles(root, path44.join(paths.haiveDir, ".cache", "observations.jsonl"), sinceMs)).filter((h) => !isNoisePath(h.path)) : [];
13285
+ const agentHotFiles = useAgent ? (await readAgentHotFiles(root, path45.join(paths.haiveDir, ".cache", "observations.jsonl"), sinceMs)).filter((h) => !isNoisePath(h.path)) : [];
13152
13286
  const hotFiles = mergeHotFiles(gitHotFiles, agentHotFiles);
13153
13287
  const memories = await loadMemoriesFromDir(paths.memoriesDir);
13154
13288
  const gaps = findCoverageGaps(hotFiles, memories, { minChanges, limit });
@@ -13186,8 +13320,8 @@ function registerCoverage(program2) {
13186
13320
 
13187
13321
  // src/commands/merge-driver.ts
13188
13322
  import { execFileSync as execFileSync3 } from "child_process";
13189
- import { readFileSync, writeFileSync, existsSync as existsSync48 } from "fs";
13190
- import path45 from "path";
13323
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync49 } from "fs";
13324
+ import path46 from "path";
13191
13325
  import "commander";
13192
13326
  import { findProjectRoot as findProjectRoot44, mergeMemoryVersions } from "@hivelore/core";
13193
13327
  var GITATTRIBUTES_MARK = "# Hivelore merge driver";
@@ -13200,8 +13334,8 @@ function registerMergeDriver(program2) {
13200
13334
  const cmd = program2.command("merge-driver").description("Deterministic git merge driver for Hivelore memory files (kills .ai/ conflict markers)");
13201
13335
  cmd.command("run <base> <ours> <theirs>").description("Git merge-driver entrypoint: resolve ours/theirs by frontmatter order, write into <ours>").action((base, ours, theirs) => {
13202
13336
  try {
13203
- const oursContent = readFileSync(ours, "utf8");
13204
- const theirsContent = readFileSync(theirs, "utf8");
13337
+ const oursContent = readFileSync2(ours, "utf8");
13338
+ const theirsContent = readFileSync2(theirs, "utf8");
13205
13339
  const result = mergeMemoryVersions(oursContent, theirsContent);
13206
13340
  if (result.content !== oursContent) writeFileSync(ours, result.content, "utf8");
13207
13341
  process.exit(0);
@@ -13219,8 +13353,8 @@ function registerMergeDriver(program2) {
13219
13353
  process.exitCode = 1;
13220
13354
  return;
13221
13355
  }
13222
- const gaPath = path45.join(root, ".gitattributes");
13223
- let content = existsSync48(gaPath) ? readFileSync(gaPath, "utf8") : "";
13356
+ const gaPath = path46.join(root, ".gitattributes");
13357
+ let content = existsSync49(gaPath) ? readFileSync2(gaPath, "utf8") : "";
13224
13358
  if (!content.includes(GITATTRIBUTES_MARK)) {
13225
13359
  if (content.length > 0 && !content.endsWith("\n")) content += "\n";
13226
13360
  content += GITATTRIBUTES_BLOCK + "\n";
@@ -13234,8 +13368,8 @@ function registerMergeDriver(program2) {
13234
13368
  }
13235
13369
 
13236
13370
  // src/commands/bridges.ts
13237
- import { existsSync as existsSync49 } from "fs";
13238
- import path46 from "path";
13371
+ import { existsSync as existsSync50 } from "fs";
13372
+ import path47 from "path";
13239
13373
  import "commander";
13240
13374
  import {
13241
13375
  findProjectRoot as findProjectRoot45,
@@ -13256,7 +13390,7 @@ function registerBridges(program2) {
13256
13390
  const root = findProjectRoot45(opts.dir);
13257
13391
  const paths = resolveHaivePaths40(root);
13258
13392
  const dryRun = opts.dryRun === true;
13259
- if (!existsSync49(paths.memoriesDir)) {
13393
+ if (!existsSync50(paths.memoriesDir)) {
13260
13394
  ui.warn(`No .ai/memories at ${root}. Run \`hivelore init\` first.`);
13261
13395
  process.exitCode = 1;
13262
13396
  return;
@@ -13275,7 +13409,7 @@ function registerBridges(program2) {
13275
13409
  targets = BRIDGE_TARGETS4;
13276
13410
  } else {
13277
13411
  targets = BRIDGE_TARGETS4.filter(
13278
- (t) => existsSync49(path46.join(root, BRIDGE_TARGET_PATH3[t]))
13412
+ (t) => existsSync50(path47.join(root, BRIDGE_TARGET_PATH3[t]))
13279
13413
  );
13280
13414
  if (targets.length === 0) {
13281
13415
  ui.info(
@@ -13323,7 +13457,7 @@ function registerBridges(program2) {
13323
13457
 
13324
13458
  // src/index.ts
13325
13459
  var program = new Command48();
13326
- program.name("hivelore").description("Hivelore - the deterministic policy gate for agent-written code (rules live as repo-native team memory)").version("0.37.0").option("--advanced", "show maintenance and experimental commands in help").showSuggestionAfterError(true);
13460
+ program.name("hivelore").description("Hivelore - the deterministic policy gate for agent-written code (rules live as repo-native team memory)").version("0.39.0").option("--advanced", "show maintenance and experimental commands in help").showSuggestionAfterError(true);
13327
13461
  registerInit(program);
13328
13462
  registerResolveProject(program);
13329
13463
  registerEnforce(program);