@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/{chunk-EJ7A4IKD.js → chunk-FXDGOBPT.js} +97 -38
- package/dist/chunk-FXDGOBPT.js.map +1 -0
- package/dist/index.js +169 -37
- package/dist/index.js.map +1 -1
- package/dist/{server-PZWIQUU7.js → server-WW6JHBYY.js} +2 -2
- package/package.json +9 -5
- package/dist/chunk-EJ7A4IKD.js.map +0 -1
- /package/dist/{server-PZWIQUU7.js.map → server-WW6JHBYY.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
readPresumedCorrectTargets,
|
|
14
14
|
runAstSensorOnContent,
|
|
15
15
|
runHaiveMcpStdio
|
|
16
|
-
} from "./chunk-
|
|
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.
|
|
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
|
-
|
|
7806
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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-
|
|
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.
|
|
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.
|
|
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.
|
|
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(() =>
|
|
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
|
|
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-
|
|
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
|
|
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
|
|
13360
|
-
|
|
13361
|
-
|
|
13362
|
-
|
|
13363
|
-
|
|
13364
|
-
|
|
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}
|
|
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.
|
|
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);
|