@hivelore/cli 0.43.2 → 0.46.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/README.md +7 -6
- package/dist/{chunk-FXDGOBPT.js → chunk-W7EPRKOZ.js} +75 -15
- package/dist/chunk-W7EPRKOZ.js.map +1 -0
- package/dist/index.js +251 -62
- package/dist/index.js.map +1 -1
- package/dist/{server-WW6JHBYY.js → server-V3ZGBTAG.js} +6 -4
- package/package.json +16 -5
- package/dist/chunk-FXDGOBPT.js.map +0 -1
- /package/dist/{server-WW6JHBYY.js.map → server-V3ZGBTAG.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-W7EPRKOZ.js";
|
|
17
17
|
import {
|
|
18
18
|
registerMemoryPending
|
|
19
19
|
} from "./chunk-OYJKHD22.js";
|
|
@@ -1299,7 +1299,7 @@ function attachSemanticCommands(embeddings, naming) {
|
|
|
1299
1299
|
minScore: Number(opts.minScore ?? 0)
|
|
1300
1300
|
});
|
|
1301
1301
|
if (!result) {
|
|
1302
|
-
ui.error("No embeddings index found. Run `hivelore
|
|
1302
|
+
ui.error("No embeddings index found. Run `hivelore index memories` first.");
|
|
1303
1303
|
process.exitCode = 1;
|
|
1304
1304
|
return;
|
|
1305
1305
|
}
|
|
@@ -1319,7 +1319,7 @@ function attachSemanticCommands(embeddings, naming) {
|
|
|
1319
1319
|
const { indexStat } = await loadEmbeddings();
|
|
1320
1320
|
const stat2 = await indexStat(paths);
|
|
1321
1321
|
if (!stat2.exists) {
|
|
1322
|
-
ui.warn("No embeddings index. Run `hivelore
|
|
1322
|
+
ui.warn("No embeddings index. Run `hivelore index memories` to create one.");
|
|
1323
1323
|
return;
|
|
1324
1324
|
}
|
|
1325
1325
|
console.log(`${ui.bold("entries:")} ${stat2.count}`);
|
|
@@ -1393,7 +1393,7 @@ function registerIndexCode(program2) {
|
|
|
1393
1393
|
mod = await import("@hivelore/embeddings");
|
|
1394
1394
|
} catch {
|
|
1395
1395
|
ui.error(
|
|
1396
|
-
"@hivelore/embeddings is not installed. Install it (`pnpm add @hivelore/embeddings`)
|
|
1396
|
+
"@hivelore/embeddings is not installed. Install it (`pnpm add @hivelore/embeddings`) then run `hivelore index code-search`."
|
|
1397
1397
|
);
|
|
1398
1398
|
process.exit(1);
|
|
1399
1399
|
}
|
|
@@ -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.46.0"}`;
|
|
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,
|
|
@@ -5259,7 +5259,7 @@ Wait for **explicit confirmation** before acting.
|
|
|
5259
5259
|
)
|
|
5260
5260
|
);
|
|
5261
5261
|
} catch {
|
|
5262
|
-
ui.warn("--embed: @hivelore/embeddings not available or index build failed.
|
|
5262
|
+
ui.warn("--embed: @hivelore/embeddings not available or index build failed. Install it, then run `hivelore index memories`.");
|
|
5263
5263
|
}
|
|
5264
5264
|
}
|
|
5265
5265
|
});
|
|
@@ -6974,6 +6974,7 @@ import {
|
|
|
6974
6974
|
loadConfig as loadConfig6,
|
|
6975
6975
|
loadMemoriesFromDir as loadMemoriesFromDir10,
|
|
6976
6976
|
loadPreventionEvents,
|
|
6977
|
+
readUsageEvents,
|
|
6977
6978
|
loadUsageIndex as loadUsageIndex11,
|
|
6978
6979
|
memoryFilePath as memoryFilePath5,
|
|
6979
6980
|
renderCaughtForYou,
|
|
@@ -7226,6 +7227,14 @@ async function observationStart(paths) {
|
|
|
7226
7227
|
}
|
|
7227
7228
|
return first;
|
|
7228
7229
|
}
|
|
7230
|
+
async function manualSessionStart(paths) {
|
|
7231
|
+
const events = await readUsageEvents(paths);
|
|
7232
|
+
for (let index = events.length - 1; index >= 0; index--) {
|
|
7233
|
+
const event = events[index];
|
|
7234
|
+
if (["get_briefing", "briefing", "session_start", "enforce_session_start"].includes(event.tool)) return event.at;
|
|
7235
|
+
}
|
|
7236
|
+
return null;
|
|
7237
|
+
}
|
|
7229
7238
|
async function printCaughtForYou(paths, since, quiet) {
|
|
7230
7239
|
if (quiet) return;
|
|
7231
7240
|
const memories = existsSync32(paths.memoriesDir) ? await loadMemoriesFromDir10(paths.memoriesDir) : [];
|
|
@@ -7275,7 +7284,7 @@ function registerSessionEnd(session2) {
|
|
|
7275
7284
|
let resolvedFiles = opts.files;
|
|
7276
7285
|
let goal = opts.goal;
|
|
7277
7286
|
let accomplished = opts.accomplished ?? opts.summary;
|
|
7278
|
-
const caughtSince = opts.auto ? await observationStart(paths) :
|
|
7287
|
+
const caughtSince = opts.auto ? await observationStart(paths) : await manualSessionStart(paths);
|
|
7279
7288
|
if (opts.auto) {
|
|
7280
7289
|
const autoCaptured = await autoCaptureFailureLessons(paths, root, await readObservationList(paths)).catch(() => ({ written: 0, ids: [] }));
|
|
7281
7290
|
const synth = await buildAutoRecap(paths);
|
|
@@ -7409,14 +7418,17 @@ import { mkdir as mkdir12, writeFile as writeFile18 } from "fs/promises";
|
|
|
7409
7418
|
import path30 from "path";
|
|
7410
7419
|
import {
|
|
7411
7420
|
aggregateUsage,
|
|
7421
|
+
assessBehaviourCoverage,
|
|
7412
7422
|
buildPreventionReceipt,
|
|
7423
|
+
loadCodeMap as loadCodeMap6,
|
|
7424
|
+
renderBehaviourCoverageLine,
|
|
7413
7425
|
renderPreventionReceipt,
|
|
7414
7426
|
findProjectRoot as findProjectRoot28,
|
|
7415
7427
|
loadMemoriesFromDir as loadMemoriesFromDir11,
|
|
7416
7428
|
loadUsageIndex as loadUsageIndex12,
|
|
7417
7429
|
loadPreventionEvents as loadPreventionEvents2,
|
|
7418
7430
|
parseSince,
|
|
7419
|
-
readUsageEvents,
|
|
7431
|
+
readUsageEvents as readUsageEvents2,
|
|
7420
7432
|
renderPreventionReceiptShare,
|
|
7421
7433
|
resolveHaivePaths as resolveHaivePaths27,
|
|
7422
7434
|
usageLogSize
|
|
@@ -7438,6 +7450,19 @@ function registerStats(program2) {
|
|
|
7438
7450
|
const receipt = buildPreventionReceipt(events, memories, usage, { since });
|
|
7439
7451
|
const output = sub.share ? renderPreventionReceiptShare(receipt) : opts.json ? JSON.stringify(receipt, null, 2) : renderPreventionReceipt(receipt);
|
|
7440
7452
|
console.log(output);
|
|
7453
|
+
if (!opts.json && !sub.share) {
|
|
7454
|
+
try {
|
|
7455
|
+
const codeMap = await loadCodeMap6(paths);
|
|
7456
|
+
if (codeMap) {
|
|
7457
|
+
const cov = assessBehaviourCoverage({ memories, codeFiles: Object.keys(codeMap.files) });
|
|
7458
|
+
if (cov.mainAreas.length > 0) {
|
|
7459
|
+
console.log(`
|
|
7460
|
+
Behaviour harness: ${renderBehaviourCoverageLine(cov)}.`);
|
|
7461
|
+
}
|
|
7462
|
+
}
|
|
7463
|
+
} catch {
|
|
7464
|
+
}
|
|
7465
|
+
}
|
|
7441
7466
|
});
|
|
7442
7467
|
stats.option("--since <window>", "ISO date or relative (e.g. '7d', '24h', '30m')", "30d").option("--json", "emit JSON instead of human-readable output", false).option("--memory-hits", "show top-read memories (which mems are actually being used)", false).option(
|
|
7443
7468
|
"--export-report <path>",
|
|
@@ -7465,7 +7490,7 @@ function registerStats(program2) {
|
|
|
7465
7490
|
);
|
|
7466
7491
|
return;
|
|
7467
7492
|
}
|
|
7468
|
-
const events = await
|
|
7493
|
+
const events = await readUsageEvents2(paths);
|
|
7469
7494
|
const since = parseSince(opts.since);
|
|
7470
7495
|
const aggregate = aggregateUsage(events, since ?? void 0);
|
|
7471
7496
|
if (opts.json) {
|
|
@@ -7501,7 +7526,7 @@ function registerStats(program2) {
|
|
|
7501
7526
|
async function writeRoiReport(paths, root, sinceRaw, outRelative) {
|
|
7502
7527
|
const outAbs = path30.isAbsolute(outRelative) ? path30.resolve(outRelative) : path30.resolve(root, outRelative);
|
|
7503
7528
|
const size = await usageLogSize(paths);
|
|
7504
|
-
let events = await
|
|
7529
|
+
let events = await readUsageEvents2(paths);
|
|
7505
7530
|
let memoryCount = { team: 0, personal: 0, total_skipped_session: 0 };
|
|
7506
7531
|
if (existsSync33(paths.memoriesDir)) {
|
|
7507
7532
|
const mems = await loadMemoriesFromDir11(paths.memoriesDir);
|
|
@@ -7666,7 +7691,7 @@ function registerBench(program2) {
|
|
|
7666
7691
|
},
|
|
7667
7692
|
async () => {
|
|
7668
7693
|
const t0 = performance.now();
|
|
7669
|
-
const out = await antiPatternsCheck({ diff: task, paths: [], limit: 5, semantic: true }, ctx);
|
|
7694
|
+
const out = await antiPatternsCheck({ diff: task, paths: [], limit: 5, semantic: true, track: false }, ctx);
|
|
7670
7695
|
return summarize("anti_patterns_check", t0, out, [
|
|
7671
7696
|
`${out.warnings.length}/${out.scanned} warn`
|
|
7672
7697
|
]);
|
|
@@ -7757,7 +7782,7 @@ function registerBenchmark(program2) {
|
|
|
7757
7782
|
" - Hivelore agents must run `hivelore briefing --files ... --task ...` first.",
|
|
7758
7783
|
" - Plain agents must not read `.ai` or call Hivelore.",
|
|
7759
7784
|
"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.",
|
|
7785
|
+
" Its `## Outcome` section must include: Task completed, Tests passed, Policy violations, Duration seconds, Total tokens, Runner ID, Evaluator ID, Independent evaluation.",
|
|
7761
7786
|
"6. Run `hivelore benchmark report --dir <benchmark-root> --out RESULTS.md`.",
|
|
7762
7787
|
"7. Do not make comparative claims until evidence_grade=decision-ready (>=10 paired tasks with complete outcomes).",
|
|
7763
7788
|
"",
|
|
@@ -7802,7 +7827,10 @@ function parseAgentReport(fixture, report) {
|
|
|
7802
7827
|
tests_passed: reportBoolean(report, "Tests passed"),
|
|
7803
7828
|
policy_violations: reportNumber(report, "Policy violations"),
|
|
7804
7829
|
duration_seconds: reportNumber(report, "Duration seconds"),
|
|
7805
|
-
total_tokens: reportNumber(report, "Total tokens")
|
|
7830
|
+
total_tokens: reportNumber(report, "Total tokens"),
|
|
7831
|
+
runner_id: reportValue(report, "Runner ID"),
|
|
7832
|
+
evaluator_id: reportValue(report, "Evaluator ID"),
|
|
7833
|
+
independent_evaluation: reportBoolean(report, "Independent evaluation")
|
|
7806
7834
|
};
|
|
7807
7835
|
}
|
|
7808
7836
|
function summarizeRows(rows) {
|
|
@@ -7813,14 +7841,14 @@ function summarizeRows(rows) {
|
|
|
7813
7841
|
const plainTasks = new Set(plainRows.map((row) => taskName(row.fixture)));
|
|
7814
7842
|
const pairedTasks = new Set(haiveRows.map((row) => taskName(row.fixture)).filter((name) => plainTasks.has(name))).size;
|
|
7815
7843
|
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
|
|
7844
|
+
(row) => row.task_completed !== null && row.tests_passed !== null && row.policy_violations !== null && row.duration_seconds !== null && row.total_tokens !== null && row.runner_id !== null && row.evaluator_id !== null && row.evaluator_id !== row.runner_id && row.independent_evaluation === true
|
|
7817
7845
|
);
|
|
7818
7846
|
const decisionReady = pairedTasks >= 10 && outcomeComplete;
|
|
7819
7847
|
return {
|
|
7820
7848
|
fixtures: rows.length,
|
|
7821
7849
|
paired_tasks: pairedTasks,
|
|
7822
7850
|
evidence_grade: decisionReady ? "decision-ready" : "insufficient",
|
|
7823
|
-
evidence_reason: decisionReady ? "At least 10 paired tasks with complete
|
|
7851
|
+
evidence_reason: decisionReady ? "At least 10 paired tasks with complete outcomes reviewed by an evaluator distinct from the runner." : `Need >=10 paired tasks, complete Outcome fields, and independent evaluator attestations; found ${pairedTasks} pair(s), outcome_complete=${outcomeComplete}.`,
|
|
7824
7852
|
haive: summarizeGroup(haiveRows),
|
|
7825
7853
|
plain: summarizeGroup(plainRows)
|
|
7826
7854
|
};
|
|
@@ -7941,7 +7969,7 @@ import {
|
|
|
7941
7969
|
function registerEval(program2) {
|
|
7942
7970
|
program2.command("eval").description(
|
|
7943
7971
|
"Rigorous, repeatable quality eval: do the right memories surface (retrieval) and do the right sensors fire (catch-rate)? Emits a numeric 0\u2013100 score. Uses .ai/eval cases via --spec, or auto-synthesizes cases from anchored memories."
|
|
7944
|
-
).option("--spec <file>", "JSON eval spec ({ retrieval: [...], sensors: [...] })").option("--semantic-only", "self-eval probes by title alone (no anchor files) \u2014 harder retrieval", false).option("-k, --top <n>", "briefing top-k considered a hit", "8").option("--json", "emit JSON", false).option("--out <file>", "write a Markdown report").option("--fail-under <score>", "exit non-zero if the overall score is below this (0\u2013100) \u2014 for CI gates").option("--fail-under-catch-rate <pct>", "exit non-zero if sensor catch-rate is below this percentage").option("--fail-under-gate-precision <pct>", "exit non-zero if gate precision is below this percentage").option("--baseline", "save this run as the baseline (.ai/eval/baseline.json) for future --compare", false).option("--compare", "diff this run against the saved baseline and print the delta", false).option("--baseline-file <path>", "baseline file to read/write (default: .ai/eval/baseline.json)").option("--fail-on-regression", "with --compare, exit non-zero if the score dropped vs the baseline", false).option("--regression-gate", "CI-safe gate: compare against the baseline IF one exists (fail on regression), else no-op", false).option("--approve-cases", "approve every proposed golden case (gate-miss labeled) into the scored retrieval set, then exit", false).option("--record", "append this run's score to .ai/.cache/eval-history.jsonl (trend the harness over time)", false).option("--trend", "print the recorded score trend (sparkline + latest/best/delta) and exit", false).option("--ref <ref>", "version/commit label stored with a --record run").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
7972
|
+
).option("--spec <file>", "JSON eval spec ({ retrieval: [...], sensors: [...] })").option("--semantic-only", "self-eval probes by title alone (no anchor files) \u2014 harder retrieval", false).option("--semantic-ranking", "run the real embeddings-backed ranking lane; fails if embeddings/index are unavailable", false).option("-k, --top <n>", "briefing top-k considered a hit", "8").option("--json", "emit JSON", false).option("--out <file>", "write a Markdown report").option("--fail-under <score>", "exit non-zero if the overall score is below this (0\u2013100) \u2014 for CI gates").option("--fail-under-catch-rate <pct>", "exit non-zero if sensor catch-rate is below this percentage").option("--fail-under-gate-precision <pct>", "exit non-zero if gate precision is below this percentage").option("--baseline", "save this run as the baseline (.ai/eval/baseline.json) for future --compare", false).option("--compare", "diff this run against the saved baseline and print the delta", false).option("--baseline-file <path>", "baseline file to read/write (default: .ai/eval/baseline.json)").option("--fail-on-regression", "with --compare, exit non-zero if the score dropped vs the baseline", false).option("--regression-gate", "CI-safe gate: compare against the baseline IF one exists (fail on regression), else no-op", false).option("--approve-cases", "approve every proposed golden case (gate-miss labeled) into the scored retrieval set, then exit", false).option("--record", "append this run's score to .ai/.cache/eval-history.jsonl (trend the harness over time)", false).option("--trend", "print the recorded score trend (sparkline + latest/best/delta) and exit", false).option("--ref <ref>", "version/commit label stored with a --record run").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
7945
7973
|
const root = findProjectRoot31(opts.dir);
|
|
7946
7974
|
const paths = resolveHaivePaths29(root);
|
|
7947
7975
|
if (!existsSync35(paths.memoriesDir)) {
|
|
@@ -8003,8 +8031,15 @@ function registerEval(program2) {
|
|
|
8003
8031
|
if (spec.retrieval && spec.retrieval.length > 0) {
|
|
8004
8032
|
const results = [];
|
|
8005
8033
|
for (const c of spec.retrieval) {
|
|
8006
|
-
const
|
|
8007
|
-
|
|
8034
|
+
const retrieval = await runRetrieval(c, k, ctx, opts.semanticRanking === true);
|
|
8035
|
+
if (opts.semanticRanking && retrieval.searchMode !== "semantic") {
|
|
8036
|
+
ui.error(
|
|
8037
|
+
"Semantic eval requested, but the embeddings package/index is unavailable. Install @hivelore/embeddings and run `hivelore index memories` first."
|
|
8038
|
+
);
|
|
8039
|
+
process.exitCode = 1;
|
|
8040
|
+
return;
|
|
8041
|
+
}
|
|
8042
|
+
results.push(scoreRetrievalCase(c.name, c.expect_ids, retrieval.ids));
|
|
8008
8043
|
}
|
|
8009
8044
|
retrievalAgg = aggregateRetrieval(results);
|
|
8010
8045
|
}
|
|
@@ -8044,7 +8079,7 @@ function registerEval(program2) {
|
|
|
8044
8079
|
});
|
|
8045
8080
|
if (!opts.json) ui.success(`Recorded eval score ${report.score}/100 to history.`);
|
|
8046
8081
|
}
|
|
8047
|
-
const baselineFile = opts.baselineFile ? path32.isAbsolute(opts.baselineFile) ? opts.baselineFile : path32.join(root, opts.baselineFile) : path32.join(root, ".ai", "eval", "baseline.json");
|
|
8082
|
+
const baselineFile = opts.baselineFile ? path32.isAbsolute(opts.baselineFile) ? opts.baselineFile : path32.join(root, opts.baselineFile) : path32.join(root, ".ai", "eval", opts.semanticRanking ? "semantic-baseline.json" : "baseline.json");
|
|
8048
8083
|
if (opts.baseline) {
|
|
8049
8084
|
const snapshot = {
|
|
8050
8085
|
saved_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -8081,6 +8116,7 @@ function registerEval(program2) {
|
|
|
8081
8116
|
console.log(JSON.stringify({
|
|
8082
8117
|
root,
|
|
8083
8118
|
k,
|
|
8119
|
+
ranking_mode: opts.semanticRanking ? "semantic" : "deterministic",
|
|
8084
8120
|
spec_source: resolvedSpec.source,
|
|
8085
8121
|
provenance: {
|
|
8086
8122
|
synthesized: resolvedSpec.synthesized,
|
|
@@ -8240,7 +8276,7 @@ async function resolveSpec(opts, root, memoriesDir) {
|
|
|
8240
8276
|
authored: 0
|
|
8241
8277
|
};
|
|
8242
8278
|
}
|
|
8243
|
-
async function runRetrieval(c, k, ctx) {
|
|
8279
|
+
async function runRetrieval(c, k, ctx, semanticRanking = false) {
|
|
8244
8280
|
const out = await getBriefing(
|
|
8245
8281
|
{
|
|
8246
8282
|
task: c.task,
|
|
@@ -8254,17 +8290,17 @@ async function runRetrieval(c, k, ctx) {
|
|
|
8254
8290
|
include_stale: false,
|
|
8255
8291
|
track: false,
|
|
8256
8292
|
memory_scopes: ["team", "module"],
|
|
8257
|
-
deterministic:
|
|
8293
|
+
deterministic: !semanticRanking,
|
|
8258
8294
|
format: "compact",
|
|
8259
8295
|
min_semantic_score: 0
|
|
8260
8296
|
},
|
|
8261
8297
|
ctx
|
|
8262
8298
|
);
|
|
8263
|
-
return out.memories.map((m) => m.id);
|
|
8299
|
+
return { ids: out.memories.map((m) => m.id), searchMode: out.search_mode };
|
|
8264
8300
|
}
|
|
8265
8301
|
async function runSensorCase(c, ctx) {
|
|
8266
8302
|
const out = await antiPatternsCheck(
|
|
8267
|
-
{ diff: c.diff, paths: c.paths ?? [], limit: 50, semantic: false },
|
|
8303
|
+
{ diff: c.diff, paths: c.paths ?? [], limit: 50, semantic: false, track: false },
|
|
8268
8304
|
ctx
|
|
8269
8305
|
);
|
|
8270
8306
|
return out.warnings.filter((w) => w.reasons.includes("sensor")).map((w) => w.id);
|
|
@@ -8357,7 +8393,7 @@ import {
|
|
|
8357
8393
|
loadMemoriesFromDir as loadMemoriesFromDir12,
|
|
8358
8394
|
memoryFilePath as memoryFilePath6,
|
|
8359
8395
|
parseSince as parseSince2,
|
|
8360
|
-
readUsageEvents as
|
|
8396
|
+
readUsageEvents as readUsageEvents3,
|
|
8361
8397
|
resolveHaivePaths as resolveHaivePaths30,
|
|
8362
8398
|
serializeMemory as serializeMemory13,
|
|
8363
8399
|
suggestTopicKey
|
|
@@ -8388,7 +8424,7 @@ function registerMemorySuggest(memory2) {
|
|
|
8388
8424
|
}
|
|
8389
8425
|
const root = findProjectRoot32(opts.dir);
|
|
8390
8426
|
const paths = resolveHaivePaths30(root);
|
|
8391
|
-
const events = await
|
|
8427
|
+
const events = await readUsageEvents3(paths);
|
|
8392
8428
|
if (events.length === 0) {
|
|
8393
8429
|
if (opts.json) {
|
|
8394
8430
|
console.log(JSON.stringify({ suggestions: [] }));
|
|
@@ -8765,6 +8801,8 @@ import "commander";
|
|
|
8765
8801
|
import {
|
|
8766
8802
|
codeMapPath as codeMapPath2,
|
|
8767
8803
|
assessSensorHealth as assessSensorHealth2,
|
|
8804
|
+
assessBehaviourCoverage as assessBehaviourCoverage2,
|
|
8805
|
+
renderBehaviourCoverageLine as renderBehaviourCoverageLine2,
|
|
8768
8806
|
sensorPromotedAtMap as sensorPromotedAtMap2,
|
|
8769
8807
|
countSourceFilesOnDisk,
|
|
8770
8808
|
extractReferencedPaths,
|
|
@@ -8773,12 +8811,12 @@ import {
|
|
|
8773
8811
|
findProjectRoot as findProjectRoot35,
|
|
8774
8812
|
getUsage as getUsage9,
|
|
8775
8813
|
isStackPackSeed as isStackPackSeed2,
|
|
8776
|
-
loadCodeMap as
|
|
8814
|
+
loadCodeMap as loadCodeMap7,
|
|
8777
8815
|
loadConfig as loadConfig10,
|
|
8778
8816
|
loadMemoriesFromDirDetailed,
|
|
8779
8817
|
loadSensorLedger as loadSensorLedger2,
|
|
8780
8818
|
loadUsageIndex as loadUsageIndex15,
|
|
8781
|
-
readUsageEvents as
|
|
8819
|
+
readUsageEvents as readUsageEvents4,
|
|
8782
8820
|
resolveHaivePaths as resolveHaivePaths33
|
|
8783
8821
|
} from "@hivelore/core";
|
|
8784
8822
|
|
|
@@ -9027,14 +9065,15 @@ function registerDoctor(program2) {
|
|
|
9027
9065
|
}
|
|
9028
9066
|
}
|
|
9029
9067
|
const lintReport = await lintMemoriesAsync(root);
|
|
9030
|
-
|
|
9068
|
+
const actionableLintFindings = lintReport.findings.filter((finding) => finding.severity !== "info");
|
|
9069
|
+
if (actionableLintFindings.length > 0) {
|
|
9031
9070
|
const warnCount = lintReport.findings.filter((finding) => finding.severity === "warn").length;
|
|
9032
9071
|
const errorCount = lintReport.findings.filter((finding) => finding.severity === "error").length;
|
|
9033
9072
|
const severity = errorCount > 0 ? "error" : warnCount > 0 ? "warn" : "info";
|
|
9034
9073
|
findings.push({
|
|
9035
9074
|
severity,
|
|
9036
9075
|
code: "memory-lint-findings",
|
|
9037
|
-
message: `memory lint reports ${
|
|
9076
|
+
message: `memory lint reports ${actionableLintFindings.length} actionable finding${actionableLintFindings.length === 1 ? "" : "s"} (${errorCount} error, ${warnCount} warn).`,
|
|
9038
9077
|
fix: "hivelore memory lint --fix --apply"
|
|
9039
9078
|
});
|
|
9040
9079
|
}
|
|
@@ -9103,7 +9142,7 @@ function registerDoctor(program2) {
|
|
|
9103
9142
|
}
|
|
9104
9143
|
const astSensorCount = sensorMemories.filter((m) => m.memory.frontmatter.sensor?.kind === "ast").length;
|
|
9105
9144
|
if (astSensorCount > 0) {
|
|
9106
|
-
const { astEngineAvailable: astEngineAvailable2 } = await import("./server-
|
|
9145
|
+
const { astEngineAvailable: astEngineAvailable2 } = await import("./server-V3ZGBTAG.js");
|
|
9107
9146
|
if (!await astEngineAvailable2()) {
|
|
9108
9147
|
findings.push({
|
|
9109
9148
|
severity: "warn",
|
|
@@ -9164,7 +9203,7 @@ function registerDoctor(program2) {
|
|
|
9164
9203
|
fix: "Review the sensor manually; no automatic delete/retire command is provided."
|
|
9165
9204
|
});
|
|
9166
9205
|
}
|
|
9167
|
-
const codeMap = await
|
|
9206
|
+
const codeMap = await loadCodeMap7(paths);
|
|
9168
9207
|
if (!codeMap) {
|
|
9169
9208
|
findings.push({
|
|
9170
9209
|
severity: "warn",
|
|
@@ -9186,7 +9225,7 @@ function registerDoctor(program2) {
|
|
|
9186
9225
|
});
|
|
9187
9226
|
}
|
|
9188
9227
|
const onDisk = await countSourceFilesOnDisk(root);
|
|
9189
|
-
if (
|
|
9228
|
+
if (isCodeMapNearEmpty(indexedCount, onDisk)) {
|
|
9190
9229
|
findings.push({
|
|
9191
9230
|
severity: "warn",
|
|
9192
9231
|
code: "code-map-near-empty",
|
|
@@ -9196,6 +9235,7 @@ function registerDoctor(program2) {
|
|
|
9196
9235
|
}
|
|
9197
9236
|
}
|
|
9198
9237
|
findings.push(...await collectHarnessCoverageFindings(codeMap, memories));
|
|
9238
|
+
findings.push(...collectBehaviourCoverageFindings(codeMap, memories));
|
|
9199
9239
|
findings.push(...await collectSemanticIndexFindings(paths, config, memories.length, codeMap));
|
|
9200
9240
|
try {
|
|
9201
9241
|
const scaffoldGaps = await collectScaffoldLoopGaps(paths);
|
|
@@ -9209,7 +9249,7 @@ function registerDoctor(program2) {
|
|
|
9209
9249
|
}
|
|
9210
9250
|
} catch {
|
|
9211
9251
|
}
|
|
9212
|
-
const events = await
|
|
9252
|
+
const events = await readUsageEvents4(paths);
|
|
9213
9253
|
if (events.length === 0) {
|
|
9214
9254
|
findings.push({
|
|
9215
9255
|
severity: "info",
|
|
@@ -9273,7 +9313,8 @@ function registerDoctor(program2) {
|
|
|
9273
9313
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `hivelore init` without --manual)."
|
|
9274
9314
|
});
|
|
9275
9315
|
}
|
|
9276
|
-
findings.push(...await collectInstallFindings(root, "0.
|
|
9316
|
+
findings.push(...await collectInstallFindings(root, "0.46.0"));
|
|
9317
|
+
findings.push(...await collectMcpRuntimeFindings(paths, "0.46.0"));
|
|
9277
9318
|
findings.push(...await collectToolchainFindings(root));
|
|
9278
9319
|
try {
|
|
9279
9320
|
const legacyRaw = execSync("haive-mcp --version", {
|
|
@@ -9281,7 +9322,7 @@ function registerDoctor(program2) {
|
|
|
9281
9322
|
timeout: 3e3,
|
|
9282
9323
|
stdio: ["ignore", "pipe", "ignore"]
|
|
9283
9324
|
}).trim();
|
|
9284
|
-
const cliVersion = "0.
|
|
9325
|
+
const cliVersion = "0.46.0";
|
|
9285
9326
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
9286
9327
|
findings.push({
|
|
9287
9328
|
severity: "warn",
|
|
@@ -9337,6 +9378,39 @@ npm uninstall -g @hivelore/mcp`
|
|
|
9337
9378
|
emit(findings, opts, repairs);
|
|
9338
9379
|
});
|
|
9339
9380
|
}
|
|
9381
|
+
function isCodeMapNearEmpty(indexedCount, sourceFilesOnDisk) {
|
|
9382
|
+
return sourceFilesOnDisk >= 20 && indexedCount < sourceFilesOnDisk * 0.05;
|
|
9383
|
+
}
|
|
9384
|
+
function mcpRuntimeVersionFinding(marker, expectedVersion, processAlive) {
|
|
9385
|
+
if (!processAlive || !marker.version || marker.version === expectedVersion) return null;
|
|
9386
|
+
return {
|
|
9387
|
+
severity: "warn",
|
|
9388
|
+
code: "active-mcp-server-stale",
|
|
9389
|
+
message: `An active MCP server is still v${marker.version} while the installed CLI is v${expectedVersion}. MCP clients keep server processes alive across package upgrades.`,
|
|
9390
|
+
fix: "Restart the IDE/agent MCP client, then confirm get_briefing.server_version matches `hivelore --version`.",
|
|
9391
|
+
section: "Agent coverage"
|
|
9392
|
+
};
|
|
9393
|
+
}
|
|
9394
|
+
async function collectMcpRuntimeFindings(paths, expectedVersion) {
|
|
9395
|
+
const markerFile = path37.join(paths.runtimeDir, "mcp-server.json");
|
|
9396
|
+
if (!existsSync40(markerFile)) return [];
|
|
9397
|
+
try {
|
|
9398
|
+
const marker = JSON.parse(await readFile16(markerFile, "utf8"));
|
|
9399
|
+
let alive = false;
|
|
9400
|
+
if (typeof marker.pid === "number") {
|
|
9401
|
+
try {
|
|
9402
|
+
process.kill(marker.pid, 0);
|
|
9403
|
+
alive = true;
|
|
9404
|
+
} catch {
|
|
9405
|
+
alive = false;
|
|
9406
|
+
}
|
|
9407
|
+
}
|
|
9408
|
+
const finding = mcpRuntimeVersionFinding(marker, expectedVersion, alive);
|
|
9409
|
+
return finding ? [finding] : [];
|
|
9410
|
+
} catch {
|
|
9411
|
+
return [];
|
|
9412
|
+
}
|
|
9413
|
+
}
|
|
9340
9414
|
function emit(findings, opts, repairs = []) {
|
|
9341
9415
|
const classified = findings.map((finding) => ({
|
|
9342
9416
|
...finding,
|
|
@@ -9505,6 +9579,33 @@ async function collectHarnessCoverageFindings(codeMap, memories) {
|
|
|
9505
9579
|
});
|
|
9506
9580
|
return findings;
|
|
9507
9581
|
}
|
|
9582
|
+
function collectBehaviourCoverageFindings(codeMap, memories) {
|
|
9583
|
+
if (!codeMap) return [];
|
|
9584
|
+
const codeFiles = Object.keys(codeMap.files);
|
|
9585
|
+
const cov = assessBehaviourCoverage2({ memories, codeFiles });
|
|
9586
|
+
if (cov.mainAreas.length === 0) return [];
|
|
9587
|
+
const uncoveredHint = cov.uncoveredAreas.length > 0 && cov.totalOracles > 0 ? `
|
|
9588
|
+
No behavioural oracle: ${cov.uncoveredAreas.slice(0, 5).map((a) => `\`${a}\``).join(", ")}` + (cov.uncoveredAreas.length > 5 ? `, +${cov.uncoveredAreas.length - 5} more` : "") : "";
|
|
9589
|
+
let fix;
|
|
9590
|
+
if (cov.uncoveredAreas.length > 0) {
|
|
9591
|
+
const lines = cov.uncoveredAreaSuggestions.slice(0, 4).map(
|
|
9592
|
+
(s) => s.candidateLessonId ? `hivelore sensors scaffold ${s.candidateLessonId} --red-ref <pre-fix-commit> # guard ${s.area}` : `hivelore memory tried --paths ${s.area}/ --what "<incident>" --why-failed "<why>" # then scaffold --red-ref for ${s.area}`
|
|
9593
|
+
);
|
|
9594
|
+
const more = cov.uncoveredAreas.length > 4 ? `
|
|
9595
|
+
# +${cov.uncoveredAreas.length - 4} more area(s)` : "";
|
|
9596
|
+
fix = "Scaffold a behavioural oracle for each uncovered area:\n " + lines.join("\n ") + more;
|
|
9597
|
+
} else if (cov.armedOracles < cov.totalOracles || cov.redProvenOracles < cov.armedOracles) {
|
|
9598
|
+
fix = "Arm warn-only oracles to `block` and re-propose with `--red-ref` so each substantiates its incident.";
|
|
9599
|
+
}
|
|
9600
|
+
return [{
|
|
9601
|
+
severity: "info",
|
|
9602
|
+
code: "behaviour-coverage",
|
|
9603
|
+
section: "Protection",
|
|
9604
|
+
coverage_percent: Math.round(cov.areasWithOracle.length / cov.mainAreas.length * 100),
|
|
9605
|
+
message: `Behaviour harness: ${renderBehaviourCoverageLine2(cov)}.` + uncoveredHint,
|
|
9606
|
+
fix
|
|
9607
|
+
}];
|
|
9608
|
+
}
|
|
9508
9609
|
async function collectSemanticIndexFindings(paths, config, memoryCount, codeMap) {
|
|
9509
9610
|
const findings = [];
|
|
9510
9611
|
const autoWantsCodeSearch = Boolean(config.autopilot || config.autoRepair?.codeSearch);
|
|
@@ -9516,7 +9617,7 @@ async function collectSemanticIndexFindings(paths, config, memoryCount, codeMap)
|
|
|
9516
9617
|
severity: autoWantsCodeSearch ? "warn" : "info",
|
|
9517
9618
|
code: "embeddings-unavailable",
|
|
9518
9619
|
message: "@hivelore/embeddings is not available, so get_briefing falls back to lexical ranking and code_search cannot run.",
|
|
9519
|
-
fix: "npm install -g @hivelore/
|
|
9620
|
+
fix: "npm install -g @hivelore/embeddings@latest\nhivelore index status",
|
|
9520
9621
|
section: "Index health"
|
|
9521
9622
|
});
|
|
9522
9623
|
return findings;
|
|
@@ -9528,7 +9629,7 @@ async function collectSemanticIndexFindings(paths, config, memoryCount, codeMap)
|
|
|
9528
9629
|
severity: "warn",
|
|
9529
9630
|
code: "semantic-memory-index-missing",
|
|
9530
9631
|
message: "Memory embeddings index is missing or empty; get_briefing will report literal_fallback instead of semantic ranking.",
|
|
9531
|
-
fix: "hivelore
|
|
9632
|
+
fix: "hivelore index memories",
|
|
9532
9633
|
section: "Index health"
|
|
9533
9634
|
});
|
|
9534
9635
|
}
|
|
@@ -10030,7 +10131,7 @@ import {
|
|
|
10030
10131
|
detectSensorWeakening,
|
|
10031
10132
|
isSensorScannablePath,
|
|
10032
10133
|
findProjectRoot as findProjectRoot37,
|
|
10033
|
-
loadCodeMap as
|
|
10134
|
+
loadCodeMap as loadCodeMap8,
|
|
10034
10135
|
renderBootstrapChecklist,
|
|
10035
10136
|
findUncapturedFailures,
|
|
10036
10137
|
handoffAgeMs,
|
|
@@ -10963,7 +11064,7 @@ async function checkBootstrapComplete(paths, config, productionCodeChanged) {
|
|
|
10963
11064
|
} catch {
|
|
10964
11065
|
}
|
|
10965
11066
|
const memories = existsSync42(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
|
|
10966
|
-
const codeMap = await
|
|
11067
|
+
const codeMap = await loadCodeMap8(paths);
|
|
10967
11068
|
const codeFiles = codeMap ? Object.keys(codeMap.files).filter((file) => existsSync42(path40.join(paths.root, file))) : [];
|
|
10968
11069
|
let existingModules = [];
|
|
10969
11070
|
try {
|
|
@@ -11028,7 +11129,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
11028
11129
|
findings: [{ severity: "info", code: "enforcement-off", message: "Hivelore enforcement is disabled." }]
|
|
11029
11130
|
});
|
|
11030
11131
|
}
|
|
11031
|
-
findings.push(...await inspectIntegrationVersions(root, "0.
|
|
11132
|
+
findings.push(...await inspectIntegrationVersions(root, "0.46.0"));
|
|
11032
11133
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
11033
11134
|
const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
|
|
11034
11135
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent Hivelore briefing marker exists." } : {
|
|
@@ -11271,15 +11372,26 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
|
11271
11372
|
}
|
|
11272
11373
|
async function runPrecommitPolicy(paths, gate, stage, config) {
|
|
11273
11374
|
const snapshot = await getPolicyDiffSnapshot(paths.root, stage);
|
|
11274
|
-
const
|
|
11375
|
+
const weakeningApprovals = await resolveSensorWeakeningApprovals(paths.root, stage);
|
|
11376
|
+
const detectedWeakenings = detectSensorWeakening(snapshot.diff);
|
|
11377
|
+
const weakenings = detectedWeakenings.filter((weakening) => !weakeningApprovals.has(weakening.memory_id));
|
|
11378
|
+
const approvedWeakenings = detectedWeakenings.filter((weakening) => weakeningApprovals.has(weakening.memory_id));
|
|
11275
11379
|
const weakeningFindings = weakenings.length > 0 ? [{
|
|
11276
11380
|
severity: config.enforcement?.sensorWeakeningGate === "block" ? "error" : "warn",
|
|
11277
11381
|
code: "sensor-weakened",
|
|
11278
11382
|
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" : "") + ".",
|
|
11279
|
-
fix: "If
|
|
11383
|
+
fix: "If intentional, add `Hivelore-Sensor-Change: <memory-id>` to the commit message and set `HIVELORE_SENSOR_WEAKENING_APPROVALS=<memory-id>` for the local commit hook; otherwise restore the sensor.",
|
|
11280
11384
|
memory_ids: [...new Set(weakenings.map((w) => w.memory_id))].slice(0, 10),
|
|
11281
11385
|
impact: config.enforcement?.sensorWeakeningGate === "block" ? 30 : 8
|
|
11282
11386
|
}] : [];
|
|
11387
|
+
if (approvedWeakenings.length > 0) {
|
|
11388
|
+
weakeningFindings.push({
|
|
11389
|
+
severity: "ok",
|
|
11390
|
+
code: "sensor-weakening-approved",
|
|
11391
|
+
message: `Reviewed sensor change(s) approved for ${[...new Set(approvedWeakenings.map((w) => w.memory_id))].join(", ")}.`,
|
|
11392
|
+
memory_ids: [...new Set(approvedWeakenings.map((w) => w.memory_id))]
|
|
11393
|
+
});
|
|
11394
|
+
}
|
|
11283
11395
|
if (gate === "off") {
|
|
11284
11396
|
return [
|
|
11285
11397
|
{ severity: "info", code: "precommit-policy-off", message: "Anti-pattern gate is disabled (enforcement.antiPatternGate=off)." },
|
|
@@ -11337,6 +11449,7 @@ async function runPrecommitPolicy(paths, gate, stage, config) {
|
|
|
11337
11449
|
} catch {
|
|
11338
11450
|
}
|
|
11339
11451
|
}
|
|
11452
|
+
const noticeFinding = result.notice ? [{ severity: "info", code: "precommit-policy-notice", message: result.notice }] : [];
|
|
11340
11453
|
if (!result.should_block) {
|
|
11341
11454
|
return [
|
|
11342
11455
|
{
|
|
@@ -11344,6 +11457,7 @@ async function runPrecommitPolicy(paths, gate, stage, config) {
|
|
|
11344
11457
|
code: "precommit-policy-pass",
|
|
11345
11458
|
message: `${stage === "ci" ? "CI" : "Pre-commit"} policy passed for ${touchedPaths.length} changed file(s).`
|
|
11346
11459
|
},
|
|
11460
|
+
...noticeFinding,
|
|
11347
11461
|
...reviewFinding,
|
|
11348
11462
|
...sensorFindings,
|
|
11349
11463
|
...weakeningFindings
|
|
@@ -11360,11 +11474,34 @@ async function runPrecommitPolicy(paths, gate, stage, config) {
|
|
|
11360
11474
|
memory_ids: blockingWarnings.slice(0, 10).map((w) => w.id),
|
|
11361
11475
|
impact: 45
|
|
11362
11476
|
},
|
|
11477
|
+
...noticeFinding,
|
|
11363
11478
|
...reviewFinding,
|
|
11364
11479
|
...sensorFindings,
|
|
11365
11480
|
...weakeningFindings
|
|
11366
11481
|
];
|
|
11367
11482
|
}
|
|
11483
|
+
function parseSensorWeakeningApprovals(text) {
|
|
11484
|
+
const approved = /* @__PURE__ */ new Set();
|
|
11485
|
+
if (!text) return approved;
|
|
11486
|
+
const trailer = /^Hivelore-Sensor-Change:\s*(.+)$/gmi;
|
|
11487
|
+
for (const match of text.matchAll(trailer)) {
|
|
11488
|
+
for (const id of (match[1] ?? "").split(/[\s,]+/)) if (id) approved.add(id);
|
|
11489
|
+
}
|
|
11490
|
+
if (!text.includes("Hivelore-Sensor-Change:")) {
|
|
11491
|
+
for (const id of text.split(/[\s,]+/)) if (id) approved.add(id);
|
|
11492
|
+
}
|
|
11493
|
+
return approved;
|
|
11494
|
+
}
|
|
11495
|
+
async function resolveSensorWeakeningApprovals(root, stage) {
|
|
11496
|
+
const approved = parseSensorWeakeningApprovals(process.env["HIVELORE_SENSOR_WEAKENING_APPROVALS"]);
|
|
11497
|
+
if (stage !== "ci") return approved;
|
|
11498
|
+
try {
|
|
11499
|
+
const { stdout } = await execFileAsync2("git", ["log", "-1", "--pretty=%B"], { cwd: root });
|
|
11500
|
+
for (const id of parseSensorWeakeningApprovals(stdout)) approved.add(id);
|
|
11501
|
+
} catch {
|
|
11502
|
+
}
|
|
11503
|
+
return approved;
|
|
11504
|
+
}
|
|
11368
11505
|
async function stagedFileContent(root, rel) {
|
|
11369
11506
|
try {
|
|
11370
11507
|
return await runCommand2("git", ["show", `:${rel}`], root);
|
|
@@ -12632,6 +12769,7 @@ import {
|
|
|
12632
12769
|
addedLineNumbersFromDiff as addedLineNumbersFromDiff2,
|
|
12633
12770
|
buildProposeCommand,
|
|
12634
12771
|
scaffoldPostIncidentTest,
|
|
12772
|
+
incidentHintsFromDiff,
|
|
12635
12773
|
selectCommandSensors as selectCommandSensors2,
|
|
12636
12774
|
TEST_FRAMEWORKS,
|
|
12637
12775
|
sensorPatternBrittleness as sensorPatternBrittleness2,
|
|
@@ -12642,6 +12780,14 @@ import {
|
|
|
12642
12780
|
withoutQuarantineNote
|
|
12643
12781
|
} from "@hivelore/core";
|
|
12644
12782
|
var exec5 = promisify7(execFile7);
|
|
12783
|
+
function rejectProposal(opts, reason, guidance) {
|
|
12784
|
+
if (opts.json) console.log(JSON.stringify({ accepted: false, reason, guidance }, null, 2));
|
|
12785
|
+
else {
|
|
12786
|
+
ui.error(`Rejected (${reason}).`);
|
|
12787
|
+
ui.warn(guidance);
|
|
12788
|
+
}
|
|
12789
|
+
process.exitCode = 1;
|
|
12790
|
+
}
|
|
12645
12791
|
function registerSensors(program2) {
|
|
12646
12792
|
const sensors = program2.command("sensors").description("Operate executable sensors derived from Hivelore memories");
|
|
12647
12793
|
sensors.command("list").description("List memories carrying executable sensors").option("--json", "emit JSON", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
@@ -12682,7 +12828,10 @@ function registerSensors(program2) {
|
|
|
12682
12828
|
const targets = scannableSensorTargets(diff);
|
|
12683
12829
|
const hits = runSensors2(memories, targets);
|
|
12684
12830
|
const astMemories = (await runnableSensorMemories(paths, false)).filter(
|
|
12685
|
-
(m) =>
|
|
12831
|
+
(m) => {
|
|
12832
|
+
const sensor = m.frontmatter.sensor;
|
|
12833
|
+
return sensor?.kind === "ast" && Boolean(sensor.pattern || sensor.rule);
|
|
12834
|
+
}
|
|
12686
12835
|
);
|
|
12687
12836
|
const astHits = [];
|
|
12688
12837
|
let astUnrunnable = 0;
|
|
@@ -12930,16 +13079,14 @@ function registerSensors(program2) {
|
|
|
12930
13079
|
});
|
|
12931
13080
|
sensors.command("propose").description(
|
|
12932
13081
|
"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 })'"
|
|
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) => {
|
|
13082
|
+
).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("--json", "emit a machine-readable proposal verdict", false).option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
12934
13083
|
if (opts.kind === "shell" || opts.kind === "test" || opts.kind === "ast") {
|
|
12935
13084
|
if ((opts.kind === "shell" || opts.kind === "test") && !opts.command?.trim()) {
|
|
12936
|
-
|
|
12937
|
-
process.exitCode = 1;
|
|
13085
|
+
rejectProposal(opts, "missing-command", "--kind shell|test requires --command.");
|
|
12938
13086
|
return;
|
|
12939
13087
|
}
|
|
12940
13088
|
if (opts.kind === "ast" && !opts.pattern?.trim() && !opts.rule?.trim()) {
|
|
12941
|
-
|
|
12942
|
-
process.exitCode = 1;
|
|
13089
|
+
rejectProposal(opts, "missing-ast-rule", "--kind ast requires --pattern or --rule <json>.");
|
|
12943
13090
|
return;
|
|
12944
13091
|
}
|
|
12945
13092
|
let astRule;
|
|
@@ -12949,13 +13096,12 @@ function registerSensors(program2) {
|
|
|
12949
13096
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("rule must be a JSON object");
|
|
12950
13097
|
astRule = parsed;
|
|
12951
13098
|
} catch (err) {
|
|
12952
|
-
|
|
12953
|
-
process.exitCode = 1;
|
|
13099
|
+
rejectProposal(opts, "invalid-ast-rule", `--rule must be valid ast-grep Rule JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
12954
13100
|
return;
|
|
12955
13101
|
}
|
|
12956
13102
|
}
|
|
12957
13103
|
const root2 = findProjectRoot39(opts.dir);
|
|
12958
|
-
const { proposeSensor } = await import("./server-
|
|
13104
|
+
const { proposeSensor } = await import("./server-V3ZGBTAG.js");
|
|
12959
13105
|
const out = await proposeSensor(
|
|
12960
13106
|
{
|
|
12961
13107
|
memory_id: id,
|
|
@@ -12976,6 +13122,11 @@ function registerSensors(program2) {
|
|
|
12976
13122
|
},
|
|
12977
13123
|
{ paths: resolveHaivePaths36(root2) }
|
|
12978
13124
|
);
|
|
13125
|
+
if (opts.json) {
|
|
13126
|
+
console.log(JSON.stringify(out, null, 2));
|
|
13127
|
+
if (!out.accepted) process.exitCode = 1;
|
|
13128
|
+
return;
|
|
13129
|
+
}
|
|
12979
13130
|
if (out.accepted) {
|
|
12980
13131
|
ui.success(`${opts.kind === "ast" ? "AST" : "Command"} sensor accepted (${out.severity}) on ${id}`);
|
|
12981
13132
|
ui.info(` ${out.guidance}`);
|
|
@@ -12987,8 +13138,7 @@ function registerSensors(program2) {
|
|
|
12987
13138
|
return;
|
|
12988
13139
|
}
|
|
12989
13140
|
if (!opts.pattern?.trim()) {
|
|
12990
|
-
|
|
12991
|
-
process.exitCode = 1;
|
|
13141
|
+
rejectProposal(opts, "missing-pattern", "kind=regex requires --pattern.");
|
|
12992
13142
|
return;
|
|
12993
13143
|
}
|
|
12994
13144
|
const severity = opts.severity === "warn" ? "warn" : "block";
|
|
@@ -12996,8 +13146,7 @@ function registerSensors(program2) {
|
|
|
12996
13146
|
new RegExp(opts.pattern, opts.flags ?? "");
|
|
12997
13147
|
if (opts.absent) new RegExp(opts.absent, opts.flags ?? "");
|
|
12998
13148
|
} catch (err) {
|
|
12999
|
-
|
|
13000
|
-
process.exitCode = 1;
|
|
13149
|
+
rejectProposal(opts, "invalid-regex", `Invalid regex: ${String(err)}`);
|
|
13001
13150
|
return;
|
|
13002
13151
|
}
|
|
13003
13152
|
const root = findProjectRoot39(opts.dir);
|
|
@@ -13005,8 +13154,7 @@ function registerSensors(program2) {
|
|
|
13005
13154
|
const loaded = existsSync44(paths.memoriesDir) ? await loadMemoriesFromDir16(paths.memoriesDir) : [];
|
|
13006
13155
|
const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
|
|
13007
13156
|
if (!found) {
|
|
13008
|
-
|
|
13009
|
-
process.exitCode = 1;
|
|
13157
|
+
rejectProposal(opts, "memory-not-found", `No memory found with id ${id}`);
|
|
13010
13158
|
return;
|
|
13011
13159
|
}
|
|
13012
13160
|
const anchorPaths = opts.paths ? opts.paths.split(",").map((s) => s.trim()).filter(Boolean) : found.memory.frontmatter.anchor.paths;
|
|
@@ -13028,6 +13176,18 @@ function registerSensors(program2) {
|
|
|
13028
13176
|
last_fired: null
|
|
13029
13177
|
};
|
|
13030
13178
|
const verdict = judgeProposedSensor(sensor, { currentTargets, badExamples });
|
|
13179
|
+
if (opts.json && !verdict.accepted) {
|
|
13180
|
+
console.log(JSON.stringify({
|
|
13181
|
+
accepted: verdict.accepted,
|
|
13182
|
+
reason: verdict.reason ?? null,
|
|
13183
|
+
severity,
|
|
13184
|
+
memory_id: id,
|
|
13185
|
+
self_check: verdict.self_check,
|
|
13186
|
+
brittle: verdict.brittle
|
|
13187
|
+
}, null, 2));
|
|
13188
|
+
if (!verdict.accepted) process.exitCode = 1;
|
|
13189
|
+
return;
|
|
13190
|
+
}
|
|
13031
13191
|
if (!verdict.accepted) {
|
|
13032
13192
|
ui.error(`Rejected (${verdict.reason}).`);
|
|
13033
13193
|
if (verdict.reason === "fires-on-current") {
|
|
@@ -13041,6 +13201,17 @@ function registerSensors(program2) {
|
|
|
13041
13201
|
return;
|
|
13042
13202
|
}
|
|
13043
13203
|
await writeFile27(found.filePath, serializeMemory15({ frontmatter: { ...found.memory.frontmatter, sensor }, body: found.memory.body }), "utf8");
|
|
13204
|
+
if (opts.json) {
|
|
13205
|
+
console.log(JSON.stringify({
|
|
13206
|
+
accepted: true,
|
|
13207
|
+
reason: null,
|
|
13208
|
+
severity,
|
|
13209
|
+
memory_id: id,
|
|
13210
|
+
self_check: verdict.self_check,
|
|
13211
|
+
brittle: verdict.brittle
|
|
13212
|
+
}, null, 2));
|
|
13213
|
+
return;
|
|
13214
|
+
}
|
|
13044
13215
|
ui.success(`Sensor accepted (${severity}) on ${id}`);
|
|
13045
13216
|
ui.info(`pattern=${JSON.stringify(opts.pattern)}${opts.absent ? ` absent=${JSON.stringify(opts.absent)}` : ""}`);
|
|
13046
13217
|
ui.info(
|
|
@@ -13054,7 +13225,10 @@ function registerSensors(program2) {
|
|
|
13054
13225
|
});
|
|
13055
13226
|
sensors.command("scaffold").description(
|
|
13056
13227
|
"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"
|
|
13057
|
-
).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(
|
|
13228
|
+
).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(
|
|
13229
|
+
"--red-ref <ref>",
|
|
13230
|
+
"pre-fix incident commit/ref: the scaffold names the symbols the fix (<ref>..HEAD) touched and pre-fills the example around them, so the assertion is a targeted edit, not a blank page"
|
|
13231
|
+
).option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
13058
13232
|
const root = findProjectRoot39(opts.dir);
|
|
13059
13233
|
const paths = resolveHaivePaths36(root);
|
|
13060
13234
|
const loaded = existsSync44(paths.memoriesDir) ? await loadMemoriesFromDir16(paths.memoriesDir) : [];
|
|
@@ -13074,13 +13248,28 @@ function registerSensors(program2) {
|
|
|
13074
13248
|
const allGroups = await detectTestFrameworksForAnchors(root, fm.anchor.paths ?? []);
|
|
13075
13249
|
const groups = opts.out ? allGroups.slice(0, 1) : allGroups;
|
|
13076
13250
|
const fields = parseLessonFields(found.memory.body);
|
|
13251
|
+
let incidentHints;
|
|
13252
|
+
if (opts.redRef) {
|
|
13253
|
+
try {
|
|
13254
|
+
const args = ["diff", `${opts.redRef}`, "HEAD", "--", ...fm.anchor.paths ?? []];
|
|
13255
|
+
const { stdout } = await exec5("git", args, { cwd: root, maxBuffer: 64 * 1024 * 1024 });
|
|
13256
|
+
incidentHints = incidentHintsFromDiff(stdout, { redRef: opts.redRef });
|
|
13257
|
+
if (incidentHints.changedSymbols.length === 0 && incidentHints.changedFiles.length === 0) {
|
|
13258
|
+
ui.warn(`--red-ref ${opts.redRef}: no changed symbols found in the anchor scope \u2014 using the generic template.`);
|
|
13259
|
+
incidentHints = void 0;
|
|
13260
|
+
}
|
|
13261
|
+
} catch (err) {
|
|
13262
|
+
ui.warn(`--red-ref ${opts.redRef}: could not read the fix diff (${err instanceof Error ? err.message : String(err)}) \u2014 using the generic template.`);
|
|
13263
|
+
}
|
|
13264
|
+
}
|
|
13077
13265
|
const lesson = {
|
|
13078
13266
|
memoryId: id,
|
|
13079
13267
|
title: fields.title || id,
|
|
13080
13268
|
whyFailed: fields.whyFailed,
|
|
13081
13269
|
instead: fields.instead,
|
|
13082
13270
|
incident: fm.sensor?.incident,
|
|
13083
|
-
paths: fm.anchor.paths
|
|
13271
|
+
paths: fm.anchor.paths,
|
|
13272
|
+
incidentHints
|
|
13084
13273
|
};
|
|
13085
13274
|
let scaffolds = groups.map(
|
|
13086
13275
|
(g) => scaffoldPostIncidentTest(lesson, { framework: forced ?? g.framework, outPath: opts.out, baseDir: g.baseDir })
|
|
@@ -13183,7 +13372,7 @@ async function runnableSensorMemories(paths, regexOnly = true) {
|
|
|
13183
13372
|
}
|
|
13184
13373
|
async function stagedDiff(root) {
|
|
13185
13374
|
try {
|
|
13186
|
-
const { stdout } = await exec5("git", ["diff", "--cached"], { cwd: root });
|
|
13375
|
+
const { stdout } = await exec5("git", ["diff", "--cached"], { cwd: root, maxBuffer: 256 * 1024 * 1024 });
|
|
13187
13376
|
return stdout;
|
|
13188
13377
|
} catch (err) {
|
|
13189
13378
|
throw new Error(`git diff --cached failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -13961,7 +14150,7 @@ function registerBridges(program2) {
|
|
|
13961
14150
|
|
|
13962
14151
|
// src/index.ts
|
|
13963
14152
|
var program = new Command48();
|
|
13964
|
-
program.name("hivelore").description("Hivelore - the deterministic policy gate for agent-written code (rules live as repo-native team memory)").version("0.
|
|
14153
|
+
program.name("hivelore").description("Hivelore - the deterministic policy gate for agent-written code (rules live as repo-native team memory)").version("0.46.0").option("--advanced", "show maintenance and experimental commands in help").showSuggestionAfterError(true);
|
|
13965
14154
|
registerInit(program);
|
|
13966
14155
|
registerResolveProject(program);
|
|
13967
14156
|
registerEnforce(program);
|
|
@@ -14051,7 +14240,7 @@ program.parseAsync(process.argv).catch((err) => {
|
|
|
14051
14240
|
console.error(`\x1B[31m\u2717\x1B[0m ${field}${issue.message}`);
|
|
14052
14241
|
}
|
|
14053
14242
|
} else {
|
|
14054
|
-
console.error(err instanceof Error ? err.message : err);
|
|
14243
|
+
console.error(err instanceof Error ? process.env.HIVELORE_DEBUG ? err.stack ?? err.message : err.message : err);
|
|
14055
14244
|
}
|
|
14056
14245
|
process.exit(1);
|
|
14057
14246
|
});
|