@hivelore/cli 0.42.1 → 0.43.2

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
@@ -13,7 +13,7 @@ import {
13
13
  readPresumedCorrectTargets,
14
14
  runAstSensorOnContent,
15
15
  runHaiveMcpStdio
16
- } from "./chunk-EJ7A4IKD.js";
16
+ } from "./chunk-FXDGOBPT.js";
17
17
  import {
18
18
  registerMemoryPending
19
19
  } from "./chunk-OYJKHD22.js";
@@ -3758,7 +3758,7 @@ ${SEED_FOOTER(stack)}` });
3758
3758
 
3759
3759
  // src/commands/init.ts
3760
3760
  var execFileAsync = promisify2(execFile2);
3761
- var HAIVE_GITHUB_ACTION_REF = `v${"0.42.1"}`;
3761
+ var HAIVE_GITHUB_ACTION_REF = `v${"0.43.2"}`;
3762
3762
  var PROJECT_CONTEXT_TEMPLATE = `# Project context
3763
3763
 
3764
3764
  > Generated by \`hivelore init\`. Run \`hivelore init --bootstrap\` to auto-fill from your codebase,
@@ -7757,7 +7757,9 @@ function registerBenchmark(program2) {
7757
7757
  " - Hivelore agents must run `hivelore briefing --files ... --task ...` first.",
7758
7758
  " - Plain agents must not read `.ai` or call Hivelore.",
7759
7759
  "5. Require every agent to write `BENCHMARK_AGENT_REPORT.md`.",
7760
+ " Its `## Outcome` section must include: Task completed, Tests passed, Policy violations, Duration seconds, Total tokens.",
7760
7761
  "6. Run `hivelore benchmark report --dir <benchmark-root> --out RESULTS.md`.",
7762
+ "7. Do not make comparative claims until evidence_grade=decision-ready (>=10 paired tasks with complete outcomes).",
7761
7763
  "",
7762
7764
  "Recommended metrics: pass rate, test iterations, files read, files changed, visible artifacts, decision quality, and token proxy."
7763
7765
  ].join("\n"));
@@ -7795,15 +7797,32 @@ function parseAgentReport(fixture, report) {
7795
7797
  terminal_failures: countMatches(section(report, "Terminal Errors"), /fail|error|not raised|exited with code 1/gi),
7796
7798
  decision_mentions: sectionBulletCount(report, "Key Decisions"),
7797
7799
  report_tokens_est: estimateTokens2(report),
7798
- haive_impact: /Hivelore Memory Impact[\s\S]*?\b(yes|directly|changed|shaped|confirmed)\b/i.test(report)
7800
+ haive_impact: /Hivelore Memory Impact[\s\S]*?\b(yes|directly|changed|shaped|confirmed)\b/i.test(report),
7801
+ task_completed: reportBoolean(report, "Task completed"),
7802
+ tests_passed: reportBoolean(report, "Tests passed"),
7803
+ policy_violations: reportNumber(report, "Policy violations"),
7804
+ duration_seconds: reportNumber(report, "Duration seconds"),
7805
+ total_tokens: reportNumber(report, "Total tokens")
7799
7806
  };
7800
7807
  }
7801
7808
  function summarizeRows(rows) {
7802
7809
  const byGroup = (group) => rows.filter((r) => r.group === group);
7810
+ const haiveRows = byGroup("haive");
7811
+ const plainRows = byGroup("plain");
7812
+ const taskName = (fixture) => fixture.replace(/-(haive|plain)$/, "");
7813
+ const plainTasks = new Set(plainRows.map((row) => taskName(row.fixture)));
7814
+ const pairedTasks = new Set(haiveRows.map((row) => taskName(row.fixture)).filter((name) => plainTasks.has(name))).size;
7815
+ const outcomeComplete = rows.length > 0 && rows.every(
7816
+ (row) => row.task_completed !== null && row.tests_passed !== null && row.policy_violations !== null && row.duration_seconds !== null && row.total_tokens !== null
7817
+ );
7818
+ const decisionReady = pairedTasks >= 10 && outcomeComplete;
7803
7819
  return {
7804
7820
  fixtures: rows.length,
7805
- haive: summarizeGroup(byGroup("haive")),
7806
- plain: summarizeGroup(byGroup("plain"))
7821
+ paired_tasks: pairedTasks,
7822
+ evidence_grade: decisionReady ? "decision-ready" : "insufficient",
7823
+ evidence_reason: decisionReady ? "At least 10 paired tasks with complete correctness, policy, duration, and token outcomes." : `Need >=10 paired tasks and complete Outcome fields; found ${pairedTasks} pair(s), outcome_complete=${outcomeComplete}.`,
7824
+ haive: summarizeGroup(haiveRows),
7825
+ plain: summarizeGroup(plainRows)
7807
7826
  };
7808
7827
  }
