@hivelore/cli 0.38.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,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-I4VELI5K.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.0"}`;
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.0"));
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.0";
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");
@@ -9288,8 +9380,8 @@ which -a hivelore haive`
9288
9380
  const missingBins = /* @__PURE__ */ new Map();
9289
9381
  const staleBins = /* @__PURE__ */ new Map();
9290
9382
  for (const rel of integrationFiles) {
9291
- const file = path36.join(root, rel);
9292
- if (!existsSync39(file)) continue;
9383
+ const file = path37.join(root, rel);
9384
+ if (!existsSync40(file)) continue;
9293
9385
  const text = await readFile16(file, "utf8").catch(() => "");
9294
9386
  for (const bin of extractAbsoluteHaiveBins(text)) {
9295
9387
  const version = versionForBinary(bin);
@@ -9322,7 +9414,7 @@ which -a hivelore haive`
9322
9414
  async function collectToolchainFindings(root) {
9323
9415
  const findings = [];
9324
9416
  const pkg = await readJson(
9325
- path36.join(root, "package.json")
9417
+ path37.join(root, "package.json")
9326
9418
  );
9327
9419
  const wantsPnpm = pkg?.packageManager?.startsWith("pnpm@") || Object.values(pkg?.scripts ?? {}).some((script) => /\bpnpm\b/.test(script));
9328
9420
  if (!wantsPnpm) return findings;
@@ -9341,10 +9433,10 @@ async function collectToolchainFindings(root) {
9341
9433
  }
9342
9434
  async function collectDistFreshnessFindings(root, expectedVersion) {
9343
9435
  const findings = [];
9344
- 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 ?? "");
9345
9437
  if (!isHaiveWorkspace) return findings;
9346
- const cliDist = path36.join(root, "packages/cli/dist/index.js");
9347
- if (!existsSync39(cliDist)) {
9438
+ const cliDist = path37.join(root, "packages/cli/dist/index.js");
9439
+ if (!existsSync40(cliDist)) {
9348
9440
  findings.push({
9349
9441
  severity: "warn",
9350
9442
  code: "workspace-dist-missing",
@@ -9368,10 +9460,10 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
9368
9460
  "packages/core/src/index.ts",
9369
9461
  "packages/mcp/src/server.ts",
9370
9462
  "packages/cli/src/index.ts"
9371
- ].map((rel) => path36.join(root, rel)).filter(existsSync39);
9463
+ ].map((rel) => path37.join(root, rel)).filter(existsSync40);
9372
9464
  if (sourceFiles.length > 0) {
9373
- const distMtime = statSync2(cliDist).mtimeMs;
9374
- 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));
9375
9467
  if (newestSource > distMtime + 1e3) {
9376
9468
  findings.push({
9377
9469
  severity: "info",
@@ -9386,7 +9478,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
9386
9478
  }
9387
9479
  async function collectWorkspaceVersionFindings(root, expectedVersion) {
9388
9480
  const findings = [];
9389
- const rootPkg = await readJson(path36.join(root, "package.json"));
9481
+ const rootPkg = await readJson(path37.join(root, "package.json"));
9390
9482
  const workspacePackages = [
9391
9483
  "packages/core/package.json",
9392
9484
  "packages/embeddings/package.json",
@@ -9395,7 +9487,7 @@ async function collectWorkspaceVersionFindings(root, expectedVersion) {
9395
9487
  ];
9396
9488
  const existing = (await Promise.all(workspacePackages.map(async (rel) => ({
9397
9489
  rel,
9398
- pkg: await readJson(path36.join(root, rel))
9490
+ pkg: await readJson(path37.join(root, rel))
9399
9491
  })))).filter((item) => item.pkg);
9400
9492
  const isHaiveWorkspace = rootPkg?.name === "hivelore-monorepo" || rootPkg?.name === "haive-monorepo" || existing.some((item) => item.pkg?.name?.startsWith("@hivelore/"));
9401
9493
  if (!isHaiveWorkspace) return findings;
@@ -9457,7 +9549,7 @@ function collectGlobalHivemoduleFindings(expectedVersion) {
9457
9549
  }
9458
9550
  }
9459
9551
  async function readJson(file) {
9460
- if (!existsSync39(file)) return null;
9552
+ if (!existsSync40(file)) return null;
9461
9553
  try {
9462
9554
  return JSON.parse(await readFile16(file, "utf8"));
9463
9555
  } catch {
@@ -9522,7 +9614,7 @@ function extractAbsoluteHaiveBins(text) {
9522
9614
  const p = match[2];
9523
9615
  if (!p) continue;
9524
9616
  try {
9525
- if (statSync2(p).isDirectory()) continue;
9617
+ if (statSync3(p).isDirectory()) continue;
9526
9618
  } catch {
9527
9619
  }
9528
9620
  out.add(p);
@@ -9686,23 +9778,23 @@ function runCommand(cmd, args, cwd) {
9686
9778
  }
9687
9779
 
9688
9780
  // src/commands/resolve-project.ts
9689
- import path37 from "path";
9781
+ import path38 from "path";
9690
9782
  import "commander";
9691
9783
  import { resolveProjectInfo } from "@hivelore/core";
9692
9784
  function registerResolveProject(program2) {
9693
9785
  program2.command("resolve-project").description(
9694
9786
  "Print JSON for Hivelore project root resolution (HAIVE_PROJECT_ROOT, markers, .ai layout)."
9695
9787
  ).option("-d, --dir <dir>", "working directory", process.cwd()).action((opts) => {
9696
- const info = resolveProjectInfo({ cwd: path37.resolve(opts.dir) });
9788
+ const info = resolveProjectInfo({ cwd: path38.resolve(opts.dir) });
9697
9789
  console.log(JSON.stringify({ ok: true, info }, null, 2));
9698
9790
  });
9699
9791
  }
9700
9792
 
9701
9793
  // src/commands/enforce.ts
9702
9794
  import { execFile as execFile5, execFileSync as execFileSync2, spawn as spawn4 } from "child_process";
9703
- import { existsSync as existsSync41, statSync as statSync3 } from "fs";
9795
+ import { existsSync as existsSync42, statSync as statSync4 } from "fs";
9704
9796
  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";
9797
+ import path40 from "path";
9706
9798
  import { promisify as promisify5 } from "util";
9707
9799
  import "commander";
9708
9800
  import {
@@ -9711,6 +9803,7 @@ import {
9711
9803
  assessSensorHealth as assessSensorHealth3,
9712
9804
  sensorPromotedAtMap as sensorPromotedAtMap3,
9713
9805
  assessBootstrapState,
9806
+ detectSensorWeakening,
9714
9807
  isSensorScannablePath,
9715
9808
  findProjectRoot as findProjectRoot37,
9716
9809
  loadCodeMap as loadCodeMap7,
@@ -9722,7 +9815,7 @@ import {
9722
9815
  isRetiredMemory as isRetiredMemory2,
9723
9816
  loadConfig as loadConfig12,
9724
9817
  detectAgentContext,
9725
- loadMemoriesFromDir as loadMemoriesFromDir14,
9818
+ loadMemoriesFromDir as loadMemoriesFromDir15,
9726
9819
  loadSensorLedger as loadSensorLedger3,
9727
9820
  memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
9728
9821
  readRecentBriefingMarker,
@@ -9741,9 +9834,9 @@ import {
9741
9834
  } from "@hivelore/core";
9742
9835
 
9743
9836
  // src/utils/claude-hooks.ts
9744
- import { existsSync as existsSync40 } from "fs";
9837
+ import { existsSync as existsSync41 } from "fs";
9745
9838
  import { mkdir as mkdir15, readFile as readFile17, writeFile as writeFile24 } from "fs/promises";
9746
- import path38 from "path";
9839
+ import path39 from "path";
9747
9840
  var HAIVE_HOOK_TAG = "haive-enforcement";
9748
9841
  var POST_TOOL_USE_GROUP = {
9749
9842
  matcher: "Edit|Write|Bash",
@@ -9829,7 +9922,7 @@ function unpatchClaudeSettings(input) {
9829
9922
  async function installClaudeHooksAtPath(settingsPath) {
9830
9923
  let raw = null;
9831
9924
  let created = false;
9832
- if (existsSync40(settingsPath)) {
9925
+ if (existsSync41(settingsPath)) {
9833
9926
  try {
9834
9927
  raw = JSON.parse(await readFile17(settingsPath, "utf8"));
9835
9928
  } catch {
@@ -9839,12 +9932,12 @@ async function installClaudeHooksAtPath(settingsPath) {
9839
9932
  created = true;
9840
9933
  }
9841
9934
  const patched = patchClaudeSettings(raw);
9842
- await mkdir15(path38.dirname(settingsPath), { recursive: true });
9935
+ await mkdir15(path39.dirname(settingsPath), { recursive: true });
9843
9936
  await writeFile24(settingsPath, JSON.stringify(patched, null, 2) + "\n", "utf8");
9844
9937
  return { settingsPath, created };
9845
9938
  }
9846
9939
  async function uninstallClaudeHooksAtPath(settingsPath) {
9847
- if (!existsSync40(settingsPath)) {
9940
+ if (!existsSync41(settingsPath)) {
9848
9941
  return { settingsPath, created: false };
9849
9942
  }
9850
9943
  const raw = JSON.parse(await readFile17(settingsPath, "utf8"));
@@ -9855,9 +9948,9 @@ async function uninstallClaudeHooksAtPath(settingsPath) {
9855
9948
  function defaultClaudeSettingsPath(scope, projectRoot) {
9856
9949
  if (scope === "user") {
9857
9950
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
9858
- return path38.join(home, ".claude", "settings.json");
9951
+ return path39.join(home, ".claude", "settings.json");
9859
9952
  }
9860
- return path38.join(projectRoot, ".claude", "settings.local.json");
9953
+ return path39.join(projectRoot, ".claude", "settings.local.json");
9861
9954
  }
9862
9955
 
9863
9956
  // src/utils/command-sensors.ts
@@ -10044,7 +10137,7 @@ function registerEnforce(program2) {
10044
10137
  ui.success(`Removed Hivelore hooks from ${result.settingsPath}`);
10045
10138
  } else {
10046
10139
  const result = await installClaudeHooksAtPath(settingsPath);
10047
- 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})`);
10048
10141
  }
10049
10142
  } catch (err) {
10050
10143
  ui.warn(`Claude Code hooks not ${opts.removeClaude ? "removed" : "installed"}: ${err instanceof Error ? err.message : String(err)}`);
@@ -10066,19 +10159,19 @@ function registerEnforce(program2) {
10066
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) => {
10067
10160
  const root = findProjectRoot37(opts.dir);
10068
10161
  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)`);
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)`);
10072
10165
  else {
10073
10166
  const removed = await cleanupCacheDir(cacheDir);
10074
- 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)` : ""}`);
10075
10168
  }
10076
10169
  }
10077
- if (existsSync41(paths.runtimeDir)) {
10078
- 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)`);
10079
10172
  else {
10080
10173
  const removed = await cleanupRuntimeDir(paths.runtimeDir);
10081
- 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)` : ""}`);
10082
10175
  }
10083
10176
  }
10084
10177
  });
@@ -10122,7 +10215,7 @@ function registerEnforce(program2) {
10122
10215
  const root = resolveRoot(opts.dir, payload);
10123
10216
  if (!root) return;
10124
10217
  const paths = resolveHaivePaths35(root);
10125
- if (!existsSync41(paths.haiveDir)) return;
10218
+ if (!existsSync42(paths.haiveDir)) return;
10126
10219
  await mkdir16(paths.runtimeDir, { recursive: true });
10127
10220
  const sessionId = opts.sessionId ?? payload.session_id;
10128
10221
  const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this Hivelore-initialized project.";
@@ -10185,7 +10278,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
10185
10278
  const root = resolveRoot(opts.dir, payload);
10186
10279
  if (!root) return;
10187
10280
  const paths = resolveHaivePaths35(root);
10188
- if (!existsSync41(paths.haiveDir)) return;
10281
+ if (!existsSync42(paths.haiveDir)) return;
10189
10282
  if (!isWriteLikeTool(payload)) return;
10190
10283
  const config = await loadConfig12(paths);
10191
10284
  if (config.enforcement?.requireBriefingFirst === false) return;
@@ -10247,10 +10340,26 @@ function emitPreToolUseContext(text) {
10247
10340
  })
10248
10341
  );
10249
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
+ }
10250
10359
  async function buildFinishReport(dir) {
10251
10360
  const root = findProjectRoot37(dir);
10252
10361
  const paths = resolveHaivePaths35(root);
10253
- const initialized = existsSync41(paths.haiveDir);
10362
+ const initialized = existsSync42(paths.haiveDir);
10254
10363
  const config = initialized ? await loadConfig12(paths) : {};
10255
10364
  const mode = config.enforcement?.mode ?? "strict";
10256
10365
  const findings = [];
@@ -10271,6 +10380,7 @@ async function buildFinishReport(dir) {
10271
10380
  });
10272
10381
  }
10273
10382
  findings.push(...await checkFailureCapture(paths, config));
10383
+ findings.push(...await checkPostIncidentScaffolds(paths));
10274
10384
  findings.push(...await checkBootstrapComplete(paths, config, true));
10275
10385
  const status = await getGitSyncStatus(root);
10276
10386
  if (!status.available) {
@@ -10442,8 +10552,8 @@ async function buildFinishReport(dir) {
10442
10552
  async function checkFailureCapture(paths, config) {
10443
10553
  const gate = config.enforcement?.failureCaptureGate ?? "warn";
10444
10554
  if (gate === "off") return [];
10445
- const obsFile = path39.join(paths.haiveDir, ".cache", "observations.jsonl");
10446
- if (!existsSync41(obsFile)) return [];
10555
+ const obsFile = path40.join(paths.haiveDir, ".cache", "observations.jsonl");
10556
+ if (!existsSync42(obsFile)) return [];
10447
10557
  const failures = [];
10448
10558
  try {
10449
10559
  const raw = await readFile18(obsFile, "utf8");
@@ -10460,7 +10570,7 @@ async function checkFailureCapture(paths, config) {
10460
10570
  return [];
10461
10571
  }
10462
10572
  if (failures.length === 0) return [];
10463
- const memories = existsSync41(paths.memoriesDir) ? await loadMemoriesFromDir14(paths.memoriesDir) : [];
10573
+ const memories = existsSync42(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
10464
10574
  const captureTimes = memories.filter(({ memory: memory2 }) => ["attempt", "gotcha"].includes(memory2.frontmatter.type)).map(({ memory: memory2 }) => memory2.frontmatter.created_at);
10465
10575
  const uncaptured = findUncapturedFailures(failures, captureTimes);
10466
10576
  if (uncaptured.length === 0) {
@@ -10495,7 +10605,7 @@ function finishReport(root, initialized, mode, findings, config) {
10495
10605
  async function runWithEnforcement(command, args, opts) {
10496
10606
  const root = findProjectRoot37(opts.dir);
10497
10607
  const paths = resolveHaivePaths35(root);
10498
- if (!existsSync41(paths.haiveDir)) {
10608
+ if (!existsSync42(paths.haiveDir)) {
10499
10609
  ui.error(`No .ai/ found at ${root}. Run \`hivelore init\` first.`);
10500
10610
  process.exit(1);
10501
10611
  }
@@ -10514,7 +10624,7 @@ async function runWithEnforcement(command, args, opts) {
10514
10624
  process.exit(2);
10515
10625
  }
10516
10626
  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`);
10627
+ ui.info(`Briefing written to ${path40.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
10518
10628
  const child = spawn4(command, args, {
10519
10629
  cwd: root,
10520
10630
  stdio: "inherit",
@@ -10566,9 +10676,9 @@ async function writeWrapperBriefing(paths, sessionId, task) {
10566
10676
  source: "haive-run",
10567
10677
  memoryIds: briefing.memories.map((m) => m.id)
10568
10678
  });
10569
- const dir = path39.join(paths.runtimeDir, "enforcement", "briefings");
10679
+ const dir = path40.join(paths.runtimeDir, "enforcement", "briefings");
10570
10680
  await mkdir16(dir, { recursive: true });
10571
- const file = path39.join(dir, `${sessionId}.md`);
10681
+ const file = path40.join(dir, `${sessionId}.md`);
10572
10682
  const parts = [
10573
10683
  "# Hivelore Briefing",
10574
10684
  "",
@@ -10606,7 +10716,7 @@ async function checkBootstrapComplete(paths, config, productionCodeChanged) {
10606
10716
  projectContextRaw = await readFile18(paths.projectContext, "utf8");
10607
10717
  } catch {
10608
10718
  }
10609
- const memories = existsSync41(paths.memoriesDir) ? await loadMemoriesFromDir14(paths.memoriesDir) : [];
10719
+ const memories = existsSync42(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
10610
10720
  const codeMap = await loadCodeMap7(paths);
10611
10721
  const codeFiles = codeMap ? Object.keys(codeMap.files) : [];
10612
10722
  let existingModules = [];
@@ -10638,7 +10748,7 @@ ${renderBootstrapChecklist(assessment)}`,
10638
10748
  async function buildEnforcementReport(dir, stage, sessionId) {
10639
10749
  const root = findProjectRoot37(dir);
10640
10750
  const paths = resolveHaivePaths35(root);
10641
- const initialized = existsSync41(paths.haiveDir);
10751
+ const initialized = existsSync42(paths.haiveDir);
10642
10752
  const config = initialized ? await loadConfig12(paths) : {};
10643
10753
  if (initialized) {
10644
10754
  await applyLightweightRepairs(root, paths);
@@ -10672,7 +10782,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
10672
10782
  findings: [{ severity: "info", code: "enforcement-off", message: "Hivelore enforcement is disabled." }]
10673
10783
  });
10674
10784
  }
10675
- findings.push(...await inspectIntegrationVersions(root, "0.38.0"));
10785
+ findings.push(...await inspectIntegrationVersions(root, "0.39.0"));
10676
10786
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
10677
10787
  const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
10678
10788
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent Hivelore briefing marker exists." } : {
@@ -10743,10 +10853,14 @@ async function buildEnforcementReport(dir, stage, sessionId) {
10743
10853
  }
10744
10854
  const score = buildScore(effectiveFindings, config.enforcement?.scoreThreshold);
10745
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);
10746
10860
  effectiveFindings = [...effectiveFindings, {
10747
10861
  severity: "error",
10748
10862
  code: "enforcement-score-below-threshold",
10749
- 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(", ")}` : "") + ".",
10750
10864
  fix: "Load the relevant briefing, address policy findings, then rerun `hivelore enforce check`.",
10751
10865
  impact: 0
10752
10866
  }];
@@ -10787,8 +10901,8 @@ function withCategories(report) {
10787
10901
  async function hasRecentSessionRecap(paths) {
10788
10902
  const handoffAge = await handoffAgeMs(paths.root);
10789
10903
  if (handoffAge !== null && handoffAge < SESSION_RECAP_TTL_MS) return true;
10790
- if (!existsSync41(paths.memoriesDir)) return false;
10791
- const all = await loadMemoriesFromDir14(paths.memoriesDir);
10904
+ if (!existsSync42(paths.memoriesDir)) return false;
10905
+ const all = await loadMemoriesFromDir15(paths.memoriesDir);
10792
10906
  return all.some(({ memory: memory2 }) => {
10793
10907
  const fm = memory2.frontmatter;
10794
10908
  const freshnessDate = fm.verified_at ?? fm.created_at;
@@ -10796,8 +10910,8 @@ async function hasRecentSessionRecap(paths) {
10796
10910
  });
10797
10911
  }
10798
10912
  async function verifyMemoryPolicy(paths, config) {
10799
- if (!existsSync41(paths.memoriesDir)) return [];
10800
- const all = await loadMemoriesFromDir14(paths.memoriesDir);
10913
+ if (!existsSync42(paths.memoriesDir)) return [];
10914
+ const all = await loadMemoriesFromDir15(paths.memoriesDir);
10801
10915
  const findings = [];
10802
10916
  const staleImportant = [];
10803
10917
  let verified = 0;
@@ -10834,12 +10948,12 @@ async function verifyMemoryPolicy(paths, config) {
10834
10948
  return findings;
10835
10949
  }
10836
10950
  async function verifyDecisionCoverage(paths, stage, sessionId) {
10837
- if (!existsSync41(paths.memoriesDir)) return [];
10951
+ if (!existsSync42(paths.memoriesDir)) return [];
10838
10952
  const changedFiles = (await getChangedFiles(paths.root, stage)).filter((f) => !isGeneratedArtifact(f));
10839
10953
  if (changedFiles.length === 0) {
10840
10954
  return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
10841
10955
  }
10842
- const all = await loadMemoriesFromDir14(paths.memoriesDir);
10956
+ const all = await loadMemoriesFromDir15(paths.memoriesDir);
10843
10957
  const changedSet = new Set(changedFiles);
10844
10958
  const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention"]);
10845
10959
  const relevant = all.filter(({ memory: memory2 }) => {
@@ -10868,7 +10982,7 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
10868
10982
  const consulted = new Set(marker?.memory_ids ?? []);
10869
10983
  const missing = relevant.filter(({ memory: memory2, filePath }) => {
10870
10984
  if (consulted.has(memory2.frontmatter.id)) return false;
10871
- if (changedSet.has(path39.relative(paths.root, filePath))) return false;
10985
+ if (changedSet.has(path40.relative(paths.root, filePath))) return false;
10872
10986
  return true;
10873
10987
  }).map(({ memory: memory2 }) => memory2);
10874
10988
  if (missing.length === 0) {
@@ -10910,15 +11024,27 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
10910
11024
  }];
10911
11025
  }
10912
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
+ }] : [];
10913
11037
  if (gate === "off") {
10914
- 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
+ ];
10915
11042
  }
10916
- const snapshot = await getPolicyDiffSnapshot(paths.root, stage);
10917
11043
  const touchedPaths = snapshot.paths;
10918
11044
  if (touchedPaths.length === 0) {
10919
11045
  const code = stage === "ci" ? "no-ci-diff-changes" : "no-staged-changes";
10920
11046
  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 }];
11047
+ return [{ severity: "info", code, message }, ...weakeningFindings];
10922
11048
  }
10923
11049
  const { block_on, anchored_blocks } = antiPatternGateParams2(gate);
10924
11050
  const result = await preCommitCheck({
@@ -10933,7 +11059,7 @@ async function runPrecommitPolicy(paths, gate, stage) {
10933
11059
  (w) => w.level === "review" && !w.reasons.includes("sensor")
10934
11060
  );
10935
11061
  const REVIEW_SEEN_TTL_MS = 24 * 60 * 60 * 1e3;
10936
- const reviewSeenFile = path39.join(paths.runtimeDir, "enforcement", "review-seen.json");
11062
+ const reviewSeenFile = path40.join(paths.runtimeDir, "enforcement", "review-seen.json");
10937
11063
  let reviewSeen = {};
10938
11064
  try {
10939
11065
  reviewSeen = JSON.parse(await readFile18(reviewSeenFile, "utf8"));
@@ -10960,7 +11086,7 @@ async function runPrecommitPolicy(paths, gate, stage) {
10960
11086
  for (const [id, at] of Object.entries(reviewSeen)) {
10961
11087
  if (!Number.isFinite(Date.parse(at)) || now - Date.parse(at) >= REVIEW_SEEN_TTL_MS) delete reviewSeen[id];
10962
11088
  }
10963
- await mkdir16(path39.dirname(reviewSeenFile), { recursive: true });
11089
+ await mkdir16(path40.dirname(reviewSeenFile), { recursive: true });
10964
11090
  await writeFile25(reviewSeenFile, JSON.stringify(reviewSeen, null, 2), "utf8");
10965
11091
  } catch {
10966
11092
  }
@@ -10973,7 +11099,8 @@ async function runPrecommitPolicy(paths, gate, stage) {
10973
11099
  message: `${stage === "ci" ? "CI" : "Pre-commit"} policy passed for ${touchedPaths.length} changed file(s).`
10974
11100
  },
10975
11101
  ...reviewFinding,
10976
- ...sensorFindings
11102
+ ...sensorFindings,
11103
+ ...weakeningFindings
10977
11104
  ];
10978
11105
  }
10979
11106
  const blockingWarnings = result.warnings.filter((w) => w.level === "blocking");
@@ -10988,13 +11115,14 @@ async function runPrecommitPolicy(paths, gate, stage) {
10988
11115
  impact: 45
10989
11116
  },
10990
11117
  ...reviewFinding,
10991
- ...sensorFindings
11118
+ ...sensorFindings,
11119
+ ...weakeningFindings
10992
11120
  ];
10993
11121
  }
10994
11122
  async function runSensorGate(paths, diff, stage) {
10995
- if (!diff || !existsSync41(paths.memoriesDir)) return [];
11123
+ if (!diff || !existsSync42(paths.memoriesDir)) return [];
10996
11124
  try {
10997
- const loaded = await loadMemoriesFromDir14(paths.memoriesDir);
11125
+ const loaded = await loadMemoriesFromDir15(paths.memoriesDir);
10998
11126
  const scannable = loaded.map((l) => l.memory).filter((m) => Boolean(m.frontmatter.sensor) && !isRetiredMemory2(m.frontmatter, m.body));
10999
11127
  if (scannable.length === 0) return [];
11000
11128
  const targets = sensorTargetsFromDiff(diff).filter((t) => isSensorScannablePath(t.path));
@@ -11126,8 +11254,14 @@ command: ${run.command} (exit ${run.exit_code}, ${run.duration_ms}ms)${outputBlo
11126
11254
  });
11127
11255
  }
11128
11256
  return findings;
11129
- } catch {
11130
- 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
+ }];
11131
11265
  }
11132
11266
  }
11133
11267
  async function findGeneratedArtifacts(paths) {
@@ -11160,16 +11294,16 @@ async function cleanupRuntimeDir(runtimeDir) {
11160
11294
  for (const entry of entries) {
11161
11295
  if (entry.name === ".gitignore" || entry.name === "README.md") continue;
11162
11296
  if (entry.name === "enforcement") {
11163
- removed += await cleanupEnforcementDir(path39.join(runtimeDir, entry.name));
11297
+ removed += await cleanupEnforcementDir(path40.join(runtimeDir, entry.name));
11164
11298
  continue;
11165
11299
  }
11166
- await rm2(path39.join(runtimeDir, entry.name), { recursive: true, force: true });
11300
+ await rm2(path40.join(runtimeDir, entry.name), { recursive: true, force: true });
11167
11301
  removed++;
11168
11302
  }
11169
- await writeFile25(path39.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
11170
- 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"))) {
11171
11305
  await writeFile25(
11172
- path39.join(runtimeDir, "README.md"),
11306
+ path40.join(runtimeDir, "README.md"),
11173
11307
  "# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. Hivelore cleanup preserves briefing markers so enforcement state remains valid.\n",
11174
11308
  "utf8"
11175
11309
  );
@@ -11182,10 +11316,10 @@ async function cleanupCacheDir(cacheDir) {
11182
11316
  const entries = await readdir4(cacheDir, { withFileTypes: true }).catch(() => []);
11183
11317
  for (const entry of entries) {
11184
11318
  if (entry.name === ".gitignore") continue;
11185
- await rm2(path39.join(cacheDir, entry.name), { recursive: true, force: true });
11319
+ await rm2(path40.join(cacheDir, entry.name), { recursive: true, force: true });
11186
11320
  removed++;
11187
11321
  }
11188
- await writeFile25(path39.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
11322
+ await writeFile25(path40.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
11189
11323
  return removed;
11190
11324
  }
11191
11325
  async function cleanupEnforcementDir(enforcementDir) {
@@ -11193,7 +11327,7 @@ async function cleanupEnforcementDir(enforcementDir) {
11193
11327
  const entries = await readdir4(enforcementDir, { withFileTypes: true }).catch(() => []);
11194
11328
  for (const entry of entries) {
11195
11329
  if (entry.name === "briefings") continue;
11196
- await rm2(path39.join(enforcementDir, entry.name), { recursive: true, force: true });
11330
+ await rm2(path40.join(enforcementDir, entry.name), { recursive: true, force: true });
11197
11331
  removed++;
11198
11332
  }
11199
11333
  return removed;
@@ -11211,8 +11345,8 @@ async function inspectIntegrationVersions(root, expectedVersion) {
11211
11345
  const missingBins = /* @__PURE__ */ new Map();
11212
11346
  const staleBins = /* @__PURE__ */ new Map();
11213
11347
  for (const rel of files) {
11214
- const file = path39.join(root, rel);
11215
- if (!existsSync41(file)) continue;
11348
+ const file = path40.join(root, rel);
11349
+ if (!existsSync42(file)) continue;
11216
11350
  const text = await readFile18(file, "utf8").catch(() => "");
11217
11351
  for (const bin of extractAbsoluteHaiveBins2(text)) {
11218
11352
  const version = versionForBinary2(bin);
@@ -11259,7 +11393,7 @@ function extractAbsoluteHaiveBins2(text) {
11259
11393
  const p = match[2];
11260
11394
  if (!p) continue;
11261
11395
  try {
11262
- if (statSync3(p).isDirectory()) continue;
11396
+ if (statSync4(p).isDirectory()) continue;
11263
11397
  } catch {
11264
11398
  }
11265
11399
  out.add(p);
@@ -11329,7 +11463,7 @@ async function resolveCiDiffRange(root) {
11329
11463
  }
11330
11464
  async function resolveGithubEventRange(root) {
11331
11465
  const eventPath = process.env.GITHUB_EVENT_PATH;
11332
- if (!eventPath || !existsSync41(eventPath)) return null;
11466
+ if (!eventPath || !existsSync42(eventPath)) return null;
11333
11467
  try {
11334
11468
  const event = JSON.parse(await readFile18(eventPath, "utf8"));
11335
11469
  const prBase = cleanGitSha(event.pull_request?.base?.sha);
@@ -11434,7 +11568,7 @@ function isShippablePath(file) {
11434
11568
  }
11435
11569
  var CI_SKIP_DIRECTIVE = /\[skip ci\]|\[ci skip\]|\[no ci\]|\[skip actions\]|\*\*\*NO_CI\*\*\*|skip-checks: *true/i;
11436
11570
  async function checkCommitMessageSkipCi(root, msgfile) {
11437
- const file = path39.isAbsolute(msgfile) ? msgfile : path39.join(root, msgfile);
11571
+ const file = path40.isAbsolute(msgfile) ? msgfile : path40.join(root, msgfile);
11438
11572
  const raw = await readFile18(file, "utf8").catch(() => "");
11439
11573
  const cleaned = raw.split("\n").filter((line) => !line.startsWith("#")).join("\n");
11440
11574
  if (!CI_SKIP_DIRECTIVE.test(cleaned)) return { block: false, message: "" };
@@ -11463,7 +11597,7 @@ async function inspectReleaseVersionState(root, upstream) {
11463
11597
  }
11464
11598
  async function readPackageVersion(root, relPath) {
11465
11599
  try {
11466
- const data = JSON.parse(await readFile18(path39.join(root, relPath), "utf8"));
11600
+ const data = JSON.parse(await readFile18(path40.join(root, relPath), "utf8"));
11467
11601
  return typeof data.version === "string" ? data.version : void 0;
11468
11602
  } catch {
11469
11603
  return void 0;
@@ -11651,8 +11785,8 @@ function buildScore(findings, threshold = 80) {
11651
11785
  };
11652
11786
  }
11653
11787
  async function installGitEnforcement(root) {
11654
- const hooksDir = path39.join(root, ".git", "hooks");
11655
- if (!existsSync41(path39.join(root, ".git"))) {
11788
+ const hooksDir = path40.join(root, ".git", "hooks");
11789
+ if (!existsSync42(path40.join(root, ".git"))) {
11656
11790
  ui.warn("No .git directory found; git enforcement hooks skipped.");
11657
11791
  return;
11658
11792
  }
@@ -11708,8 +11842,8 @@ _hivelore sync --quiet --since ORIG_HEAD || true
11708
11842
  }
11709
11843
  ];
11710
11844
  for (const hook of hooks) {
11711
- const file = path39.join(hooksDir, hook.name);
11712
- if (existsSync41(file)) {
11845
+ const file = path40.join(hooksDir, hook.name);
11846
+ if (existsSync42(file)) {
11713
11847
  const current = await readFile18(file, "utf8").catch(() => "");
11714
11848
  if (current.includes(ENFORCE_HOOK_MARKER)) {
11715
11849
  await writeFile25(file, hook.body, "utf8");
@@ -11726,10 +11860,10 @@ ${hook.body}`, "utf8");
11726
11860
  ui.success("Installed git hooks: pre-commit, pre-push, commit-msg (blocking) + post-merge, post-rewrite (sync)");
11727
11861
  }
11728
11862
  async function installCiEnforcement(root) {
11729
- const workflowPath = path39.join(root, ".github", "workflows", "haive-enforcement.yml");
11730
- 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 });
11731
11865
  const workflow = renderCiEnforcementWorkflow();
11732
- if (existsSync41(workflowPath)) {
11866
+ if (existsSync42(workflowPath)) {
11733
11867
  const existing = await readFile18(workflowPath, "utf8");
11734
11868
  const start = "# haive:enforcement-workflow:start";
11735
11869
  const end = "# haive:enforcement-workflow:end";
@@ -11737,14 +11871,14 @@ async function installCiEnforcement(root) {
11737
11871
  const endAt = existing.indexOf(end);
11738
11872
  if (startAt >= 0 && endAt > startAt) {
11739
11873
  await writeFile25(workflowPath, existing.slice(0, startAt) + workflow + existing.slice(endAt + end.length), "utf8");
11740
- ui.success(`Updated ${path39.relative(root, workflowPath)} managed block`);
11874
+ ui.success(`Updated ${path40.relative(root, workflowPath)} managed block`);
11741
11875
  } else {
11742
11876
  ui.info("GitHub Actions enforcement workflow already exists without Hivelore markers \u2014 preserved");
11743
11877
  }
11744
11878
  return;
11745
11879
  }
11746
11880
  await writeFile25(workflowPath, workflow, "utf8");
11747
- ui.success(`Created ${path39.relative(root, workflowPath)}`);
11881
+ ui.success(`Created ${path40.relative(root, workflowPath)}`);
11748
11882
  }
11749
11883
  function renderCiEnforcementWorkflow() {
11750
11884
  return `# haive:enforcement-workflow:start
@@ -11947,15 +12081,15 @@ function extractToolPaths(payload, root) {
11947
12081
  }
11948
12082
  function normalizeToolPath(file, root) {
11949
12083
  const normalized = file.replace(/\\/g, "/");
11950
- if (!path39.isAbsolute(normalized)) return normalized.replace(/^\.\//, "");
11951
- return path39.relative(root, normalized).replace(/\\/g, "/");
12084
+ if (!path40.isAbsolute(normalized)) return normalized.replace(/^\.\//, "");
12085
+ return path40.relative(root, normalized).replace(/\\/g, "/");
11952
12086
  }
11953
12087
  async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
11954
- if (!existsSync41(paths.memoriesDir)) return [];
12088
+ if (!existsSync42(paths.memoriesDir)) return [];
11955
12089
  const marker = await readRecentBriefingMarker(paths, sessionId);
11956
12090
  const consulted = new Set(marker?.memory_ids ?? []);
11957
12091
  const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention", "attempt"]);
11958
- const all = await loadMemoriesFromDir14(paths.memoriesDir);
12092
+ const all = await loadMemoriesFromDir15(paths.memoriesDir);
11959
12093
  return all.filter(({ memory: memory2 }) => {
11960
12094
  const fm = memory2.frontmatter;
11961
12095
  if (!policyTypes.has(fm.type)) return false;
@@ -11996,7 +12130,7 @@ async function readStdin2(maxBytes) {
11996
12130
  }
11997
12131
  var ATOMIC_STAGE_EXCLUDE = ["/.usage/", "/.runtime/", "/.cache/"];
11998
12132
  async function stageResyncedArtifacts(root, paths) {
11999
- const aiRel = path39.relative(root, paths.haiveDir);
12133
+ const aiRel = path40.relative(root, paths.haiveDir);
12000
12134
  const out = await runCommand2("git", ["diff", "--name-only", "--", aiRel], root).catch(() => "");
12001
12135
  const toStage = out.split("\n").map((line) => line.trim()).filter(Boolean).filter((file) => !ATOMIC_STAGE_EXCLUDE.some((excl) => `/${file}`.includes(excl)));
12002
12136
  if (toStage.length === 0) return;
@@ -12023,9 +12157,9 @@ function runCommand2(cmd, args, cwd) {
12023
12157
  }
12024
12158
 
12025
12159
  // src/commands/release.ts
12026
- import { existsSync as existsSync42 } from "fs";
12160
+ import { existsSync as existsSync43 } from "fs";
12027
12161
  import { readFile as readFile19, writeFile as writeFile26 } from "fs/promises";
12028
- import path40 from "path";
12162
+ import path41 from "path";
12029
12163
  import "commander";
12030
12164
  import { findProjectRoot as findProjectRoot38 } from "@hivelore/core";
12031
12165
  import { execFile as execFile6 } from "child_process";
@@ -12039,7 +12173,7 @@ var VERSION_FILES2 = [
12039
12173
  "packages/embeddings/package.json"
12040
12174
  ];
12041
12175
  async function readCurrentVersion(root) {
12042
- 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"));
12043
12177
  if (!pkg.version) throw new Error("Root package.json has no version field.");
12044
12178
  return pkg.version;
12045
12179
  }
@@ -12060,8 +12194,8 @@ function registerRelease(program2) {
12060
12194
  const current = await readCurrentVersion(root);
12061
12195
  const next = nextVersion(current, spec);
12062
12196
  for (const rel of VERSION_FILES2) {
12063
- const file = path40.join(root, rel);
12064
- if (!existsSync42(file)) {
12197
+ const file = path41.join(root, rel);
12198
+ if (!existsSync43(file)) {
12065
12199
  ui.warn(`skip ${rel} (missing)`);
12066
12200
  continue;
12067
12201
  }
@@ -12074,8 +12208,8 @@ function registerRelease(program2) {
12074
12208
  }
12075
12209
  await writeFile26(file, updated, "utf8");
12076
12210
  }
12077
- const changelog = path40.join(root, "CHANGELOG.md");
12078
- if (existsSync42(changelog)) {
12211
+ const changelog = path41.join(root, "CHANGELOG.md");
12212
+ if (existsSync43(changelog)) {
12079
12213
  const raw = await readFile19(changelog, "utf8");
12080
12214
  const heading = `## [${next}]${opts.title ? ` \u2014 ${opts.title}` : ""}`;
12081
12215
  if (!raw.includes(`## [${next}]`)) {
@@ -12098,8 +12232,8 @@ ${heading}
12098
12232
  const root = findProjectRoot38(opts.dir);
12099
12233
  const version = await readCurrentVersion(root);
12100
12234
  for (const rel of VERSION_FILES2.slice(1)) {
12101
- const file = path40.join(root, rel);
12102
- if (!existsSync42(file)) continue;
12235
+ const file = path41.join(root, rel);
12236
+ if (!existsSync43(file)) continue;
12103
12237
  const v = JSON.parse(await readFile19(file, "utf8")).version;
12104
12238
  if (v !== version) {
12105
12239
  ui.error(`${rel} is at ${v}, root at ${version} \u2014 lockstep broken; run \`hivelore release bump\` first.`);
@@ -12145,9 +12279,9 @@ function registerRun(program2) {
12145
12279
 
12146
12280
  // src/commands/sensors.ts
12147
12281
  import { execFile as execFile7 } from "child_process";
12148
- import { existsSync as existsSync43 } from "fs";
12282
+ import { existsSync as existsSync44 } from "fs";
12149
12283
  import { chmod as chmod2, mkdir as mkdir17, readFile as readFile20, writeFile as writeFile27 } from "fs/promises";
12150
- import path41 from "path";
12284
+ import path42 from "path";
12151
12285
  import { promisify as promisify7 } from "util";
12152
12286
  import "commander";
12153
12287
  import {
@@ -12160,12 +12294,13 @@ import {
12160
12294
  judgeProposedSensor,
12161
12295
  loadConfig as loadConfig13,
12162
12296
  loadSensorLedger as loadSensorLedger4,
12163
- loadMemoriesFromDir as loadMemoriesFromDir15,
12297
+ loadMemoriesFromDir as loadMemoriesFromDir16,
12164
12298
  normalizeFramework,
12165
12299
  parseLessonFields,
12166
12300
  recordPreventionHits as recordPreventionHits2,
12167
12301
  resolveHaivePaths as resolveHaivePaths36,
12168
12302
  runSensors as runSensors2,
12303
+ buildProposeCommand,
12169
12304
  scaffoldPostIncidentTest,
12170
12305
  selectCommandSensors as selectCommandSensors2,
12171
12306
  TEST_FRAMEWORKS,
@@ -12212,7 +12347,7 @@ function registerSensors(program2) {
12212
12347
  const root = findProjectRoot39(opts.dir);
12213
12348
  const paths = resolveHaivePaths36(root);
12214
12349
  const memories = await runnableSensorMemories(paths);
12215
- 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);
12216
12351
  const targets = scannableSensorTargets(diff);
12217
12352
  const hits = runSensors2(memories, targets);
12218
12353
  const config = await loadConfig13(paths);
@@ -12344,7 +12479,7 @@ function registerSensors(program2) {
12344
12479
  }
12345
12480
  const root = findProjectRoot39(opts.dir);
12346
12481
  const paths = resolveHaivePaths36(root);
12347
- const loaded = existsSync43(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
12482
+ const loaded = existsSync44(paths.memoriesDir) ? await loadMemoriesFromDir16(paths.memoriesDir) : [];
12348
12483
  const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
12349
12484
  if (!found) {
12350
12485
  ui.error(`No memory found with id ${id}`);
@@ -12411,7 +12546,7 @@ function registerSensors(program2) {
12411
12546
  return;
12412
12547
  }
12413
12548
  const root2 = findProjectRoot39(opts.dir);
12414
- const { proposeSensor } = await import("./server-HG2K3WOQ.js");
12549
+ const { proposeSensor } = await import("./server-47VOVJJT.js");
12415
12550
  const out = await proposeSensor(
12416
12551
  {
12417
12552
  memory_id: id,
@@ -12455,7 +12590,7 @@ function registerSensors(program2) {
12455
12590
  }
12456
12591
  const root = findProjectRoot39(opts.dir);
12457
12592
  const paths = resolveHaivePaths36(root);
12458
- const loaded = existsSync43(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
12593
+ const loaded = existsSync44(paths.memoriesDir) ? await loadMemoriesFromDir16(paths.memoriesDir) : [];
12459
12594
  const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
12460
12595
  if (!found) {
12461
12596
  ui.error(`No memory found with id ${id}`);
@@ -12499,13 +12634,18 @@ function registerSensors(program2) {
12499
12634
  ui.info(
12500
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}`)
12501
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
+ }
12502
12642
  });
12503
12643
  sensors.command("scaffold").description(
12504
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"
12505
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) => {
12506
12646
  const root = findProjectRoot39(opts.dir);
12507
12647
  const paths = resolveHaivePaths36(root);
12508
- const loaded = existsSync43(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
12648
+ const loaded = existsSync44(paths.memoriesDir) ? await loadMemoriesFromDir16(paths.memoriesDir) : [];
12509
12649
  const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
12510
12650
  if (!found) {
12511
12651
  ui.error(`No memory found with id ${id}`);
@@ -12513,43 +12653,59 @@ function registerSensors(program2) {
12513
12653
  return;
12514
12654
  }
12515
12655
  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) {
12656
+ const forced = opts.framework ? normalizeFramework(opts.framework) : null;
12657
+ if (opts.framework && !forced) {
12519
12658
  ui.error(`Unknown --framework "${opts.framework}". Use one of: ${TEST_FRAMEWORKS.join(", ")}.`);
12520
12659
  process.exitCode = 1;
12521
12660
  return;
12522
12661
  }
12662
+ const allGroups = await detectTestFrameworksForAnchors(root, fm.anchor.paths ?? []);
12663
+ const groups = opts.out ? allGroups.slice(0, 1) : allGroups;
12523
12664
  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 }
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 })
12534
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
+ }
12535
12683
  if (opts.stdout) {
12536
- console.log(scaffold.content);
12537
- 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}`);
12538
12689
  return;
12539
12690
  }
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;
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.`);
12545
12704
  }
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
12705
  ui.info(" 1. Fill in the assertion (RED on the incident, GREEN once fixed).");
12550
- ui.info(` 2. Run it: ${scaffold.runCommand}`);
12706
+ ui.info(` 2. Run ${scaffolds.length > 1 ? "them" : "it"}: ${scaffolds.map((s) => s.runCommand).join(" && ")}`);
12551
12707
  ui.info(" 3. Arm it as a deterministic gate:");
12552
- console.log(` ${scaffold.proposeCommand}`);
12708
+ console.log(` ${proposeCmd}`);
12553
12709
  });
12554
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) => {
12555
12711
  const format = opts.format ?? "grep";
@@ -12561,13 +12717,13 @@ function registerSensors(program2) {
12561
12717
  const root = findProjectRoot39(opts.dir);
12562
12718
  const paths = resolveHaivePaths36(root);
12563
12719
  const rows = await sensorRows(paths);
12564
- const outDir = path41.resolve(root, opts.outDir ?? ".ai/generated");
12720
+ const outDir = path42.resolve(root, opts.outDir ?? ".ai/generated");
12565
12721
  await mkdir17(outDir, { recursive: true });
12566
- 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");
12567
12723
  const content = format === "grep" ? renderGrepScript(rows) : JSON.stringify({ sensors: rows }, null, 2) + "\n";
12568
12724
  await writeFile27(outPath, content, "utf8");
12569
12725
  if (format === "grep") await chmod2(outPath, 493);
12570
- ui.success(`Exported ${rows.length} sensor(s): ${path41.relative(root, outPath)}`);
12726
+ ui.success(`Exported ${rows.length} sensor(s): ${path42.relative(root, outPath)}`);
12571
12727
  });
12572
12728
  }
12573
12729
  function deriveProposedMessage(body, pattern, absent) {
@@ -12600,8 +12756,8 @@ async function sensorRows(paths) {
12600
12756
  });
12601
12757
  }
12602
12758
  async function runnableSensorMemories(paths, regexOnly = true) {
12603
- if (!existsSync43(paths.memoriesDir)) return [];
12604
- const loaded = await loadMemoriesFromDir15(paths.memoriesDir);
12759
+ if (!existsSync44(paths.memoriesDir)) return [];
12760
+ const loaded = await loadMemoriesFromDir16(paths.memoriesDir);
12605
12761
  return loaded.map(({ memory: memory2 }) => memory2).filter((memory2) => {
12606
12762
  const sensor = memory2.frontmatter.sensor;
12607
12763
  if (!sensor) return false;
@@ -12642,15 +12798,15 @@ function shellQuote(value) {
12642
12798
  }
12643
12799
 
12644
12800
  // src/commands/ingest.ts
12645
- import { existsSync as existsSync44 } from "fs";
12801
+ import { existsSync as existsSync45 } from "fs";
12646
12802
  import { mkdir as mkdir18, readFile as readFile21, writeFile as writeFile28 } from "fs/promises";
12647
- import path42 from "path";
12803
+ import path43 from "path";
12648
12804
  import "commander";
12649
12805
  import {
12650
12806
  draftsFromFindings,
12651
12807
  filterNewDrafts,
12652
12808
  findProjectRoot as findProjectRoot40,
12653
- loadMemoriesFromDir as loadMemoriesFromDir16,
12809
+ loadMemoriesFromDir as loadMemoriesFromDir17,
12654
12810
  memoryFilePath as memoryFilePath7,
12655
12811
  parseFindings,
12656
12812
  resolveHaivePaths as resolveHaivePaths37,
@@ -12680,7 +12836,7 @@ function registerIngest(program2) {
12680
12836
  }
12681
12837
  const root = findProjectRoot40(opts.dir);
12682
12838
  const paths = resolveHaivePaths37(root);
12683
- if (!existsSync44(paths.haiveDir)) {
12839
+ if (!existsSync45(paths.haiveDir)) {
12684
12840
  ui.error(`No .ai/ found at ${root}. Run \`hivelore init\` first.`);
12685
12841
  process.exitCode = 1;
12686
12842
  return;
@@ -12701,8 +12857,8 @@ function registerIngest(program2) {
12701
12857
  process.exitCode = 1;
12702
12858
  return;
12703
12859
  }
12704
- const reportPath = path42.resolve(root, file);
12705
- if (!existsSync44(reportPath)) {
12860
+ const reportPath = path43.resolve(root, file);
12861
+ if (!existsSync45(reportPath)) {
12706
12862
  ui.error(`Report file not found: ${reportPath}`);
12707
12863
  process.exitCode = 1;
12708
12864
  return;
@@ -12734,7 +12890,7 @@ function registerIngest(program2) {
12734
12890
  process.exitCode = 1;
12735
12891
  return;
12736
12892
  }
12737
- const existing = existsSync44(paths.memoriesDir) ? await loadMemoriesFromDir16(paths.memoriesDir) : [];
12893
+ const existing = existsSync45(paths.memoriesDir) ? await loadMemoriesFromDir17(paths.memoriesDir) : [];
12738
12894
  const existingTopics = new Set(
12739
12895
  existing.map(({ memory: memory2 }) => memory2.frontmatter.topic).filter((t) => Boolean(t))
12740
12896
  );
@@ -12796,13 +12952,13 @@ function registerIngest(program2) {
12796
12952
  await writeDraft(paths, draft);
12797
12953
  created++;
12798
12954
  }
12799
- 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)}/`);
12800
12956
  ui.info("Review with `hivelore memory pending`; promote sensors with `hivelore sensors promote <id> --yes`.");
12801
12957
  });
12802
12958
  }
12803
12959
  async function writeDraft(paths, draft) {
12804
12960
  const file = memoryFilePath7(paths, draft.frontmatter.scope, draft.frontmatter.id, draft.frontmatter.module);
12805
- await mkdir18(path42.dirname(file), { recursive: true });
12961
+ await mkdir18(path43.dirname(file), { recursive: true });
12806
12962
  await writeFile28(file, serializeMemory16({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
12807
12963
  return file;
12808
12964
  }
@@ -12844,13 +13000,13 @@ async function fetchSonarIssues(opts) {
12844
13000
  }
12845
13001
 
12846
13002
  // src/commands/dashboard.ts
12847
- import { existsSync as existsSync45 } from "fs";
13003
+ import { existsSync as existsSync46 } from "fs";
12848
13004
  import "commander";
12849
13005
  import {
12850
13006
  buildDashboard,
12851
13007
  findProjectRoot as findProjectRoot41,
12852
13008
  loadConfig as loadConfig14,
12853
- loadMemoriesFromDir as loadMemoriesFromDir17,
13009
+ loadMemoriesFromDir as loadMemoriesFromDir18,
12854
13010
  loadPreventionEvents as loadPreventionEvents4,
12855
13011
  loadUsageIndex as loadUsageIndex16,
12856
13012
  resolveHaivePaths as resolveHaivePaths38
@@ -12861,12 +13017,12 @@ function registerDashboard(program2) {
12861
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) => {
12862
13018
  const root = findProjectRoot41(opts.dir);
12863
13019
  const paths = resolveHaivePaths38(root);
12864
- if (!existsSync45(paths.haiveDir)) {
13020
+ if (!existsSync46(paths.haiveDir)) {
12865
13021
  ui.error(`No .ai/ found at ${root}. Run \`hivelore init\` first.`);
12866
13022
  process.exitCode = 1;
12867
13023
  return;
12868
13024
  }
12869
- const memories = existsSync45(paths.memoriesDir) ? await loadMemoriesFromDir17(paths.memoriesDir) : [];
13025
+ const memories = existsSync46(paths.memoriesDir) ? await loadMemoriesFromDir18(paths.memoriesDir) : [];
12870
13026
  const usage = await loadUsageIndex16(paths);
12871
13027
  const preventionEvents = await loadPreventionEvents4(paths);
12872
13028
  const config = await loadConfig14(paths);
@@ -12992,8 +13148,8 @@ function warnNum(n) {
12992
13148
  // src/commands/dev-link.ts
12993
13149
  import { execFile as execFile8 } from "child_process";
12994
13150
  import { cp, readFile as readFile22 } from "fs/promises";
12995
- import { existsSync as existsSync46 } from "fs";
12996
- import path43 from "path";
13151
+ import { existsSync as existsSync47 } from "fs";
13152
+ import path44 from "path";
12997
13153
  import { promisify as promisify8 } from "util";
12998
13154
  import "commander";
12999
13155
  import { findProjectRoot as findProjectRoot42 } from "@hivelore/core";
@@ -13002,7 +13158,7 @@ function registerDevLink(program2) {
13002
13158
  const dev = program2.commands.find((c) => c.name() === "dev") ?? program2.command("dev").description("Developer utilities for working on Hivelore itself.");
13003
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) => {
13004
13160
  const root = findProjectRoot42(opts.dir);
13005
- if (!existsSync46(path43.join(root, "packages", "cli", "dist", "index.js"))) {
13161
+ if (!existsSync47(path44.join(root, "packages", "cli", "dist", "index.js"))) {
13006
13162
  ui.error(`Not the Hivelore monorepo (no packages/cli/dist) at ${root}. Run \`pnpm -r build\` first, or pass --dir.`);
13007
13163
  process.exitCode = 1;
13008
13164
  return;
@@ -13011,9 +13167,9 @@ function registerDevLink(program2) {
13011
13167
  try {
13012
13168
  globalModules = (await exec6("npm", ["root", "-g"])).stdout.trim();
13013
13169
  } catch {
13014
- 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");
13015
13171
  }
13016
- 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));
13017
13173
  if (scopeDirs.length === 0) {
13018
13174
  ui.error(`No global @hivelore (or legacy @hiveai) install under ${globalModules}. Install once with \`npm i -g @hivelore/cli\`, then re-run.`);
13019
13175
  process.exitCode = 1;
@@ -13021,24 +13177,24 @@ function registerDevLink(program2) {
13021
13177
  }
13022
13178
  const linked = [];
13023
13179
  const copyDist = async (fromPkg, toDistDir) => {
13024
- const from = path43.join(root, "packages", fromPkg, "dist");
13025
- 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;
13026
13182
  await cp(from, toDistDir, { recursive: true });
13027
- linked.push(path43.relative(globalModules, toDistDir));
13183
+ linked.push(path44.relative(globalModules, toDistDir));
13028
13184
  };
13029
13185
  for (const globalHive of scopeDirs) {
13030
- const nestedScope = path43.basename(globalHive);
13186
+ const nestedScope = path44.basename(globalHive);
13031
13187
  for (const pkg of ["cli", "mcp"]) {
13032
- await copyDist(pkg, path43.join(globalHive, pkg, "dist"));
13188
+ await copyDist(pkg, path44.join(globalHive, pkg, "dist"));
13033
13189
  for (const nested of ["core", "embeddings"]) {
13034
- 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"));
13035
13191
  }
13036
13192
  }
13037
- await copyDist("core", path43.join(globalHive, "core", "dist"));
13193
+ await copyDist("core", path44.join(globalHive, "core", "dist"));
13038
13194
  }
13039
13195
  let version = "unknown";
13040
13196
  try {
13041
- 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";
13042
13198
  } catch {
13043
13199
  }
13044
13200
  if (opts.json) {
@@ -13049,7 +13205,7 @@ function registerDevLink(program2) {
13049
13205
  ui.warn("Nothing linked \u2014 no matching dist targets were found in the global install.");
13050
13206
  return;
13051
13207
  }
13052
- 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(", ")}`);
13053
13209
  for (const t of linked) console.log(` ${ui.dim("\u2192")} ${t}`);
13054
13210
  console.log(ui.dim("The global binary now runs your local build (git hooks + MCP included)."));
13055
13211
  });
@@ -13057,8 +13213,8 @@ function registerDevLink(program2) {
13057
13213
 
13058
13214
  // src/commands/coverage.ts
13059
13215
  import { readFile as readFile23 } from "fs/promises";
13060
- import { existsSync as existsSync47 } from "fs";
13061
- import path44 from "path";
13216
+ import { existsSync as existsSync48 } from "fs";
13217
+ import path45 from "path";
13062
13218
  import "commander";
13063
13219
  import {
13064
13220
  findCoverageGaps,
@@ -13068,7 +13224,7 @@ import {
13068
13224
  tallyHotFiles
13069
13225
  } from "@hivelore/core";
13070
13226
  async function readAgentHotFiles(root, cacheFile, sinceMs) {
13071
- if (!existsSync47(cacheFile)) return [];
13227
+ if (!existsSync48(cacheFile)) return [];
13072
13228
  const raw = await readFile23(cacheFile, "utf8").catch(() => "");
13073
13229
  const files = [];
13074
13230
  for (const line of raw.split("\n")) {
@@ -13082,7 +13238,7 @@ async function readAgentHotFiles(root, cacheFile, sinceMs) {
13082
13238
  }
13083
13239
  for (const f of obs.files ?? []) {
13084
13240
  if (typeof f !== "string" || !f) continue;
13085
- const rel = path44.isAbsolute(f) ? path44.relative(root, f) : f;
13241
+ const rel = path45.isAbsolute(f) ? path45.relative(root, f) : f;
13086
13242
  if (rel.startsWith("..")) continue;
13087
13243
  files.push(rel);
13088
13244
  }
@@ -13126,7 +13282,7 @@ function registerCoverage(program2) {
13126
13282
  }) : null;
13127
13283
  const gitHotFiles = (radar?.hotFiles ?? []).filter((h) => !isNoisePath(h.path)).map((h) => ({ path: h.path, changes: h.changes, source: "git" }));
13128
13284
  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)) : [];
13285
+ const agentHotFiles = useAgent ? (await readAgentHotFiles(root, path45.join(paths.haiveDir, ".cache", "observations.jsonl"), sinceMs)).filter((h) => !isNoisePath(h.path)) : [];
13130
13286
  const hotFiles = mergeHotFiles(gitHotFiles, agentHotFiles);
13131
13287
  const memories = await loadMemoriesFromDir(paths.memoriesDir);
13132
13288
  const gaps = findCoverageGaps(hotFiles, memories, { minChanges, limit });
@@ -13164,8 +13320,8 @@ function registerCoverage(program2) {
13164
13320
 
13165
13321
  // src/commands/merge-driver.ts
13166
13322
  import { execFileSync as execFileSync3 } from "child_process";
13167
- import { readFileSync, writeFileSync, existsSync as existsSync48 } from "fs";
13168
- import path45 from "path";
13323
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync49 } from "fs";
13324
+ import path46 from "path";
13169
13325
  import "commander";
13170
13326
  import { findProjectRoot as findProjectRoot44, mergeMemoryVersions } from "@hivelore/core";
13171
13327
  var GITATTRIBUTES_MARK = "# Hivelore merge driver";
@@ -13178,8 +13334,8 @@ function registerMergeDriver(program2) {
13178
13334
  const cmd = program2.command("merge-driver").description("Deterministic git merge driver for Hivelore memory files (kills .ai/ conflict markers)");
13179
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) => {
13180
13336
  try {
13181
- const oursContent = readFileSync(ours, "utf8");
13182
- const theirsContent = readFileSync(theirs, "utf8");
13337
+ const oursContent = readFileSync2(ours, "utf8");
13338
+ const theirsContent = readFileSync2(theirs, "utf8");
13183
13339
  const result = mergeMemoryVersions(oursContent, theirsContent);
13184
13340
  if (result.content !== oursContent) writeFileSync(ours, result.content, "utf8");
13185
13341
  process.exit(0);
@@ -13197,8 +13353,8 @@ function registerMergeDriver(program2) {
13197
13353
  process.exitCode = 1;
13198
13354
  return;
13199
13355
  }
13200
- const gaPath = path45.join(root, ".gitattributes");
13201
- let content = existsSync48(gaPath) ? readFileSync(gaPath, "utf8") : "";
13356
+ const gaPath = path46.join(root, ".gitattributes");
13357
+ let content = existsSync49(gaPath) ? readFileSync2(gaPath, "utf8") : "";
13202
13358
  if (!content.includes(GITATTRIBUTES_MARK)) {
13203
13359
  if (content.length > 0 && !content.endsWith("\n")) content += "\n";
13204
13360
  content += GITATTRIBUTES_BLOCK + "\n";
@@ -13212,8 +13368,8 @@ function registerMergeDriver(program2) {
13212
13368
  }
13213
13369
 
13214
13370
  // src/commands/bridges.ts
13215
- import { existsSync as existsSync49 } from "fs";
13216
- import path46 from "path";
13371
+ import { existsSync as existsSync50 } from "fs";
13372
+ import path47 from "path";
13217
13373
  import "commander";
13218
13374
  import {
13219
13375
  findProjectRoot as findProjectRoot45,
@@ -13234,7 +13390,7 @@ function registerBridges(program2) {
13234
13390
  const root = findProjectRoot45(opts.dir);
13235
13391
  const paths = resolveHaivePaths40(root);
13236
13392
  const dryRun = opts.dryRun === true;
13237
- if (!existsSync49(paths.memoriesDir)) {
13393
+ if (!existsSync50(paths.memoriesDir)) {
13238
13394
  ui.warn(`No .ai/memories at ${root}. Run \`hivelore init\` first.`);
13239
13395
  process.exitCode = 1;
13240
13396
  return;
@@ -13253,7 +13409,7 @@ function registerBridges(program2) {
13253
13409
  targets = BRIDGE_TARGETS4;
13254
13410
  } else {
13255
13411
  targets = BRIDGE_TARGETS4.filter(
13256
- (t) => existsSync49(path46.join(root, BRIDGE_TARGET_PATH3[t]))
13412
+ (t) => existsSync50(path47.join(root, BRIDGE_TARGET_PATH3[t]))
13257
13413
  );
13258
13414
  if (targets.length === 0) {
13259
13415
  ui.info(
@@ -13301,7 +13457,7 @@ function registerBridges(program2) {
13301
13457
 
13302
13458
  // src/index.ts
13303
13459
  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);
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);
13305
13461
  registerInit(program);
13306
13462
  registerResolveProject(program);
13307
13463
  registerEnforce(program);