7809
7828
  function summarizeGroup(rows) {
@@ -7817,7 +7836,12 @@ function summarizeGroup(rows) {
7817
7836
  terminal_failures: sum("terminal_failures"),
7818
7837
  decision_mentions: sum("decision_mentions"),
7819
7838
  report_tokens_est: sum("report_tokens_est"),
7820
- haive_impact_count: rows.filter((r) => r.haive_impact).length
7839
+ haive_impact_count: rows.filter((r) => r.haive_impact).length,
7840
+ completed: rows.filter((r) => r.task_completed === true).length,
7841
+ tests_passed: rows.filter((r) => r.tests_passed === true).length,
7842
+ policy_violations: rows.reduce((total, row) => total + (row.policy_violations ?? 0), 0),
7843
+ duration_seconds: rows.reduce((total, row) => total + (row.duration_seconds ?? 0), 0),
7844
+ total_tokens: rows.reduce((total, row) => total + (row.total_tokens ?? 0), 0)
7821
7845
  };
7822
7846
  }
7823
7847
  function renderMarkdown(root, summary, rows) {
@@ -7828,6 +7852,8 @@ function renderMarkdown(root, summary, rows) {
7828
7852
  "",
7829
7853
  "## Summary",
7830
7854
  "",
7855
+ `Evidence grade: **${summary.evidence_grade}** \u2014 ${summary.evidence_reason}`,
7856
+ "",
7831
7857
  "| Group | Fixtures | Commands | Files read | Files modified | Test iterations | Terminal failures | Decision mentions | Report tokens (est, report only) | Hivelore impact |",
7832
7858
  "| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |",
7833
7859
  groupLine("Hivelore", summary.haive),
@@ -7851,6 +7877,23 @@ function renderMarkdown(root, summary, rows) {
7851
7877
  ];
7852
7878
  return lines.join("\n");
7853
7879
  }
7880
+ function reportValue(report, label) {
7881
+ const match = new RegExp(`^[-*]\\s*${escapeRegExp(label)}\\s*:\\s*(.+)$`, "im").exec(report);
7882
+ return match?.[1]?.trim() ?? null;
7883
+ }
7884
+ function reportBoolean(report, label) {
7885
+ const value = reportValue(report, label);
7886
+ if (!value) return null;
7887
+ if (/^(yes|true|pass|passed|complete|completed)$/i.test(value)) return true;
7888
+ if (/^(no|false|fail|failed|incomplete)$/i.test(value)) return false;
7889
+ return null;
7890
+ }
7891
+ function reportNumber(report, label) {
7892
+ const value = reportValue(report, label);
7893
+ if (!value) return null;
7894
+ const parsed = Number(value.replace(/,/g, ""));
7895
+ return Number.isFinite(parsed) ? parsed : null;
7896
+ }
7854
7897
  function groupLine(label, group) {
7855
7898
  return `| ${label} | ${group.fixtures} | ${group.commands} | ${group.files_read} | ${group.files_modified} | ${group.test_iterations} | ${group.terminal_failures} | ${group.decision_mentions} | ${group.report_tokens_est} | ${group.haive_impact_count} |`;
7856
7899
  }
@@ -7978,6 +8021,8 @@ function registerEval(program2) {
7978
8021
  const authoredRetrievalCases = retrievalAgg ? retrievalAgg.cases.slice(resolvedSpec.synthesized) : [];
7979
8022
  const authoredRetrievalAgg = authoredRetrievalCases.length > 0 ? aggregateRetrieval(authoredRetrievalCases) : null;
7980
8023
  const authoredScore = resolvedSpec.authored > 0 && resolvedSpec.synthesized > 0 && (authoredRetrievalAgg || sensorAgg) ? overallScore(authoredRetrievalAgg, sensorAgg) : null;
8024
+ const authoredReport = resolvedSpec.authored > 0 && (authoredRetrievalAgg || sensorAgg) ? buildReport(authoredRetrievalAgg, sensorAgg) : null;
8025
+ const gateReport = authoredReport ?? report;
7981
8026
  const [usage, preventionEvents, config] = await Promise.all([
7982
8027
  loadUsageIndex13(paths),
7983
8028
  loadPreventionEvents3(paths),
@@ -8006,6 +8051,7 @@ function registerEval(program2) {
8006
8051
  k,
8007
8052
  spec_source: resolvedSpec.source,
8008
8053
  report,
8054
+ ...authoredReport ? { authored_report: authoredReport } : {},
8009
8055
  gate_precision: gatePrecision
8010
8056
  };
8011
8057
  await mkdir13(path32.dirname(baselineFile), { recursive: true });
@@ -8025,7 +8071,7 @@ function registerEval(program2) {
8025
8071
  }
8026
8072
  } else {
8027
8073
  const snapshot = JSON.parse(await readFile15(baselineFile, "utf8"));
8028
- delta = compareEvalReports(snapshot.report, report);
8074
+ delta = compareEvalReports(snapshot.authored_report ?? snapshot.report, gateReport);
8029
8075
  if (snapshot.gate_precision) {
8030
8076
  gateDelta = compareGatePrecision(snapshot.gate_precision, gatePrecision);
8031
8077
  }
@@ -8042,6 +8088,7 @@ function registerEval(program2) {
8042
8088
  ...authoredScore !== null ? { authored_score: authoredScore } : {}
8043
8089
  },
8044
8090
  report,
8091
+ ...authoredReport ? { authored_report: authoredReport } : {},
8045
8092
  tier_contract: { checks: tierChecks, failures: tierFailures.length },
8046
8093
  proposed_golden_cases: proposedGoldenCount,
8047
8094
  gate_precision: gatePrecision,
@@ -8049,7 +8096,7 @@ function registerEval(program2) {
8049
8096
  ...gateDelta ? { gate_delta: gateDelta } : {}
8050
8097
  }, null, 2));
8051
8098
  if (tierFailures.length > 0) process.exitCode = 1;
8052
- applyExitGates(opts, report, delta, gatePrecision, gateDelta);
8099
+ applyExitGates(opts, gateReport, delta, gatePrecision, gateDelta);
8053
8100
  return;
8054
8101
  }
8055
8102
  if (resolvedSpec.authored === 0 && resolvedSpec.synthesized > 0) {
@@ -8083,7 +8130,7 @@ function registerEval(program2) {
8083
8130
  console.log(md);
8084
8131
  }
8085
8132
  if (tierFailures.length > 0) process.exitCode = 1;
8086
- applyExitGates(opts, report, delta, gatePrecision, gateDelta);
8133
+ applyExitGates(opts, gateReport, delta, gatePrecision, gateDelta);
8087
8134
  });
8088
8135
  }
8089
8136
  function parsePctThreshold(label, raw) {
@@ -8172,7 +8219,7 @@ async function resolveSpec(opts, root, memoriesDir) {
8172
8219
  if (existsSync35(defaultSpec)) {
8173
8220
  const raw = await readFile15(defaultSpec, "utf8");
8174
8221
  const explicit = JSON.parse(raw);
8175
- const memories2 = await loadMemoriesFromDir(memoriesDir);
8222
+ const memories2 = (await loadMemoriesFromDir(memoriesDir)).filter(({ memory: memory2 }) => memory2.frontmatter.scope !== "personal");
8176
8223
  const synthesized2 = synthesizeSelfEvalCases(memories2, { includeFiles: !opts.semanticOnly });
8177
8224
  return {
8178
8225
  spec: {
@@ -8184,7 +8231,7 @@ async function resolveSpec(opts, root, memoriesDir) {
8184
8231
  authored: countCases(explicit)
8185
8232
  };
8186
8233
  }
8187
- const memories = await loadMemoriesFromDir(memoriesDir);
8234
+ const memories = (await loadMemoriesFromDir(memoriesDir)).filter(({ memory: memory2 }) => memory2.frontmatter.scope !== "personal");
8188
8235
  const synthesized = synthesizeSelfEvalCases(memories, { includeFiles: !opts.semanticOnly });
8189
8236
  return {
8190
8237
  spec: { retrieval: synthesized },
@@ -8206,6 +8253,8 @@ async function runRetrieval(c, k, ctx) {
8206
8253
  semantic: true,
8207
8254
  include_stale: false,
8208
8255
  track: false,
8256
+ memory_scopes: ["team", "module"],
8257
+ deterministic: true,
8209
8258
  format: "compact",
8210
8259
  min_semantic_score: 0
8211
8260
  },
@@ -9026,9 +9075,35 @@ function registerDoctor(program2) {
9026
9075
  fix: "hivelore sensors list # then `hivelore sensors promote <id>` for a trusted, non-brittle sensor \u2014 or retire the noise"
9027
9076
  });
9028
9077
  }
9078
+ const commandSensorMemories = sensorMemories.filter((m) => {
9079
+ const kind = m.memory.frontmatter.sensor?.kind;
9080
+ return kind === "shell" || kind === "test";
9081
+ });
9082
+ if (commandSensorMemories.length > 0 && config.enforcement?.runCommandSensors !== true) {
9083
+ findings.push({
9084
+ severity: "error",
9085
+ code: "command-sensors-disabled",
9086
+ section: "Protection",
9087
+ message: `${commandSensorMemories.length} command/test sensor(s) exist but enforcement.runCommandSensors is not true \u2014 their behaviour protection is OFF.`,
9088
+ fix: "Set enforcement.runCommandSensors=true after reviewing the repo-authored commands."
9089
+ });
9090
+ }
9091
+ const unprovenBlocks = commandSensorMemories.filter((m) => {
9092
+ const sensor = m.memory.frontmatter.sensor;
9093
+ return sensor.severity === "block" && sensor.red_proven !== true;
9094
+ });
9095
+ if (unprovenBlocks.length > 0) {
9096
+ findings.push({
9097
+ severity: "error",
9098
+ code: "command-sensors-block-without-red",
9099
+ section: "Protection",
9100
+ message: `${unprovenBlocks.length} blocking command/test sensor(s) predate mandatory prove-RED and cannot substantiate their incident claim.`,
9101
+ fix: "Re-propose each sensor with --red-ref <pre-fix-commit>, or demote it to warn."
9102
+ });
9103
+ }
9029
9104
  const astSensorCount = sensorMemories.filter((m) => m.memory.frontmatter.sensor?.kind === "ast").length;
9030
9105
  if (astSensorCount > 0) {
9031
- const { astEngineAvailable: astEngineAvailable2 } = await import("./server-PZWIQUU7.js");
9106
+ const { astEngineAvailable: astEngineAvailable2 } = await import("./server-WW6JHBYY.js");
9032
9107
  if (!await astEngineAvailable2()) {
9033
9108
  findings.push({
9034
9109
  severity: "warn",
@@ -9198,7 +9273,7 @@ function registerDoctor(program2) {
9198
9273
  fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `hivelore init` without --manual)."
9199
9274
  });
9200
9275
  }
9201
- findings.push(...await collectInstallFindings(root, "0.42.1"));
9276
+ findings.push(...await collectInstallFindings(root, "0.43.2"));
9202
9277
  findings.push(...await collectToolchainFindings(root));
9203
9278
  try {
9204
9279
  const legacyRaw = execSync("haive-mcp --version", {
@@ -9206,7 +9281,7 @@ function registerDoctor(program2) {
9206
9281
  timeout: 3e3,
9207
9282
  stdio: ["ignore", "pipe", "ignore"]
9208
9283
  }).trim();
9209
- const cliVersion = "0.42.1";
9284
+ const cliVersion = "0.43.2";
9210
9285
  if (legacyRaw && legacyRaw !== cliVersion) {
9211
9286
  findings.push({
9212
9287
  severity: "warn",
@@ -10889,7 +10964,7 @@ async function checkBootstrapComplete(paths, config, productionCodeChanged) {
10889
10964
  }
10890
10965
  const memories = existsSync42(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
10891
10966
  const codeMap = await loadCodeMap7(paths);
10892
- const codeFiles = codeMap ? Object.keys(codeMap.files) : [];
10967
+ const codeFiles = codeMap ? Object.keys(codeMap.files).filter((file) => existsSync42(path40.join(paths.root, file))) : [];
10893
10968
  let existingModules = [];
10894
10969
  try {
10895
10970
  const entries = await readdir4(paths.modulesContextDir, { withFileTypes: true });
@@ -10953,7 +11028,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
10953
11028
  findings: [{ severity: "info", code: "enforcement-off", message: "Hivelore enforcement is disabled." }]
10954
11029
  });
10955
11030
  }
10956
- findings.push(...await inspectIntegrationVersions(root, "0.42.1"));
11031
+ findings.push(...await inspectIntegrationVersions(root, "0.43.2"));
10957
11032
  if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
10958
11033
  const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
10959
11034
  findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent Hivelore briefing marker exists." } : {
@@ -10987,7 +11062,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
10987
11062
  findings.push(...await verifyDecisionCoverage(paths, stage, sessionId));
10988
11063
  }
10989
11064
  if (stage === "pre-commit" || stage === "ci") {
10990
- findings.push(...await runPrecommitPolicy(paths, config.enforcement?.antiPatternGate ?? "anchored", stage));
11065
+ findings.push(...await runPrecommitPolicy(paths, config.enforcement?.antiPatternGate ?? "anchored", stage, config));
10991
11066
  } else if (stage === "local") {
10992
11067
  findings.push({
10993
11068
  severity: "info",
@@ -11194,16 +11269,16 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
11194
11269
  impact: Math.min(35, 10 + missing.length * 5)
11195
11270
  }];
11196
11271
  }
11197
- async function runPrecommitPolicy(paths, gate, stage) {
11272
+ async function runPrecommitPolicy(paths, gate, stage, config) {
11198
11273
  const snapshot = await getPolicyDiffSnapshot(paths.root, stage);
11199
11274
  const weakenings = detectSensorWeakening(snapshot.diff);
11200
11275
  const weakeningFindings = weakenings.length > 0 ? [{
11201
- severity: "warn",
11276
+ severity: config.enforcement?.sensorWeakeningGate === "block" ? "error" : "warn",
11202
11277
  code: "sensor-weakened",
11203
11278
  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" : "") + ".",
11204
11279
  fix: "If the demotion/removal is intentional, say so in the commit message; otherwise restore the sensor (`hivelore sensors list` shows the current state).",
11205
11280
  memory_ids: [...new Set(weakenings.map((w) => w.memory_id))].slice(0, 10),
11206
- impact: 8
11281
+ impact: config.enforcement?.sensorWeakeningGate === "block" ? 30 : 8
11207
11282
  }] : [];
11208
11283
  if (gate === "off") {
11209
11284
  return [
@@ -11367,7 +11442,7 @@ async function runSensorGate(paths, diff, stage) {
11367
11442
  } else {
11368
11443
  for (const memory2 of astSensorMemories) {
11369
11444
  const sensor = memory2.frontmatter.sensor;
11370
- if (!sensor.pattern) continue;
11445
+ if (!sensor.pattern && !sensor.rule) continue;
11371
11446
  const applicable = targets.filter((t) => sensorAppliesToPath2(sensor, memory2.frontmatter.anchor.paths, t.path));
11372
11447
  if (applicable.length === 0) continue;
11373
11448
  let fired = false;
@@ -11378,6 +11453,8 @@ async function runSensorGate(paths, diff, stage) {
11378
11453
  if (content === null) continue;
11379
11454
  const scan = await runAstSensorOnContent({
11380
11455
  pattern: sensor.pattern,
11456
+ rule: sensor.rule,
11457
+ language: sensor.language,
11381
11458
  absent: sensor.absent,
11382
11459
  content,
11383
11460
  filePath: target.path,
@@ -11422,7 +11499,7 @@ async function runSensorGate(paths, diff, stage) {
11422
11499
  }
11423
11500
  }
11424
11501
  }
11425
- const config = await loadConfig12(paths).catch(() => null);
11502
+ const config = await loadConfig12(paths).catch(() => ({}));
11426
11503
  if (config?.enforcement?.runCommandSensors === true) {
11427
11504
  const changedPaths = targets.map((t) => t.path).filter(Boolean);
11428
11505
  const specs = selectCommandSensors(scannable, changedPaths).filter((sp) => !seen.has(sp.memory_id));
@@ -11460,13 +11537,14 @@ async function runSensorGate(paths, diff, stage) {
11460
11537
  if (run.status === "passed") continue;
11461
11538
  seen.add(run.memory_id);
11462
11539
  if (run.status === "unrunnable") {
11540
+ const strictUnrunnable = config.enforcement?.commandSensorUnrunnable === "block";
11463
11541
  findings.push({
11464
- severity: "warn",
11542
+ severity: strictUnrunnable ? "error" : "warn",
11465
11543
  code: "command-sensor-unrunnable",
11466
11544
  message: `Command sensor ${run.memory_id} could not run (${run.unrunnable_reason}): \`${run.command}\`` + (run.output_tail ? `
11467
11545
  ${run.output_tail}` : ""),
11468
11546
  fix: "Fix the sensor's command (or its timeout_ms), or demote it: `hivelore sensors promote <id> --severity warn`.",
11469
- impact: 5
11547
+ impact: strictUnrunnable ? 35 : 5
11470
11548
  });
11471
11549
  continue;
11472
11550
  }
@@ -12589,6 +12667,7 @@ function registerSensors(program2) {
12589
12667
  );
12590
12668
  if ("absent" in row && row.absent) console.log(` ${ui.dim("only when missing:")} ${row.absent}`);
12591
12669
  if (row.paths.length > 0) console.log(` ${ui.dim("paths:")} ${row.paths.join(", ")}`);
12670
+ if (row.red_proven) console.log(` ${ui.green("\u2713 RED-proven")}${row.incident ? ` \u2014 ${row.incident}` : ""}`);
12592
12671
  if (row.last_fired) console.log(` ${ui.dim("last fired:")} ${row.last_fired}`);
12593
12672
  if (brittle) console.log(` ${ui.yellow("\u26A0 brittle:")} ${brittle} \u2014 consider rewriting or retiring this sensor`);
12594
12673
  }
@@ -12631,6 +12710,8 @@ function registerSensors(program2) {
12631
12710
  if (content === null) continue;
12632
12711
  const scan = await runAstSensorOnContent({
12633
12712
  pattern: sensor.pattern,
12713
+ rule: sensor.rule,
12714
+ language: sensor.language,
12634
12715
  absent: sensor.absent,
12635
12716
  content,
12636
12717
  filePath: target.path,
@@ -12798,6 +12879,11 @@ function registerSensors(program2) {
12798
12879
  process.exitCode = 1;
12799
12880
  return;
12800
12881
  }
12882
+ if (severity === "block" && (sensor.kind === "shell" || sensor.kind === "test") && sensor.red_proven !== true) {
12883
+ ui.error("Refusing to promote an unproven command/test oracle to block. Re-propose it with --red-ref <pre-fix-commit>.");
12884
+ process.exitCode = 1;
12885
+ return;
12886
+ }
12801
12887
  const brittle = sensor.kind === "regex" && sensor.pattern ? sensorPatternBrittleness2(sensor.pattern) : null;
12802
12888
  if (severity === "block" && brittle && !opts.force) {
12803
12889
  ui.error(`Refusing to block on a brittle sensor (${brittle}). Rewrite the pattern, or pass --force.`);
@@ -12844,25 +12930,39 @@ function registerSensors(program2) {
12844
12930
  });
12845
12931
  sensors.command("propose").description(
12846
12932
  "Propose a discriminating sensor for a memory \u2014 you write the pattern, Hivelore validates it before\n trusting it to block. Mirrors the MCP `propose_sensor` tool (the agent-authored path).\n\n A `block` proposal is accepted ONLY if it is not brittle, stays SILENT on the current code,\n and FIRES on the bad example. Rejected proposals are not written \u2014 fix and re-run.\n\n Example:\n hivelore sensors propose <memory-id> \\\n --pattern 'stripe\\.paymentIntents\\.create' --absent 'idempotencyKey' \\\n --bad-example 'stripe.paymentIntents.create({ amount })'"
12847
- ).argument("<memory-id>", "memory id to attach the sensor to").option("--kind <kind>", "regex (default) | ast (structural \u2014 comments/strings can't false-positive) | shell | test (route the team's own oracle)", "regex").option("--pattern <regex>", "kind=regex: regex matching the FAULTY usage").option("--command <cmd>", "kind=shell|test: command the gate runs when the diff touches the sensor's paths").option("--timeout <ms>", "kind=shell|test: max runtime in ms (default 120000)").option("--absent <regex>", "regex for the CORRECT-usage marker (makes it discriminate)").option("--bad-example <code>", "a snippet that SHOULD match (else examples are read from the lesson)").option("--severity <severity>", "block | warn", "block").option("--message <text>", "fix message shown when it fires").option("--incident <ref>", "provenance: the incident this sensor guards (e.g. 'prod #442') \u2014 shown when it fires and in the receipt").option("--red-ref <ref>", "kind=shell|test: pre-fix commit/ref \u2014 validation replays it in a scratch worktree and requires the oracle to FAIL there (records red_proven)").option("--flags <flags>", "regex flags (e.g. i)").option("--paths <csv>", "override scope paths (defaults to the memory anchors)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
12933
+ ).argument("<memory-id>", "memory id to attach the sensor to").option("--kind <kind>", "regex (default) | ast (structural \u2014 comments/strings can't false-positive) | shell | test (route the team's own oracle)", "regex").option("--pattern <regex>", "kind=regex: regex matching the FAULTY usage").option("--rule <json>", "kind=ast: full ast-grep Rule JSON (kind/inside/has/not/all/any)").option("--language <name>", "kind=ast: explicit language for non-standard extensions").option("--command <cmd>", "kind=shell|test: command the gate runs when the diff touches the sensor's paths").option("--timeout <ms>", "kind=shell|test: max runtime in ms (default 120000)").option("--absent <regex>", "regex for the CORRECT-usage marker (makes it discriminate)").option("--bad-example <code>", "a snippet that SHOULD match (else examples are read from the lesson)").option("--severity <severity>", "block | warn", "block").option("--message <text>", "fix message shown when it fires").option("--incident <ref>", "provenance: the incident this sensor guards (e.g. 'prod #442') \u2014 shown when it fires and in the receipt").option("--red-ref <ref>", "kind=shell|test: pre-fix commit/ref \u2014 validation replays it in a scratch worktree and requires the oracle to FAIL there (records red_proven)").option("--flags <flags>", "regex flags (e.g. i)").option("--paths <csv>", "override scope paths (defaults to the memory anchors)").option("-d, --dir <dir>", "project root").action(async (id, opts) => {
12848
12934
  if (opts.kind === "shell" || opts.kind === "test" || opts.kind === "ast") {
12849
12935
  if ((opts.kind === "shell" || opts.kind === "test") && !opts.command?.trim()) {
12850
12936
  ui.error("--kind shell|test requires --command.");
12851
12937
  process.exitCode = 1;
12852
12938
  return;
12853
12939
  }
12854
- if (opts.kind === "ast" && !opts.pattern?.trim()) {
12855
- ui.error("--kind ast requires --pattern (an ast-grep structural pattern).");
12940
+ if (opts.kind === "ast" && !opts.pattern?.trim() && !opts.rule?.trim()) {
12941
+ ui.error("--kind ast requires --pattern or --rule <json>.");
12856
12942
  process.exitCode = 1;
12857
12943
  return;
12858
12944
  }
12945
+ let astRule;
12946
+ if (opts.rule?.trim()) {
12947
+ try {
12948
+ const parsed = JSON.parse(opts.rule);
12949
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("rule must be a JSON object");
12950
+ astRule = parsed;
12951
+ } catch (err) {
12952
+ ui.error(`--rule must be valid ast-grep Rule JSON: ${err instanceof Error ? err.message : String(err)}`);
12953
+ process.exitCode = 1;
12954
+ return;
12955
+ }
12956
+ }
12859
12957
  const root2 = findProjectRoot39(opts.dir);
12860
- const { proposeSensor } = await import("./server-PZWIQUU7.js");
12958
+ const { proposeSensor } = await import("./server-WW6JHBYY.js");
12861
12959
  const out = await proposeSensor(
12862
12960
  {
12863
12961
  memory_id: id,
12864
12962
  kind: opts.kind,
12865
- pattern: opts.kind === "ast" ? opts.pattern.trim() : void 0,
12963
+ pattern: opts.kind === "ast" ? opts.pattern?.trim() : void 0,
12964
+ rule: astRule,
12965
+ language: opts.kind === "ast" ? opts.language : void 0,
12866
12966
  command: opts.command?.trim(),
12867
12967
  timeout_ms: opts.timeout ? Math.max(1, Number(opts.timeout)) : void 0,
12868
12968
  absent: opts.kind === "ast" ? opts.absent : void 0,
@@ -13057,10 +13157,14 @@ async function sensorRows(paths) {
13057
13157
  kind: sensor.kind,
13058
13158
  severity: sensor.severity,
13059
13159
  pattern: sensor.pattern,
13160
+ rule: sensor.rule,
13161
+ language: sensor.language,
13060
13162
  absent: sensor.absent,
13061
13163
  command: sensor.command,
13062
13164
  paths: sensor.paths.length > 0 ? sensor.paths : memory2.frontmatter.anchor.paths,
13063
13165
  message: sensor.message,
13166
+ incident: sensor.incident,
13167
+ red_proven: sensor.red_proven === true,
13064
13168
  autogen: sensor.autogen,
13065
13169
  last_fired: sensor.last_fired,
13066
13170
  ...brittle ? { brittle } : {}
@@ -13115,6 +13219,7 @@ import { mkdir as mkdir18, readFile as readFile21, writeFile as writeFile28 } fr
13115
13219
  import path43 from "path";
13116
13220
  import "commander";
13117
13221
  import {
13222
+ appendProposedRetrievalCases as appendProposedRetrievalCases2,
13118
13223
  draftsFromFindings,
13119
13224
  filterNewDrafts,
13120
13225
  findProjectRoot as findProjectRoot40,
@@ -13248,6 +13353,7 @@ function registerIngest(program2) {
13248
13353
  const created2 = [];
13249
13354
  if (!opts.dryRun) {
13250
13355
  for (const draft of fresh) created2.push(await writeDraft(paths, draft));
13356
+ if (format === "github-pr") await appendReviewGoldenCases(root, fresh);
13251
13357
  }
13252
13358
  console.log(
13253
13359
  JSON.stringify(
@@ -13300,6 +13406,7 @@ function registerIngest(program2) {
13300
13406
  await writeDraft(paths, draft);
13301
13407
  created++;
13302
13408
  }
13409
+ if (format === "github-pr") await appendReviewGoldenCases(root, fresh);
13303
13410
  ui.success(`Created ${created} proposed memory(ies) under ${path43.relative(root, paths.memoriesDir)}/`);
13304
13411
  ui.info("Review with `hivelore memory pending`; promote sensors with `hivelore sensors promote <id> --yes`.");
13305
13412
  });
@@ -13310,6 +13417,19 @@ async function writeDraft(paths, draft) {
13310
13417
  await writeFile28(file, serializeMemory16({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
13311
13418
  return file;
13312
13419
  }
13420
+ async function appendReviewGoldenCases(root, drafts) {
13421
+ if (drafts.length === 0) return;
13422
+ const specFile = path43.join(root, ".ai", "eval", "spec.json");
13423
+ const current = existsSync45(specFile) ? await readFile21(specFile, "utf8") : null;
13424
+ const cases = drafts.map((draft) => ({
13425
+ name: `review-learning:${draft.topic}`,
13426
+ task: draft.finding.message,
13427
+ ...draft.finding.path ? { files: [draft.finding.path] } : {},
13428
+ expect_ids: [draft.frontmatter.id]
13429
+ }));
13430
+ await mkdir18(path43.dirname(specFile), { recursive: true });
13431
+ await writeFile28(specFile, appendProposedRetrievalCases2(current, cases), "utf8");
13432
+ }
13313
13433
  async function fetchSonarIssues(opts) {
13314
13434
  const baseUrl = (opts.sonarUrl ?? process.env.SONAR_HOST_URL ?? "").trim().replace(/\/+$/, "");
13315
13435
  const token = (opts.sonarToken ?? process.env.SONAR_TOKEN ?? "").trim();
@@ -13356,17 +13476,29 @@ async function fetchPrReviewComments(root, ref) {
13356
13476
  const { promisify: promisify9 } = await import("util");
13357
13477
  const run = promisify9(execFile9);
13358
13478
  try {
13359
- const { stdout } = await run(
13360
- "gh",
13361
- ["api", `repos/{owner}/{repo}/pulls/${prNumber}/comments`, "--paginate"],
13362
- { cwd: root, maxBuffer: 16 * 1024 * 1024 }
13363
- );
13364
- return { ok: true, json: stdout };
13479
+ const fetchPages = async (endpoint) => {
13480
+ const { stdout } = await run(
13481
+ "gh",
13482
+ ["api", endpoint, "--paginate", "--slurp"],
13483
+ { cwd: root, maxBuffer: 16 * 1024 * 1024 }
13484
+ );
13485
+ const pages = JSON.parse(stdout);
13486
+ return Array.isArray(pages) ? pages.flatMap((page) => Array.isArray(page) ? page : []) : [];
13487
+ };
13488
+ const [reviewComments, issueComments] = await Promise.all([
13489
+ fetchPages(`repos/{owner}/{repo}/pulls/${prNumber}/comments`),
13490
+ fetchPages(`repos/{owner}/{repo}/issues/${prNumber}/comments`)
13491
+ ]);
13492
+ const normalizedIssueComments = issueComments.map((row) => ({
13493
+ ...row,
13494
+ pull_request_url: `https://api.github.com/repos/{owner}/{repo}/pulls/${prNumber}`
13495
+ }));
13496
+ return { ok: true, json: JSON.stringify([...reviewComments, ...normalizedIssueComments]) };
13365
13497
  } catch (err) {
13366
13498
  const e = err;
13367
13499
  return {
13368
13500
  ok: false,
13369
- error: `Could not fetch PR #${prNumber} review comments via gh: ${(e.stderr || e.message || String(err)).slice(0, 200)}. Install/authenticate the gh CLI, or pass a recorded comments JSON file instead.`
13501
+ error: `Could not fetch PR #${prNumber} comments via gh: ${(e.stderr || e.message || String(err)).slice(0, 200)}. Install/authenticate the gh CLI, or pass a recorded comments JSON file instead.`
13370
13502
  };
13371
13503
  }
13372
13504
  }
@@ -13829,7 +13961,7 @@ function registerBridges(program2) {
13829
13961
 
13830
13962
  // src/index.ts
13831
13963
  var program = new Command48();
13832
- program.name("hivelore").description("Hivelore - the deterministic policy gate for agent-written code (rules live as repo-native team memory)").version("0.42.1").option("--advanced", "show maintenance and experimental commands in help").showSuggestionAfterError(true);
13964
+ program.name("hivelore").description("Hivelore - the deterministic policy gate for agent-written code (rules live as repo-native team memory)").version("0.43.2").option("--advanced", "show maintenance and experimental commands in help").showSuggestionAfterError(true);
13833
13965
  registerInit(program);
13834
13966
  registerResolveProject(program);
13835
13967
  registerEnforce(program);