@hivelore/cli 0.38.0 → 0.39.1
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-UOMGIXZN.js → chunk-YMIQAOFL.js} +137 -65
- package/dist/chunk-YMIQAOFL.js.map +1 -0
- package/dist/index.js +369 -210
- package/dist/index.js.map +1 -1
- package/dist/{server-HG2K3WOQ.js → server-X6S6KTYJ.js} +4 -2
- package/package.json +4 -4
- package/dist/chunk-UOMGIXZN.js.map +0 -1
- /package/dist/{server-HG2K3WOQ.js.map → server-X6S6KTYJ.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
antiPatternsCheck,
|
|
4
4
|
codeMapTool,
|
|
5
5
|
codeSearch,
|
|
6
|
-
|
|
6
|
+
detectTestFrameworksForAnchors,
|
|
7
7
|
getBriefing,
|
|
8
8
|
getRecap,
|
|
9
9
|
memRelevantTo,
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
preCommitCheck,
|
|
12
12
|
readPresumedCorrectTargets,
|
|
13
13
|
runHaiveMcpStdio
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-YMIQAOFL.js";
|
|
15
15
|
import {
|
|
16
16
|
registerMemoryPending
|
|
17
17
|
} from "./chunk-OYJKHD22.js";
|
|
@@ -209,7 +209,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
|
|
|
209
209
|
if (!f) continue;
|
|
210
210
|
counts.set(f, (counts.get(f) ?? 0) + 1);
|
|
211
211
|
}
|
|
212
|
-
let entries = [...counts.entries()].map(([
|
|
212
|
+
let entries = [...counts.entries()].map(([path48, changes]) => ({ path: path48, changes }));
|
|
213
213
|
const lowerPaths = filePaths.map((p) => p.toLowerCase());
|
|
214
214
|
if (lowerPaths.length > 0) {
|
|
215
215
|
entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
|
|
@@ -3756,7 +3756,7 @@ ${SEED_FOOTER(stack)}` });
|
|
|
3756
3756
|
|
|
3757
3757
|
// src/commands/init.ts
|
|
3758
3758
|
var execFileAsync = promisify2(execFile2);
|
|
3759
|
-
var HAIVE_GITHUB_ACTION_REF = `v${"0.
|
|
3759
|
+
var HAIVE_GITHUB_ACTION_REF = `v${"0.39.1"}`;
|
|
3760
3760
|
var PROJECT_CONTEXT_TEMPLATE = `# Project context
|
|
3761
3761
|
|
|
3762
3762
|
> Generated by \`hivelore init\`. Run \`hivelore init --bootstrap\` to auto-fill from your codebase,
|
|
@@ -6023,7 +6023,7 @@ function parseCsv4(raw) {
|
|
|
6023
6023
|
function registerMemoryTried(memory2) {
|
|
6024
6024
|
memory2.command("tried").description(
|
|
6025
6025
|
'Record a FAILED approach \u2014 prevents repeated mistakes in future sessions.\n\n This is the most valuable type of negative knowledge. It surfaces FIRST in\n get_briefing so agents can\'t miss it. Auto-validated (no approval cycle).\n\n Use this immediately when you try something and it fails.\n\n One-shot loop close: add --sensor-pattern to validate and attach the guardrail\n in the same command (equivalent to a follow-up `sensors propose`).\n\n Example:\n hivelore memory tried \\\\\n --what "importing X with ESM dynamic import" \\\\\n --why-failed "tsup bundles it as CJS, dynamic import fails at runtime" \\\\\n --instead "use static import in the entry file" \\\\\n --paths packages/cli/src/index.ts \\\\\n --sensor-pattern "await import\\\\(" --sensor-absent "static import"\n'
|
|
6026
|
-
).requiredOption("--what <text>", "what approach was tried (short, descriptive title)").requiredOption("--why-failed <text>", "why it failed or should NOT be used (include the exact error if possible)").option("--instead <text>", "the correct approach to use instead").option("--scope <scope>", "personal | team | module
|
|
6026
|
+
).requiredOption("--what <text>", "what approach was tried (short, descriptive title)").requiredOption("--why-failed <text>", "why it failed or should NOT be used (include the exact error if possible)").option("--instead <text>", "the correct approach to use instead").option("--scope <scope>", "personal | team | module (default: personal \u2014 team when a --sensor-* option arms the lesson, so the gate travels)").option("--module <name>", "module name (required when scope=module)").option("--tags <csv>", "comma-separated tags").option("--paths <csv>", "anchor paths, comma-separated").option("--files <csv>", "alias for --paths (matches the MCP `files` parameter)").option("--author <author>", "author email or handle").option("--sensor-pattern <regex>", "one-shot: regex matching the FAULTY usage \u2014 validates + attaches a sensor in this call").option("--sensor-command <cmd>", "one-shot BEHAVIOUR sensor: a command (test/script) the gate runs when the diff touches --paths; non-zero exit = lesson fires").option("--sensor-kind <kind>", "with --sensor-command: shell | test (default test)").option("--sensor-timeout <ms>", "with --sensor-command: max runtime in ms (default 120000)").option("--sensor-absent <regex>", "one-shot: regex marking CORRECT usage nearby (excludes it from firing)").option("--sensor-severity <level>", "one-shot sensor severity: warn | block", "block").option("--sensor-message <text>", "one-shot: self-correction message shown when the sensor fires").option("--bad-example <code>", "one-shot: code snippet the sensor must fire on (validation)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
6027
6027
|
const root = findProjectRoot15(opts.dir);
|
|
6028
6028
|
const paths = resolveHaivePaths14(root);
|
|
6029
6029
|
if (!existsSync20(paths.haiveDir)) {
|
|
@@ -6040,7 +6040,8 @@ function registerMemoryTried(memory2) {
|
|
|
6040
6040
|
why_failed: opts.whyFailed,
|
|
6041
6041
|
instead: opts.instead,
|
|
6042
6042
|
// "shared" is a legacy MemoryScope alias not accepted by mem_tried — normalize to team.
|
|
6043
|
-
|
|
6043
|
+
// Undefined stays undefined: mem_tried defaults it (team when a sensor is attached).
|
|
6044
|
+
scope: opts.scope === "shared" ? "team" : opts.scope,
|
|
6044
6045
|
module: opts.module,
|
|
6045
6046
|
tags: parseCsv4(opts.tags),
|
|
6046
6047
|
paths: parseCsv4(opts.paths ?? opts.files),
|
|
@@ -6073,7 +6074,7 @@ function registerMemoryTried(memory2) {
|
|
|
6073
6074
|
return;
|
|
6074
6075
|
}
|
|
6075
6076
|
ui.success(`Recorded: ${path21.relative(root, result.file_path)}`);
|
|
6076
|
-
ui.info(`id=${result.id} type=attempt status=validated (auto-approved)`);
|
|
6077
|
+
ui.info(`id=${result.id} type=attempt scope=${result.scope} status=validated (auto-approved)`);
|
|
6077
6078
|
if (result.sensor_result) {
|
|
6078
6079
|
if (result.sensor_result.accepted) {
|
|
6079
6080
|
ui.success(`Loop closed: sensor attached (${result.sensor_result.severity}) \u2014 the gate now refuses a repeat deterministically.`);
|
|
@@ -8575,9 +8576,9 @@ function parseDays(input) {
|
|
|
8575
8576
|
}
|
|
8576
8577
|
|
|
8577
8578
|
// src/commands/doctor.ts
|
|
8578
|
-
import { existsSync as
|
|
8579
|
+
import { existsSync as existsSync40, statSync as statSync3 } from "fs";
|
|
8579
8580
|
import { readFile as readFile16, stat, writeFile as writeFile23 } from "fs/promises";
|
|
8580
|
-
import
|
|
8581
|
+
import path37 from "path";
|
|
8581
8582
|
import { execFileSync, execSync } from "child_process";
|
|
8582
8583
|
import "commander";
|
|
8583
8584
|
import {
|
|
@@ -8599,6 +8600,85 @@ import {
|
|
|
8599
8600
|
readUsageEvents as readUsageEvents3,
|
|
8600
8601
|
resolveHaivePaths as resolveHaivePaths33
|
|
8601
8602
|
} from "@hivelore/core";
|
|
8603
|
+
|
|
8604
|
+
// src/utils/post-incident-scan.ts
|
|
8605
|
+
import { readdirSync as readdirSync4, readFileSync, statSync as statSync2 } from "fs";
|
|
8606
|
+
import { existsSync as existsSync39 } from "fs";
|
|
8607
|
+
import path36 from "path";
|
|
8608
|
+
import {
|
|
8609
|
+
assessScaffoldLoop,
|
|
8610
|
+
loadMemoriesFromDir as loadMemoriesFromDir14,
|
|
8611
|
+
SCAFFOLD_MARKER_RE
|
|
8612
|
+
} from "@hivelore/core";
|
|
8613
|
+
var PRUNED_DIRS = /* @__PURE__ */ new Set([
|
|
8614
|
+
"node_modules",
|
|
8615
|
+
".git",
|
|
8616
|
+
".ai",
|
|
8617
|
+
"dist",
|
|
8618
|
+
"build",
|
|
8619
|
+
"out",
|
|
8620
|
+
"coverage",
|
|
8621
|
+
".next",
|
|
8622
|
+
".venv",
|
|
8623
|
+
"venv",
|
|
8624
|
+
"__pycache__",
|
|
8625
|
+
"target",
|
|
8626
|
+
"vendor"
|
|
8627
|
+
]);
|
|
8628
|
+
var MAX_SCAFFOLD_BYTES = 64 * 1024;
|
|
8629
|
+
var MAX_DEPTH = 8;
|
|
8630
|
+
function findPostIncidentScaffoldFiles(root) {
|
|
8631
|
+
const results = [];
|
|
8632
|
+
const walk = (dir, depth, inIncidents) => {
|
|
8633
|
+
if (depth > MAX_DEPTH) return;
|
|
8634
|
+
let entries;
|
|
8635
|
+
try {
|
|
8636
|
+
entries = readdirSync4(dir);
|
|
8637
|
+
} catch {
|
|
8638
|
+
return;
|
|
8639
|
+
}
|
|
8640
|
+
for (const entry of entries) {
|
|
8641
|
+
if (PRUNED_DIRS.has(entry)) continue;
|
|
8642
|
+
const abs = path36.join(dir, entry);
|
|
8643
|
+
let stat2;
|
|
8644
|
+
try {
|
|
8645
|
+
stat2 = statSync2(abs);
|
|
8646
|
+
} catch {
|
|
8647
|
+
continue;
|
|
8648
|
+
}
|
|
8649
|
+
if (stat2.isDirectory()) {
|
|
8650
|
+
walk(abs, depth + 1, inIncidents || entry === "incidents");
|
|
8651
|
+
continue;
|
|
8652
|
+
}
|
|
8653
|
+
if (!inIncidents || stat2.size > MAX_SCAFFOLD_BYTES) continue;
|
|
8654
|
+
try {
|
|
8655
|
+
const content = readFileSync(abs, "utf8");
|
|
8656
|
+
if (SCAFFOLD_MARKER_RE.test(content)) {
|
|
8657
|
+
results.push({ path: path36.relative(root, abs).split(path36.sep).join("/"), content });
|
|
8658
|
+
}
|
|
8659
|
+
} catch {
|
|
8660
|
+
}
|
|
8661
|
+
}
|
|
8662
|
+
};
|
|
8663
|
+
walk(root, 0, false);
|
|
8664
|
+
return results;
|
|
8665
|
+
}
|
|
8666
|
+
async function collectScaffoldLoopGaps(paths) {
|
|
8667
|
+
const files = findPostIncidentScaffoldFiles(paths.root);
|
|
8668
|
+
if (files.length === 0) return [];
|
|
8669
|
+
const loaded = existsSync39(paths.memoriesDir) ? await loadMemoriesFromDir14(paths.memoriesDir) : [];
|
|
8670
|
+
const memories = loaded.map(({ memory: memory2 }) => ({
|
|
8671
|
+
id: memory2.frontmatter.id,
|
|
8672
|
+
sensorKind: memory2.frontmatter.sensor?.kind ?? null
|
|
8673
|
+
}));
|
|
8674
|
+
return assessScaffoldLoop(files, memories);
|
|
8675
|
+
}
|
|
8676
|
+
function describeScaffoldGap(gap) {
|
|
8677
|
+
const state = gap.memory_missing ? "lesson deleted" : gap.pending && !gap.armed ? "pending, not armed" : gap.pending ? "armed but still pending" : "not armed";
|
|
8678
|
+
return `${gap.path} (${state} \u2014 ${gap.memory_id})`;
|
|
8679
|
+
}
|
|
8680
|
+
|
|
8681
|
+
// src/commands/doctor.ts
|
|
8602
8682
|
var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
|
|
8603
8683
|
function registerDoctor(program2) {
|
|
8604
8684
|
program2.command("doctor").description(
|
|
@@ -8609,7 +8689,7 @@ function registerDoctor(program2) {
|
|
|
8609
8689
|
const findings = [];
|
|
8610
8690
|
const repairs = [];
|
|
8611
8691
|
const config = await loadConfig10(paths);
|
|
8612
|
-
if (!
|
|
8692
|
+
if (!existsSync40(paths.haiveDir)) {
|
|
8613
8693
|
if (opts.json) {
|
|
8614
8694
|
console.log(JSON.stringify({
|
|
8615
8695
|
initialized: false,
|
|
@@ -8634,7 +8714,7 @@ function registerDoctor(program2) {
|
|
|
8634
8714
|
})
|
|
8635
8715
|
);
|
|
8636
8716
|
}
|
|
8637
|
-
if (!
|
|
8717
|
+
if (!existsSync40(paths.projectContext)) {
|
|
8638
8718
|
findings.push({
|
|
8639
8719
|
severity: "warn",
|
|
8640
8720
|
code: "no-project-context",
|
|
@@ -8654,7 +8734,7 @@ function registerDoctor(program2) {
|
|
|
8654
8734
|
});
|
|
8655
8735
|
} else {
|
|
8656
8736
|
const referenced = extractReferencedPaths(content);
|
|
8657
|
-
const missing = referenced.filter((p) => !
|
|
8737
|
+
const missing = referenced.filter((p) => !existsSync40(path37.resolve(root, p)));
|
|
8658
8738
|
const grounded = referenced.length - missing.length;
|
|
8659
8739
|
if (referenced.length >= 3 && grounded / referenced.length < 0.5) {
|
|
8660
8740
|
findings.push({
|
|
@@ -8676,11 +8756,11 @@ function registerDoctor(program2) {
|
|
|
8676
8756
|
});
|
|
8677
8757
|
}
|
|
8678
8758
|
}
|
|
8679
|
-
const memoriesDetailed =
|
|
8759
|
+
const memoriesDetailed = existsSync40(paths.memoriesDir) ? await loadMemoriesFromDirDetailed(paths.memoriesDir) : { loaded: [], invalid: [] };
|
|
8680
8760
|
const memories = memoriesDetailed.loaded;
|
|
8681
8761
|
const now = Date.now();
|
|
8682
8762
|
if (memoriesDetailed.invalid.length > 0) {
|
|
8683
|
-
const listed = memoriesDetailed.invalid.slice(0, 5).map((f) => `${
|
|
8763
|
+
const listed = memoriesDetailed.invalid.slice(0, 5).map((f) => `${path37.relative(root, f.filePath)} (${f.error})`).join("; ");
|
|
8684
8764
|
findings.push({
|
|
8685
8765
|
severity: "warn",
|
|
8686
8766
|
code: "invalid-memory-files",
|
|
@@ -8821,8 +8901,8 @@ function registerDoctor(program2) {
|
|
|
8821
8901
|
const anchorPaths = s.paths.length > 0 ? s.paths : m.memory.frontmatter.anchor.paths;
|
|
8822
8902
|
const targets = [];
|
|
8823
8903
|
for (const rel of anchorPaths) {
|
|
8824
|
-
const abs =
|
|
8825
|
-
if (!
|
|
8904
|
+
const abs = path37.resolve(root, rel);
|
|
8905
|
+
if (!existsSync40(abs)) continue;
|
|
8826
8906
|
try {
|
|
8827
8907
|
targets.push({ path: rel, content: await readFile16(abs, "utf8") });
|
|
8828
8908
|
} catch {
|
|
@@ -8897,6 +8977,18 @@ function registerDoctor(program2) {
|
|
|
8897
8977
|
}
|
|
8898
8978
|
findings.push(...await collectHarnessCoverageFindings(codeMap, memories));
|
|
8899
8979
|
findings.push(...await collectSemanticIndexFindings(paths, config, memories.length, codeMap));
|
|
8980
|
+
try {
|
|
8981
|
+
const scaffoldGaps = await collectScaffoldLoopGaps(paths);
|
|
8982
|
+
if (scaffoldGaps.length > 0) {
|
|
8983
|
+
findings.push({
|
|
8984
|
+
severity: "warn",
|
|
8985
|
+
code: "post-incident-test-unarmed",
|
|
8986
|
+
message: `${scaffoldGaps.length} post-incident test(s) are scaffolded but not armed as gates \u2014 the incident is documented, nothing deterministic guards it yet: ` + scaffoldGaps.slice(0, 5).map(describeScaffoldGap).join(", ") + (scaffoldGaps.length > 5 ? ", \u2026" : "") + ".",
|
|
8987
|
+
fix: "Fill the pending assertion, run it, then arm it with the `hivelore sensors propose --kind test` command in the scaffold header."
|
|
8988
|
+
});
|
|
8989
|
+
}
|
|
8990
|
+
} catch {
|
|
8991
|
+
}
|
|
8900
8992
|
const events = await readUsageEvents3(paths);
|
|
8901
8993
|
if (events.length === 0) {
|
|
8902
8994
|
findings.push({
|
|
@@ -8933,9 +9025,9 @@ function registerDoctor(program2) {
|
|
|
8933
9025
|
}
|
|
8934
9026
|
}
|
|
8935
9027
|
if (config.enforcement?.requireBriefingFirst) {
|
|
8936
|
-
const claudeSettings =
|
|
9028
|
+
const claudeSettings = path37.join(root, ".claude", "settings.local.json");
|
|
8937
9029
|
let hasClaudeEnforcement = false;
|
|
8938
|
-
if (
|
|
9030
|
+
if (existsSync40(claudeSettings)) {
|
|
8939
9031
|
try {
|
|
8940
9032
|
const { readFile: readFile24 } = await import("fs/promises");
|
|
8941
9033
|
const raw = await readFile24(claudeSettings, "utf8");
|
|
@@ -8961,7 +9053,7 @@ function registerDoctor(program2) {
|
|
|
8961
9053
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `hivelore init` without --manual)."
|
|
8962
9054
|
});
|
|
8963
9055
|
}
|
|
8964
|
-
findings.push(...await collectInstallFindings(root, "0.
|
|
9056
|
+
findings.push(...await collectInstallFindings(root, "0.39.1"));
|
|
8965
9057
|
findings.push(...await collectToolchainFindings(root));
|
|
8966
9058
|
try {
|
|
8967
9059
|
const legacyRaw = execSync("haive-mcp --version", {
|
|
@@ -8969,7 +9061,7 @@ function registerDoctor(program2) {
|
|
|
8969
9061
|
timeout: 3e3,
|
|
8970
9062
|
stdio: ["ignore", "pipe", "ignore"]
|
|
8971
9063
|
}).trim();
|
|
8972
|
-
const cliVersion = "0.
|
|
9064
|
+
const cliVersion = "0.39.1";
|
|
8973
9065
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
8974
9066
|
findings.push({
|
|
8975
9067
|
severity: "warn",
|
|
@@ -8985,17 +9077,17 @@ npm uninstall -g @hivelore/mcp`
|
|
|
8985
9077
|
}
|
|
8986
9078
|
{
|
|
8987
9079
|
const configPaths = [
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
|
|
9080
|
+
path37.join(root, ".mcp.json"),
|
|
9081
|
+
path37.join(root, ".cursor", "mcp.json"),
|
|
9082
|
+
path37.join(root, ".vscode", "mcp.json")
|
|
8991
9083
|
];
|
|
8992
9084
|
const staleConfigs = [];
|
|
8993
9085
|
for (const cfgPath of configPaths) {
|
|
8994
|
-
if (!
|
|
9086
|
+
if (!existsSync40(cfgPath)) continue;
|
|
8995
9087
|
try {
|
|
8996
9088
|
const raw = await readFile16(cfgPath, "utf8");
|
|
8997
9089
|
if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
|
|
8998
|
-
staleConfigs.push(
|
|
9090
|
+
staleConfigs.push(path37.relative(root, cfgPath));
|
|
8999
9091
|
if (opts.fix && !opts.dryRun) {
|
|
9000
9092
|
const updated = raw.replace(/"command"\s*:\s*"haive-mcp"/g, '"command": "hivelore"').replace(/"args"\s*:\s*\[\]/g, '"args": ["mcp", "--stdio"]');
|
|
9001
9093
|
await writeFile23(cfgPath, updated, "utf8");
|
|
@@ -9087,8 +9179,11 @@ function emit(findings, opts, repairs = []) {
|
|
|
9087
9179
|
}
|
|
9088
9180
|
const actions = nextActions(classified);
|
|
9089
9181
|
if (actions.length > 0) {
|
|
9090
|
-
console.log(ui.bold("
|
|
9091
|
-
for (const action of actions.slice(0, 5))
|
|
9182
|
+
console.log(ui.bold("Suggested commands"));
|
|
9183
|
+
for (const action of actions.slice(0, 5)) {
|
|
9184
|
+
const isCommand = /^(hivelore|haive|git|npm|pnpm|npx|node|gh|rm|code|cd)\b/.test(action);
|
|
9185
|
+
console.log(` ${ui.dim(isCommand ? "$" : "\u2192")} ${action}`);
|
|
9186
|
+
}
|
|
9092
9187
|
} else if (!opts.fix && classified.some((f) => f.fix)) {
|
|
9093
9188
|
ui.info("Re-run with --fix to see suggested commands.");
|
|
9094
9189
|
}
|
|
@@ -9288,8 +9383,8 @@ which -a hivelore haive`
|
|
|
9288
9383
|
const missingBins = /* @__PURE__ */ new Map();
|
|
9289
9384
|
const staleBins = /* @__PURE__ */ new Map();
|
|
9290
9385
|
for (const rel of integrationFiles) {
|
|
9291
|
-
const file =
|
|
9292
|
-
if (!
|
|
9386
|
+
const file = path37.join(root, rel);
|
|
9387
|
+
if (!existsSync40(file)) continue;
|
|
9293
9388
|
const text = await readFile16(file, "utf8").catch(() => "");
|
|
9294
9389
|
for (const bin of extractAbsoluteHaiveBins(text)) {
|
|
9295
9390
|
const version = versionForBinary(bin);
|
|
@@ -9322,7 +9417,7 @@ which -a hivelore haive`
|
|
|
9322
9417
|
async function collectToolchainFindings(root) {
|
|
9323
9418
|
const findings = [];
|
|
9324
9419
|
const pkg = await readJson(
|
|
9325
|
-
|
|
9420
|
+
path37.join(root, "package.json")
|
|
9326
9421
|
);
|
|
9327
9422
|
const wantsPnpm = pkg?.packageManager?.startsWith("pnpm@") || Object.values(pkg?.scripts ?? {}).some((script) => /\bpnpm\b/.test(script));
|
|
9328
9423
|
if (!wantsPnpm) return findings;
|
|
@@ -9341,10 +9436,10 @@ async function collectToolchainFindings(root) {
|
|
|
9341
9436
|
}
|
|
9342
9437
|
async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
9343
9438
|
const findings = [];
|
|
9344
|
-
const isHaiveWorkspace = ["hivelore-monorepo", "haive-monorepo"].includes((await readJson(
|
|
9439
|
+
const isHaiveWorkspace = ["hivelore-monorepo", "haive-monorepo"].includes((await readJson(path37.join(root, "package.json")))?.name ?? "");
|
|
9345
9440
|
if (!isHaiveWorkspace) return findings;
|
|
9346
|
-
const cliDist =
|
|
9347
|
-
if (!
|
|
9441
|
+
const cliDist = path37.join(root, "packages/cli/dist/index.js");
|
|
9442
|
+
if (!existsSync40(cliDist)) {
|
|
9348
9443
|
findings.push({
|
|
9349
9444
|
severity: "warn",
|
|
9350
9445
|
code: "workspace-dist-missing",
|
|
@@ -9368,10 +9463,10 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
|
9368
9463
|
"packages/core/src/index.ts",
|
|
9369
9464
|
"packages/mcp/src/server.ts",
|
|
9370
9465
|
"packages/cli/src/index.ts"
|
|
9371
|
-
].map((rel) =>
|
|
9466
|
+
].map((rel) => path37.join(root, rel)).filter(existsSync40);
|
|
9372
9467
|
if (sourceFiles.length > 0) {
|
|
9373
|
-
const distMtime =
|
|
9374
|
-
const newestSource = Math.max(...sourceFiles.map((file) =>
|
|
9468
|
+
const distMtime = statSync3(cliDist).mtimeMs;
|
|
9469
|
+
const newestSource = Math.max(...sourceFiles.map((file) => statSync3(file).mtimeMs));
|
|
9375
9470
|
if (newestSource > distMtime + 1e3) {
|
|
9376
9471
|
findings.push({
|
|
9377
9472
|
severity: "info",
|
|
@@ -9386,7 +9481,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
|
9386
9481
|
}
|
|
9387
9482
|
async function collectWorkspaceVersionFindings(root, expectedVersion) {
|
|
9388
9483
|
const findings = [];
|
|
9389
|
-
const rootPkg = await readJson(
|
|
9484
|
+
const rootPkg = await readJson(path37.join(root, "package.json"));
|
|
9390
9485
|
const workspacePackages = [
|
|
9391
9486
|
"packages/core/package.json",
|
|
9392
9487
|
"packages/embeddings/package.json",
|
|
@@ -9395,7 +9490,7 @@ async function collectWorkspaceVersionFindings(root, expectedVersion) {
|
|
|
9395
9490
|
];
|
|
9396
9491
|
const existing = (await Promise.all(workspacePackages.map(async (rel) => ({
|
|
9397
9492
|
rel,
|
|
9398
|
-
pkg: await readJson(
|
|
9493
|
+
pkg: await readJson(path37.join(root, rel))
|
|
9399
9494
|
})))).filter((item) => item.pkg);
|
|
9400
9495
|
const isHaiveWorkspace = rootPkg?.name === "hivelore-monorepo" || rootPkg?.name === "haive-monorepo" || existing.some((item) => item.pkg?.name?.startsWith("@hivelore/"));
|
|
9401
9496
|
if (!isHaiveWorkspace) return findings;
|
|
@@ -9457,7 +9552,7 @@ function collectGlobalHivemoduleFindings(expectedVersion) {
|
|
|
9457
9552
|
}
|
|
9458
9553
|
}
|
|
9459
9554
|
async function readJson(file) {
|
|
9460
|
-
if (!
|
|
9555
|
+
if (!existsSync40(file)) return null;
|
|
9461
9556
|
try {
|
|
9462
9557
|
return JSON.parse(await readFile16(file, "utf8"));
|
|
9463
9558
|
} catch {
|
|
@@ -9522,7 +9617,7 @@ function extractAbsoluteHaiveBins(text) {
|
|
|
9522
9617
|
const p = match[2];
|
|
9523
9618
|
if (!p) continue;
|
|
9524
9619
|
try {
|
|
9525
|
-
if (
|
|
9620
|
+
if (statSync3(p).isDirectory()) continue;
|
|
9526
9621
|
} catch {
|
|
9527
9622
|
}
|
|
9528
9623
|
out.add(p);
|
|
@@ -9686,23 +9781,23 @@ function runCommand(cmd, args, cwd) {
|
|
|
9686
9781
|
}
|
|
9687
9782
|
|
|
9688
9783
|
// src/commands/resolve-project.ts
|
|
9689
|
-
import
|
|
9784
|
+
import path38 from "path";
|
|
9690
9785
|
import "commander";
|
|
9691
9786
|
import { resolveProjectInfo } from "@hivelore/core";
|
|
9692
9787
|
function registerResolveProject(program2) {
|
|
9693
9788
|
program2.command("resolve-project").description(
|
|
9694
9789
|
"Print JSON for Hivelore project root resolution (HAIVE_PROJECT_ROOT, markers, .ai layout)."
|
|
9695
9790
|
).option("-d, --dir <dir>", "working directory", process.cwd()).action((opts) => {
|
|
9696
|
-
const info = resolveProjectInfo({ cwd:
|
|
9791
|
+
const info = resolveProjectInfo({ cwd: path38.resolve(opts.dir) });
|
|
9697
9792
|
console.log(JSON.stringify({ ok: true, info }, null, 2));
|
|
9698
9793
|
});
|
|
9699
9794
|
}
|
|
9700
9795
|
|
|
9701
9796
|
// src/commands/enforce.ts
|
|
9702
9797
|
import { execFile as execFile5, execFileSync as execFileSync2, spawn as spawn4 } from "child_process";
|
|
9703
|
-
import { existsSync as
|
|
9798
|
+
import { existsSync as existsSync42, statSync as statSync4 } from "fs";
|
|
9704
9799
|
import { chmod, mkdir as mkdir16, readFile as readFile18, readdir as readdir4, rm as rm2, writeFile as writeFile25 } from "fs/promises";
|
|
9705
|
-
import
|
|
9800
|
+
import path40 from "path";
|
|
9706
9801
|
import { promisify as promisify5 } from "util";
|
|
9707
9802
|
import "commander";
|
|
9708
9803
|
import {
|
|
@@ -9711,6 +9806,7 @@ import {
|
|
|
9711
9806
|
assessSensorHealth as assessSensorHealth3,
|
|
9712
9807
|
sensorPromotedAtMap as sensorPromotedAtMap3,
|
|
9713
9808
|
assessBootstrapState,
|
|
9809
|
+
detectSensorWeakening,
|
|
9714
9810
|
isSensorScannablePath,
|
|
9715
9811
|
findProjectRoot as findProjectRoot37,
|
|
9716
9812
|
loadCodeMap as loadCodeMap7,
|
|
@@ -9722,7 +9818,7 @@ import {
|
|
|
9722
9818
|
isRetiredMemory as isRetiredMemory2,
|
|
9723
9819
|
loadConfig as loadConfig12,
|
|
9724
9820
|
detectAgentContext,
|
|
9725
|
-
loadMemoriesFromDir as
|
|
9821
|
+
loadMemoriesFromDir as loadMemoriesFromDir15,
|
|
9726
9822
|
loadSensorLedger as loadSensorLedger3,
|
|
9727
9823
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
|
|
9728
9824
|
readRecentBriefingMarker,
|
|
@@ -9741,9 +9837,9 @@ import {
|
|
|
9741
9837
|
} from "@hivelore/core";
|
|
9742
9838
|
|
|
9743
9839
|
// src/utils/claude-hooks.ts
|
|
9744
|
-
import { existsSync as
|
|
9840
|
+
import { existsSync as existsSync41 } from "fs";
|
|
9745
9841
|
import { mkdir as mkdir15, readFile as readFile17, writeFile as writeFile24 } from "fs/promises";
|
|
9746
|
-
import
|
|
9842
|
+
import path39 from "path";
|
|
9747
9843
|
var HAIVE_HOOK_TAG = "haive-enforcement";
|
|
9748
9844
|
var POST_TOOL_USE_GROUP = {
|
|
9749
9845
|
matcher: "Edit|Write|Bash",
|
|
@@ -9829,7 +9925,7 @@ function unpatchClaudeSettings(input) {
|
|
|
9829
9925
|
async function installClaudeHooksAtPath(settingsPath) {
|
|
9830
9926
|
let raw = null;
|
|
9831
9927
|
let created = false;
|
|
9832
|
-
if (
|
|
9928
|
+
if (existsSync41(settingsPath)) {
|
|
9833
9929
|
try {
|
|
9834
9930
|
raw = JSON.parse(await readFile17(settingsPath, "utf8"));
|
|
9835
9931
|
} catch {
|
|
@@ -9839,12 +9935,12 @@ async function installClaudeHooksAtPath(settingsPath) {
|
|
|
9839
9935
|
created = true;
|
|
9840
9936
|
}
|
|
9841
9937
|
const patched = patchClaudeSettings(raw);
|
|
9842
|
-
await mkdir15(
|
|
9938
|
+
await mkdir15(path39.dirname(settingsPath), { recursive: true });
|
|
9843
9939
|
await writeFile24(settingsPath, JSON.stringify(patched, null, 2) + "\n", "utf8");
|
|
9844
9940
|
return { settingsPath, created };
|
|
9845
9941
|
}
|
|
9846
9942
|
async function uninstallClaudeHooksAtPath(settingsPath) {
|
|
9847
|
-
if (!
|
|
9943
|
+
if (!existsSync41(settingsPath)) {
|
|
9848
9944
|
return { settingsPath, created: false };
|
|
9849
9945
|
}
|
|
9850
9946
|
const raw = JSON.parse(await readFile17(settingsPath, "utf8"));
|
|
@@ -9855,9 +9951,9 @@ async function uninstallClaudeHooksAtPath(settingsPath) {
|
|
|
9855
9951
|
function defaultClaudeSettingsPath(scope, projectRoot) {
|
|
9856
9952
|
if (scope === "user") {
|
|
9857
9953
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
9858
|
-
return
|
|
9954
|
+
return path39.join(home, ".claude", "settings.json");
|
|
9859
9955
|
}
|
|
9860
|
-
return
|
|
9956
|
+
return path39.join(projectRoot, ".claude", "settings.local.json");
|
|
9861
9957
|
}
|
|
9862
9958
|
|
|
9863
9959
|
// src/utils/command-sensors.ts
|
|
@@ -10044,7 +10140,7 @@ function registerEnforce(program2) {
|
|
|
10044
10140
|
ui.success(`Removed Hivelore hooks from ${result.settingsPath}`);
|
|
10045
10141
|
} else {
|
|
10046
10142
|
const result = await installClaudeHooksAtPath(settingsPath);
|
|
10047
|
-
ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${
|
|
10143
|
+
ui.success(`${result.created ? "Created" : "Patched"} Claude Code hooks (${path40.relative(root, result.settingsPath) || result.settingsPath})`);
|
|
10048
10144
|
}
|
|
10049
10145
|
} catch (err) {
|
|
10050
10146
|
ui.warn(`Claude Code hooks not ${opts.removeClaude ? "removed" : "installed"}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -10066,19 +10162,19 @@ function registerEnforce(program2) {
|
|
|
10066
10162
|
enforce.command("cleanup").description("Remove generated Hivelore runtime/cache artifacts that should not appear in commits.").option("-d, --dir <dir>", "project root").option("--dry-run", "print what would be removed without deleting", false).action(async (opts) => {
|
|
10067
10163
|
const root = findProjectRoot37(opts.dir);
|
|
10068
10164
|
const paths = resolveHaivePaths35(root);
|
|
10069
|
-
const cacheDir =
|
|
10070
|
-
if (
|
|
10071
|
-
if (opts.dryRun) ui.info(`would clean ${
|
|
10165
|
+
const cacheDir = path40.join(paths.haiveDir, ".cache");
|
|
10166
|
+
if (existsSync42(cacheDir)) {
|
|
10167
|
+
if (opts.dryRun) ui.info(`would clean ${path40.relative(root, cacheDir)} (preserving .gitignore)`);
|
|
10072
10168
|
else {
|
|
10073
10169
|
const removed = await cleanupCacheDir(cacheDir);
|
|
10074
|
-
ui.success(`cleaned ${
|
|
10170
|
+
ui.success(`cleaned ${path40.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
|
|
10075
10171
|
}
|
|
10076
10172
|
}
|
|
10077
|
-
if (
|
|
10078
|
-
if (opts.dryRun) ui.info(`would clean ${
|
|
10173
|
+
if (existsSync42(paths.runtimeDir)) {
|
|
10174
|
+
if (opts.dryRun) ui.info(`would clean ${path40.relative(root, paths.runtimeDir)} (preserving briefing markers)`);
|
|
10079
10175
|
else {
|
|
10080
10176
|
const removed = await cleanupRuntimeDir(paths.runtimeDir);
|
|
10081
|
-
ui.success(`cleaned ${
|
|
10177
|
+
ui.success(`cleaned ${path40.relative(root, paths.runtimeDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
|
|
10082
10178
|
}
|
|
10083
10179
|
}
|
|
10084
10180
|
});
|
|
@@ -10122,7 +10218,7 @@ function registerEnforce(program2) {
|
|
|
10122
10218
|
const root = resolveRoot(opts.dir, payload);
|
|
10123
10219
|
if (!root) return;
|
|
10124
10220
|
const paths = resolveHaivePaths35(root);
|
|
10125
|
-
if (!
|
|
10221
|
+
if (!existsSync42(paths.haiveDir)) return;
|
|
10126
10222
|
await mkdir16(paths.runtimeDir, { recursive: true });
|
|
10127
10223
|
const sessionId = opts.sessionId ?? payload.session_id;
|
|
10128
10224
|
const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this Hivelore-initialized project.";
|
|
@@ -10185,7 +10281,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
10185
10281
|
const root = resolveRoot(opts.dir, payload);
|
|
10186
10282
|
if (!root) return;
|
|
10187
10283
|
const paths = resolveHaivePaths35(root);
|
|
10188
|
-
if (!
|
|
10284
|
+
if (!existsSync42(paths.haiveDir)) return;
|
|
10189
10285
|
if (!isWriteLikeTool(payload)) return;
|
|
10190
10286
|
const config = await loadConfig12(paths);
|
|
10191
10287
|
if (config.enforcement?.requireBriefingFirst === false) return;
|
|
@@ -10247,10 +10343,26 @@ function emitPreToolUseContext(text) {
|
|
|
10247
10343
|
})
|
|
10248
10344
|
);
|
|
10249
10345
|
}
|
|
10346
|
+
async function checkPostIncidentScaffolds(paths) {
|
|
10347
|
+
try {
|
|
10348
|
+
const gaps = await collectScaffoldLoopGaps(paths);
|
|
10349
|
+
if (gaps.length === 0) return [];
|
|
10350
|
+
return [{
|
|
10351
|
+
severity: "warn",
|
|
10352
|
+
code: "post-incident-test-unarmed",
|
|
10353
|
+
message: `${gaps.length} post-incident test(s) are scaffolded but not yet armed as gates: ` + gaps.slice(0, 5).map(describeScaffoldGap).join(", ") + (gaps.length > 5 ? ", \u2026" : "") + ".",
|
|
10354
|
+
fix: "Fill the pending assertion, run the test, then arm it: the scaffold header contains the exact `hivelore sensors propose --kind test` command.",
|
|
10355
|
+
memory_ids: [...new Set(gaps.map((g) => g.memory_id))].slice(0, 10),
|
|
10356
|
+
impact: 0
|
|
10357
|
+
}];
|
|
10358
|
+
} catch {
|
|
10359
|
+
return [];
|
|
10360
|
+
}
|
|
10361
|
+
}
|
|
10250
10362
|
async function buildFinishReport(dir) {
|
|
10251
10363
|
const root = findProjectRoot37(dir);
|
|
10252
10364
|
const paths = resolveHaivePaths35(root);
|
|
10253
|
-
const initialized =
|
|
10365
|
+
const initialized = existsSync42(paths.haiveDir);
|
|
10254
10366
|
const config = initialized ? await loadConfig12(paths) : {};
|
|
10255
10367
|
const mode = config.enforcement?.mode ?? "strict";
|
|
10256
10368
|
const findings = [];
|
|
@@ -10271,6 +10383,7 @@ async function buildFinishReport(dir) {
|
|
|
10271
10383
|
});
|
|
10272
10384
|
}
|
|
10273
10385
|
findings.push(...await checkFailureCapture(paths, config));
|
|
10386
|
+
findings.push(...await checkPostIncidentScaffolds(paths));
|
|
10274
10387
|
findings.push(...await checkBootstrapComplete(paths, config, true));
|
|
10275
10388
|
const status = await getGitSyncStatus(root);
|
|
10276
10389
|
if (!status.available) {
|
|
@@ -10442,8 +10555,8 @@ async function buildFinishReport(dir) {
|
|
|
10442
10555
|
async function checkFailureCapture(paths, config) {
|
|
10443
10556
|
const gate = config.enforcement?.failureCaptureGate ?? "warn";
|
|
10444
10557
|
if (gate === "off") return [];
|
|
10445
|
-
const obsFile =
|
|
10446
|
-
if (!
|
|
10558
|
+
const obsFile = path40.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
10559
|
+
if (!existsSync42(obsFile)) return [];
|
|
10447
10560
|
const failures = [];
|
|
10448
10561
|
try {
|
|
10449
10562
|
const raw = await readFile18(obsFile, "utf8");
|
|
@@ -10460,7 +10573,7 @@ async function checkFailureCapture(paths, config) {
|
|
|
10460
10573
|
return [];
|
|
10461
10574
|
}
|
|
10462
10575
|
if (failures.length === 0) return [];
|
|
10463
|
-
const memories =
|
|
10576
|
+
const memories = existsSync42(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
|
|
10464
10577
|
const captureTimes = memories.filter(({ memory: memory2 }) => ["attempt", "gotcha"].includes(memory2.frontmatter.type)).map(({ memory: memory2 }) => memory2.frontmatter.created_at);
|
|
10465
10578
|
const uncaptured = findUncapturedFailures(failures, captureTimes);
|
|
10466
10579
|
if (uncaptured.length === 0) {
|
|
@@ -10495,7 +10608,7 @@ function finishReport(root, initialized, mode, findings, config) {
|
|
|
10495
10608
|
async function runWithEnforcement(command, args, opts) {
|
|
10496
10609
|
const root = findProjectRoot37(opts.dir);
|
|
10497
10610
|
const paths = resolveHaivePaths35(root);
|
|
10498
|
-
if (!
|
|
10611
|
+
if (!existsSync42(paths.haiveDir)) {
|
|
10499
10612
|
ui.error(`No .ai/ found at ${root}. Run \`hivelore init\` first.`);
|
|
10500
10613
|
process.exit(1);
|
|
10501
10614
|
}
|
|
@@ -10514,7 +10627,7 @@ async function runWithEnforcement(command, args, opts) {
|
|
|
10514
10627
|
process.exit(2);
|
|
10515
10628
|
}
|
|
10516
10629
|
ui.info(`Hivelore briefing marker created for wrapped agent session: ${sessionId}`);
|
|
10517
|
-
ui.info(`Briefing written to ${
|
|
10630
|
+
ui.info(`Briefing written to ${path40.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
|
|
10518
10631
|
const child = spawn4(command, args, {
|
|
10519
10632
|
cwd: root,
|
|
10520
10633
|
stdio: "inherit",
|
|
@@ -10566,9 +10679,9 @@ async function writeWrapperBriefing(paths, sessionId, task) {
|
|
|
10566
10679
|
source: "haive-run",
|
|
10567
10680
|
memoryIds: briefing.memories.map((m) => m.id)
|
|
10568
10681
|
});
|
|
10569
|
-
const dir =
|
|
10682
|
+
const dir = path40.join(paths.runtimeDir, "enforcement", "briefings");
|
|
10570
10683
|
await mkdir16(dir, { recursive: true });
|
|
10571
|
-
const file =
|
|
10684
|
+
const file = path40.join(dir, `${sessionId}.md`);
|
|
10572
10685
|
const parts = [
|
|
10573
10686
|
"# Hivelore Briefing",
|
|
10574
10687
|
"",
|
|
@@ -10606,7 +10719,7 @@ async function checkBootstrapComplete(paths, config, productionCodeChanged) {
|
|
|
10606
10719
|
projectContextRaw = await readFile18(paths.projectContext, "utf8");
|
|
10607
10720
|
} catch {
|
|
10608
10721
|
}
|
|
10609
|
-
const memories =
|
|
10722
|
+
const memories = existsSync42(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
|
|
10610
10723
|
const codeMap = await loadCodeMap7(paths);
|
|
10611
10724
|
const codeFiles = codeMap ? Object.keys(codeMap.files) : [];
|
|
10612
10725
|
let existingModules = [];
|
|
@@ -10638,7 +10751,7 @@ ${renderBootstrapChecklist(assessment)}`,
|
|
|
10638
10751
|
async function buildEnforcementReport(dir, stage, sessionId) {
|
|
10639
10752
|
const root = findProjectRoot37(dir);
|
|
10640
10753
|
const paths = resolveHaivePaths35(root);
|
|
10641
|
-
const initialized =
|
|
10754
|
+
const initialized = existsSync42(paths.haiveDir);
|
|
10642
10755
|
const config = initialized ? await loadConfig12(paths) : {};
|
|
10643
10756
|
if (initialized) {
|
|
10644
10757
|
await applyLightweightRepairs(root, paths);
|
|
@@ -10672,7 +10785,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
10672
10785
|
findings: [{ severity: "info", code: "enforcement-off", message: "Hivelore enforcement is disabled." }]
|
|
10673
10786
|
});
|
|
10674
10787
|
}
|
|
10675
|
-
findings.push(...await inspectIntegrationVersions(root, "0.
|
|
10788
|
+
findings.push(...await inspectIntegrationVersions(root, "0.39.1"));
|
|
10676
10789
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
10677
10790
|
const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
|
|
10678
10791
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent Hivelore briefing marker exists." } : {
|
|
@@ -10743,10 +10856,14 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
10743
10856
|
}
|
|
10744
10857
|
const score = buildScore(effectiveFindings, config.enforcement?.scoreThreshold);
|
|
10745
10858
|
if (score.score < score.threshold) {
|
|
10859
|
+
const topPenalties = effectiveFindings.map((f) => ({
|
|
10860
|
+
code: f.code,
|
|
10861
|
+
penalty: f.severity === "error" ? f.impact ?? 25 : f.severity === "warn" ? f.impact ?? 8 : 0
|
|
10862
|
+
})).filter((p) => p.penalty > 0).sort((a, b) => b.penalty - a.penalty).slice(0, 3);
|
|
10746
10863
|
effectiveFindings = [...effectiveFindings, {
|
|
10747
10864
|
severity: "error",
|
|
10748
10865
|
code: "enforcement-score-below-threshold",
|
|
10749
|
-
message: `Enforcement score ${score.score}% is below required threshold ${score.threshold}
|
|
10866
|
+
message: `Enforcement score ${score.score}% is below required threshold ${score.threshold}%` + (topPenalties.length > 0 ? ` \u2014 top penalties: ${topPenalties.map((p) => `${p.code} (\u2212${p.penalty})`).join(", ")}` : "") + ".",
|
|
10750
10867
|
fix: "Load the relevant briefing, address policy findings, then rerun `hivelore enforce check`.",
|
|
10751
10868
|
impact: 0
|
|
10752
10869
|
}];
|
|
@@ -10787,8 +10904,8 @@ function withCategories(report) {
|
|
|
10787
10904
|
async function hasRecentSessionRecap(paths) {
|
|
10788
10905
|
const handoffAge = await handoffAgeMs(paths.root);
|
|
10789
10906
|
if (handoffAge !== null && handoffAge < SESSION_RECAP_TTL_MS) return true;
|
|
10790
|
-
if (!
|
|
10791
|
-
const all = await
|
|
10907
|
+
if (!existsSync42(paths.memoriesDir)) return false;
|
|
10908
|
+
const all = await loadMemoriesFromDir15(paths.memoriesDir);
|
|
10792
10909
|
return all.some(({ memory: memory2 }) => {
|
|
10793
10910
|
const fm = memory2.frontmatter;
|
|
10794
10911
|
const freshnessDate = fm.verified_at ?? fm.created_at;
|
|
@@ -10796,8 +10913,8 @@ async function hasRecentSessionRecap(paths) {
|
|
|
10796
10913
|
});
|
|
10797
10914
|
}
|
|
10798
10915
|
async function verifyMemoryPolicy(paths, config) {
|
|
10799
|
-
if (!
|
|
10800
|
-
const all = await
|
|
10916
|
+
if (!existsSync42(paths.memoriesDir)) return [];
|
|
10917
|
+
const all = await loadMemoriesFromDir15(paths.memoriesDir);
|
|
10801
10918
|
const findings = [];
|
|
10802
10919
|
const staleImportant = [];
|
|
10803
10920
|
let verified = 0;
|
|
@@ -10834,12 +10951,12 @@ async function verifyMemoryPolicy(paths, config) {
|
|
|
10834
10951
|
return findings;
|
|
10835
10952
|
}
|
|
10836
10953
|
async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
10837
|
-
if (!
|
|
10954
|
+
if (!existsSync42(paths.memoriesDir)) return [];
|
|
10838
10955
|
const changedFiles = (await getChangedFiles(paths.root, stage)).filter((f) => !isGeneratedArtifact(f));
|
|
10839
10956
|
if (changedFiles.length === 0) {
|
|
10840
10957
|
return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
|
|
10841
10958
|
}
|
|
10842
|
-
const all = await
|
|
10959
|
+
const all = await loadMemoriesFromDir15(paths.memoriesDir);
|
|
10843
10960
|
const changedSet = new Set(changedFiles);
|
|
10844
10961
|
const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention"]);
|
|
10845
10962
|
const relevant = all.filter(({ memory: memory2 }) => {
|
|
@@ -10868,7 +10985,7 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
|
10868
10985
|
const consulted = new Set(marker?.memory_ids ?? []);
|
|
10869
10986
|
const missing = relevant.filter(({ memory: memory2, filePath }) => {
|
|
10870
10987
|
if (consulted.has(memory2.frontmatter.id)) return false;
|
|
10871
|
-
if (changedSet.has(
|
|
10988
|
+
if (changedSet.has(path40.relative(paths.root, filePath))) return false;
|
|
10872
10989
|
return true;
|
|
10873
10990
|
}).map(({ memory: memory2 }) => memory2);
|
|
10874
10991
|
if (missing.length === 0) {
|
|
@@ -10910,15 +11027,27 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
|
10910
11027
|
}];
|
|
10911
11028
|
}
|
|
10912
11029
|
async function runPrecommitPolicy(paths, gate, stage) {
|
|
11030
|
+
const snapshot = await getPolicyDiffSnapshot(paths.root, stage);
|
|
11031
|
+
const weakenings = detectSensorWeakening(snapshot.diff);
|
|
11032
|
+
const weakeningFindings = weakenings.length > 0 ? [{
|
|
11033
|
+
severity: "warn",
|
|
11034
|
+
code: "sensor-weakened",
|
|
11035
|
+
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" : "") + ".",
|
|
11036
|
+
fix: "If the demotion/removal is intentional, say so in the commit message; otherwise restore the sensor (`hivelore sensors list` shows the current state).",
|
|
11037
|
+
memory_ids: [...new Set(weakenings.map((w) => w.memory_id))].slice(0, 10),
|
|
11038
|
+
impact: 8
|
|
11039
|
+
}] : [];
|
|
10913
11040
|
if (gate === "off") {
|
|
10914
|
-
return [
|
|
11041
|
+
return [
|
|
11042
|
+
{ severity: "info", code: "precommit-policy-off", message: "Anti-pattern gate is disabled (enforcement.antiPatternGate=off)." },
|
|
11043
|
+
...weakeningFindings
|
|
11044
|
+
];
|
|
10915
11045
|
}
|
|
10916
|
-
const snapshot = await getPolicyDiffSnapshot(paths.root, stage);
|
|
10917
11046
|
const touchedPaths = snapshot.paths;
|
|
10918
11047
|
if (touchedPaths.length === 0) {
|
|
10919
11048
|
const code = stage === "ci" ? "no-ci-diff-changes" : "no-staged-changes";
|
|
10920
11049
|
const message = stage === "ci" ? "No changed files found for CI policy diff." : "No staged changes found for pre-commit policy.";
|
|
10921
|
-
return [{ severity: "info", code, message }];
|
|
11050
|
+
return [{ severity: "info", code, message }, ...weakeningFindings];
|
|
10922
11051
|
}
|
|
10923
11052
|
const { block_on, anchored_blocks } = antiPatternGateParams2(gate);
|
|
10924
11053
|
const result = await preCommitCheck({
|
|
@@ -10933,7 +11062,7 @@ async function runPrecommitPolicy(paths, gate, stage) {
|
|
|
10933
11062
|
(w) => w.level === "review" && !w.reasons.includes("sensor")
|
|
10934
11063
|
);
|
|
10935
11064
|
const REVIEW_SEEN_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
10936
|
-
const reviewSeenFile =
|
|
11065
|
+
const reviewSeenFile = path40.join(paths.runtimeDir, "enforcement", "review-seen.json");
|
|
10937
11066
|
let reviewSeen = {};
|
|
10938
11067
|
try {
|
|
10939
11068
|
reviewSeen = JSON.parse(await readFile18(reviewSeenFile, "utf8"));
|
|
@@ -10960,7 +11089,7 @@ async function runPrecommitPolicy(paths, gate, stage) {
|
|
|
10960
11089
|
for (const [id, at] of Object.entries(reviewSeen)) {
|
|
10961
11090
|
if (!Number.isFinite(Date.parse(at)) || now - Date.parse(at) >= REVIEW_SEEN_TTL_MS) delete reviewSeen[id];
|
|
10962
11091
|
}
|
|
10963
|
-
await mkdir16(
|
|
11092
|
+
await mkdir16(path40.dirname(reviewSeenFile), { recursive: true });
|
|
10964
11093
|
await writeFile25(reviewSeenFile, JSON.stringify(reviewSeen, null, 2), "utf8");
|
|
10965
11094
|
} catch {
|
|
10966
11095
|
}
|
|
@@ -10973,7 +11102,8 @@ async function runPrecommitPolicy(paths, gate, stage) {
|
|
|
10973
11102
|
message: `${stage === "ci" ? "CI" : "Pre-commit"} policy passed for ${touchedPaths.length} changed file(s).`
|
|
10974
11103
|
},
|
|
10975
11104
|
...reviewFinding,
|
|
10976
|
-
...sensorFindings
|
|
11105
|
+
...sensorFindings,
|
|
11106
|
+
...weakeningFindings
|
|
10977
11107
|
];
|
|
10978
11108
|
}
|
|
10979
11109
|
const blockingWarnings = result.warnings.filter((w) => w.level === "blocking");
|
|
@@ -10988,13 +11118,14 @@ async function runPrecommitPolicy(paths, gate, stage) {
|
|
|
10988
11118
|
impact: 45
|
|
10989
11119
|
},
|
|
10990
11120
|
...reviewFinding,
|
|
10991
|
-
...sensorFindings
|
|
11121
|
+
...sensorFindings,
|
|
11122
|
+
...weakeningFindings
|
|
10992
11123
|
];
|
|
10993
11124
|
}
|
|
10994
11125
|
async function runSensorGate(paths, diff, stage) {
|
|
10995
|
-
if (!diff || !
|
|
11126
|
+
if (!diff || !existsSync42(paths.memoriesDir)) return [];
|
|
10996
11127
|
try {
|
|
10997
|
-
const loaded = await
|
|
11128
|
+
const loaded = await loadMemoriesFromDir15(paths.memoriesDir);
|
|
10998
11129
|
const scannable = loaded.map((l) => l.memory).filter((m) => Boolean(m.frontmatter.sensor) && !isRetiredMemory2(m.frontmatter, m.body));
|
|
10999
11130
|
if (scannable.length === 0) return [];
|
|
11000
11131
|
const targets = sensorTargetsFromDiff(diff).filter((t) => isSensorScannablePath(t.path));
|
|
@@ -11126,8 +11257,14 @@ command: ${run.command} (exit ${run.exit_code}, ${run.duration_ms}ms)${outputBlo
|
|
|
11126
11257
|
});
|
|
11127
11258
|
}
|
|
11128
11259
|
return findings;
|
|
11129
|
-
} catch {
|
|
11130
|
-
return [
|
|
11260
|
+
} catch (err) {
|
|
11261
|
+
return [{
|
|
11262
|
+
severity: "warn",
|
|
11263
|
+
code: "sensor-gate-errored",
|
|
11264
|
+
message: `The sensor gate itself errored, so NO sensors were evaluated on this diff: ` + `${err instanceof Error ? err.message : String(err)}`.slice(0, 400),
|
|
11265
|
+
fix: "Run `hivelore sensors check` to reproduce, and `hivelore doctor` for setup drift. The lessons' protection is OFF until this is fixed.",
|
|
11266
|
+
impact: 5
|
|
11267
|
+
}];
|
|
11131
11268
|
}
|
|
11132
11269
|
}
|
|
11133
11270
|
async function findGeneratedArtifacts(paths) {
|
|
@@ -11160,16 +11297,16 @@ async function cleanupRuntimeDir(runtimeDir) {
|
|
|
11160
11297
|
for (const entry of entries) {
|
|
11161
11298
|
if (entry.name === ".gitignore" || entry.name === "README.md") continue;
|
|
11162
11299
|
if (entry.name === "enforcement") {
|
|
11163
|
-
removed += await cleanupEnforcementDir(
|
|
11300
|
+
removed += await cleanupEnforcementDir(path40.join(runtimeDir, entry.name));
|
|
11164
11301
|
continue;
|
|
11165
11302
|
}
|
|
11166
|
-
await rm2(
|
|
11303
|
+
await rm2(path40.join(runtimeDir, entry.name), { recursive: true, force: true });
|
|
11167
11304
|
removed++;
|
|
11168
11305
|
}
|
|
11169
|
-
await writeFile25(
|
|
11170
|
-
if (!
|
|
11306
|
+
await writeFile25(path40.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
|
|
11307
|
+
if (!existsSync42(path40.join(runtimeDir, "README.md"))) {
|
|
11171
11308
|
await writeFile25(
|
|
11172
|
-
|
|
11309
|
+
path40.join(runtimeDir, "README.md"),
|
|
11173
11310
|
"# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. Hivelore cleanup preserves briefing markers so enforcement state remains valid.\n",
|
|
11174
11311
|
"utf8"
|
|
11175
11312
|
);
|
|
@@ -11182,10 +11319,10 @@ async function cleanupCacheDir(cacheDir) {
|
|
|
11182
11319
|
const entries = await readdir4(cacheDir, { withFileTypes: true }).catch(() => []);
|
|
11183
11320
|
for (const entry of entries) {
|
|
11184
11321
|
if (entry.name === ".gitignore") continue;
|
|
11185
|
-
await rm2(
|
|
11322
|
+
await rm2(path40.join(cacheDir, entry.name), { recursive: true, force: true });
|
|
11186
11323
|
removed++;
|
|
11187
11324
|
}
|
|
11188
|
-
await writeFile25(
|
|
11325
|
+
await writeFile25(path40.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
|
|
11189
11326
|
return removed;
|
|
11190
11327
|
}
|
|
11191
11328
|
async function cleanupEnforcementDir(enforcementDir) {
|
|
@@ -11193,7 +11330,7 @@ async function cleanupEnforcementDir(enforcementDir) {
|
|
|
11193
11330
|
const entries = await readdir4(enforcementDir, { withFileTypes: true }).catch(() => []);
|
|
11194
11331
|
for (const entry of entries) {
|
|
11195
11332
|
if (entry.name === "briefings") continue;
|
|
11196
|
-
await rm2(
|
|
11333
|
+
await rm2(path40.join(enforcementDir, entry.name), { recursive: true, force: true });
|
|
11197
11334
|
removed++;
|
|
11198
11335
|
}
|
|
11199
11336
|
return removed;
|
|
@@ -11211,8 +11348,8 @@ async function inspectIntegrationVersions(root, expectedVersion) {
|
|
|
11211
11348
|
const missingBins = /* @__PURE__ */ new Map();
|
|
11212
11349
|
const staleBins = /* @__PURE__ */ new Map();
|
|
11213
11350
|
for (const rel of files) {
|
|
11214
|
-
const file =
|
|
11215
|
-
if (!
|
|
11351
|
+
const file = path40.join(root, rel);
|
|
11352
|
+
if (!existsSync42(file)) continue;
|
|
11216
11353
|
const text = await readFile18(file, "utf8").catch(() => "");
|
|
11217
11354
|
for (const bin of extractAbsoluteHaiveBins2(text)) {
|
|
11218
11355
|
const version = versionForBinary2(bin);
|
|
@@ -11259,7 +11396,7 @@ function extractAbsoluteHaiveBins2(text) {
|
|
|
11259
11396
|
const p = match[2];
|
|
11260
11397
|
if (!p) continue;
|
|
11261
11398
|
try {
|
|
11262
|
-
if (
|
|
11399
|
+
if (statSync4(p).isDirectory()) continue;
|
|
11263
11400
|
} catch {
|
|
11264
11401
|
}
|
|
11265
11402
|
out.add(p);
|
|
@@ -11329,7 +11466,7 @@ async function resolveCiDiffRange(root) {
|
|
|
11329
11466
|
}
|
|
11330
11467
|
async function resolveGithubEventRange(root) {
|
|
11331
11468
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
11332
|
-
if (!eventPath || !
|
|
11469
|
+
if (!eventPath || !existsSync42(eventPath)) return null;
|
|
11333
11470
|
try {
|
|
11334
11471
|
const event = JSON.parse(await readFile18(eventPath, "utf8"));
|
|
11335
11472
|
const prBase = cleanGitSha(event.pull_request?.base?.sha);
|
|
@@ -11434,7 +11571,7 @@ function isShippablePath(file) {
|
|
|
11434
11571
|
}
|
|
11435
11572
|
var CI_SKIP_DIRECTIVE = /\[skip ci\]|\[ci skip\]|\[no ci\]|\[skip actions\]|\*\*\*NO_CI\*\*\*|skip-checks: *true/i;
|
|
11436
11573
|
async function checkCommitMessageSkipCi(root, msgfile) {
|
|
11437
|
-
const file =
|
|
11574
|
+
const file = path40.isAbsolute(msgfile) ? msgfile : path40.join(root, msgfile);
|
|
11438
11575
|
const raw = await readFile18(file, "utf8").catch(() => "");
|
|
11439
11576
|
const cleaned = raw.split("\n").filter((line) => !line.startsWith("#")).join("\n");
|
|
11440
11577
|
if (!CI_SKIP_DIRECTIVE.test(cleaned)) return { block: false, message: "" };
|
|
@@ -11463,7 +11600,7 @@ async function inspectReleaseVersionState(root, upstream) {
|
|
|
11463
11600
|
}
|
|
11464
11601
|
async function readPackageVersion(root, relPath) {
|
|
11465
11602
|
try {
|
|
11466
|
-
const data = JSON.parse(await readFile18(
|
|
11603
|
+
const data = JSON.parse(await readFile18(path40.join(root, relPath), "utf8"));
|
|
11467
11604
|
return typeof data.version === "string" ? data.version : void 0;
|
|
11468
11605
|
} catch {
|
|
11469
11606
|
return void 0;
|
|
@@ -11651,8 +11788,8 @@ function buildScore(findings, threshold = 80) {
|
|
|
11651
11788
|
};
|
|
11652
11789
|
}
|
|
11653
11790
|
async function installGitEnforcement(root) {
|
|
11654
|
-
const hooksDir =
|
|
11655
|
-
if (!
|
|
11791
|
+
const hooksDir = path40.join(root, ".git", "hooks");
|
|
11792
|
+
if (!existsSync42(path40.join(root, ".git"))) {
|
|
11656
11793
|
ui.warn("No .git directory found; git enforcement hooks skipped.");
|
|
11657
11794
|
return;
|
|
11658
11795
|
}
|
|
@@ -11708,8 +11845,8 @@ _hivelore sync --quiet --since ORIG_HEAD || true
|
|
|
11708
11845
|
}
|
|
11709
11846
|
];
|
|
11710
11847
|
for (const hook of hooks) {
|
|
11711
|
-
const file =
|
|
11712
|
-
if (
|
|
11848
|
+
const file = path40.join(hooksDir, hook.name);
|
|
11849
|
+
if (existsSync42(file)) {
|
|
11713
11850
|
const current = await readFile18(file, "utf8").catch(() => "");
|
|
11714
11851
|
if (current.includes(ENFORCE_HOOK_MARKER)) {
|
|
11715
11852
|
await writeFile25(file, hook.body, "utf8");
|
|
@@ -11726,10 +11863,10 @@ ${hook.body}`, "utf8");
|
|
|
11726
11863
|
ui.success("Installed git hooks: pre-commit, pre-push, commit-msg (blocking) + post-merge, post-rewrite (sync)");
|
|
11727
11864
|
}
|
|
11728
11865
|
async function installCiEnforcement(root) {
|
|
11729
|
-
const workflowPath =
|
|
11730
|
-
await mkdir16(
|
|
11866
|
+
const workflowPath = path40.join(root, ".github", "workflows", "haive-enforcement.yml");
|
|
11867
|
+
await mkdir16(path40.dirname(workflowPath), { recursive: true });
|
|
11731
11868
|
const workflow = renderCiEnforcementWorkflow();
|
|
11732
|
-
if (
|
|
11869
|
+
if (existsSync42(workflowPath)) {
|
|
11733
11870
|
const existing = await readFile18(workflowPath, "utf8");
|
|
11734
11871
|
const start = "# haive:enforcement-workflow:start";
|
|
11735
11872
|
const end = "# haive:enforcement-workflow:end";
|
|
@@ -11737,14 +11874,14 @@ async function installCiEnforcement(root) {
|
|
|
11737
11874
|
const endAt = existing.indexOf(end);
|
|
11738
11875
|
if (startAt >= 0 && endAt > startAt) {
|
|
11739
11876
|
await writeFile25(workflowPath, existing.slice(0, startAt) + workflow + existing.slice(endAt + end.length), "utf8");
|
|
11740
|
-
ui.success(`Updated ${
|
|
11877
|
+
ui.success(`Updated ${path40.relative(root, workflowPath)} managed block`);
|
|
11741
11878
|
} else {
|
|
11742
11879
|
ui.info("GitHub Actions enforcement workflow already exists without Hivelore markers \u2014 preserved");
|
|
11743
11880
|
}
|
|
11744
11881
|
return;
|
|
11745
11882
|
}
|
|
11746
11883
|
await writeFile25(workflowPath, workflow, "utf8");
|
|
11747
|
-
ui.success(`Created ${
|
|
11884
|
+
ui.success(`Created ${path40.relative(root, workflowPath)}`);
|
|
11748
11885
|
}
|
|
11749
11886
|
function renderCiEnforcementWorkflow() {
|
|
11750
11887
|
return `# haive:enforcement-workflow:start
|
|
@@ -11947,15 +12084,15 @@ function extractToolPaths(payload, root) {
|
|
|
11947
12084
|
}
|
|
11948
12085
|
function normalizeToolPath(file, root) {
|
|
11949
12086
|
const normalized = file.replace(/\\/g, "/");
|
|
11950
|
-
if (!
|
|
11951
|
-
return
|
|
12087
|
+
if (!path40.isAbsolute(normalized)) return normalized.replace(/^\.\//, "");
|
|
12088
|
+
return path40.relative(root, normalized).replace(/\\/g, "/");
|
|
11952
12089
|
}
|
|
11953
12090
|
async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
|
|
11954
|
-
if (!
|
|
12091
|
+
if (!existsSync42(paths.memoriesDir)) return [];
|
|
11955
12092
|
const marker = await readRecentBriefingMarker(paths, sessionId);
|
|
11956
12093
|
const consulted = new Set(marker?.memory_ids ?? []);
|
|
11957
12094
|
const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention", "attempt"]);
|
|
11958
|
-
const all = await
|
|
12095
|
+
const all = await loadMemoriesFromDir15(paths.memoriesDir);
|
|
11959
12096
|
return all.filter(({ memory: memory2 }) => {
|
|
11960
12097
|
const fm = memory2.frontmatter;
|
|
11961
12098
|
if (!policyTypes.has(fm.type)) return false;
|
|
@@ -11996,7 +12133,7 @@ async function readStdin2(maxBytes) {
|
|
|
11996
12133
|
}
|
|
11997
12134
|
var ATOMIC_STAGE_EXCLUDE = ["/.usage/", "/.runtime/", "/.cache/"];
|
|
11998
12135
|
async function stageResyncedArtifacts(root, paths) {
|
|
11999
|
-
const aiRel =
|
|
12136
|
+
const aiRel = path40.relative(root, paths.haiveDir);
|
|
12000
12137
|
const out = await runCommand2("git", ["diff", "--name-only", "--", aiRel], root).catch(() => "");
|
|
12001
12138
|
const toStage = out.split("\n").map((line) => line.trim()).filter(Boolean).filter((file) => !ATOMIC_STAGE_EXCLUDE.some((excl) => `/${file}`.includes(excl)));
|
|
12002
12139
|
if (toStage.length === 0) return;
|
|
@@ -12023,9 +12160,9 @@ function runCommand2(cmd, args, cwd) {
|
|
|
12023
12160
|
}
|
|
12024
12161
|
|
|
12025
12162
|
// src/commands/release.ts
|
|
12026
|
-
import { existsSync as
|
|
12163
|
+
import { existsSync as existsSync43 } from "fs";
|
|
12027
12164
|
import { readFile as readFile19, writeFile as writeFile26 } from "fs/promises";
|
|
12028
|
-
import
|
|
12165
|
+
import path41 from "path";
|
|
12029
12166
|
import "commander";
|
|
12030
12167
|
import { findProjectRoot as findProjectRoot38 } from "@hivelore/core";
|
|
12031
12168
|
import { execFile as execFile6 } from "child_process";
|
|
@@ -12039,7 +12176,7 @@ var VERSION_FILES2 = [
|
|
|
12039
12176
|
"packages/embeddings/package.json"
|
|
12040
12177
|
];
|
|
12041
12178
|
async function readCurrentVersion(root) {
|
|
12042
|
-
const pkg = JSON.parse(await readFile19(
|
|
12179
|
+
const pkg = JSON.parse(await readFile19(path41.join(root, "package.json"), "utf8"));
|
|
12043
12180
|
if (!pkg.version) throw new Error("Root package.json has no version field.");
|
|
12044
12181
|
return pkg.version;
|
|
12045
12182
|
}
|
|
@@ -12060,8 +12197,8 @@ function registerRelease(program2) {
|
|
|
12060
12197
|
const current = await readCurrentVersion(root);
|
|
12061
12198
|
const next = nextVersion(current, spec);
|
|
12062
12199
|
for (const rel of VERSION_FILES2) {
|
|
12063
|
-
const file =
|
|
12064
|
-
if (!
|
|
12200
|
+
const file = path41.join(root, rel);
|
|
12201
|
+
if (!existsSync43(file)) {
|
|
12065
12202
|
ui.warn(`skip ${rel} (missing)`);
|
|
12066
12203
|
continue;
|
|
12067
12204
|
}
|
|
@@ -12074,8 +12211,8 @@ function registerRelease(program2) {
|
|
|
12074
12211
|
}
|
|
12075
12212
|
await writeFile26(file, updated, "utf8");
|
|
12076
12213
|
}
|
|
12077
|
-
const changelog =
|
|
12078
|
-
if (
|
|
12214
|
+
const changelog = path41.join(root, "CHANGELOG.md");
|
|
12215
|
+
if (existsSync43(changelog)) {
|
|
12079
12216
|
const raw = await readFile19(changelog, "utf8");
|
|
12080
12217
|
const heading = `## [${next}]${opts.title ? ` \u2014 ${opts.title}` : ""}`;
|
|
12081
12218
|
if (!raw.includes(`## [${next}]`)) {
|
|
@@ -12098,8 +12235,8 @@ ${heading}
|
|
|
12098
12235
|
const root = findProjectRoot38(opts.dir);
|
|
12099
12236
|
const version = await readCurrentVersion(root);
|
|
12100
12237
|
for (const rel of VERSION_FILES2.slice(1)) {
|
|
12101
|
-
const file =
|
|
12102
|
-
if (!
|
|
12238
|
+
const file = path41.join(root, rel);
|
|
12239
|
+
if (!existsSync43(file)) continue;
|
|
12103
12240
|
const v = JSON.parse(await readFile19(file, "utf8")).version;
|
|
12104
12241
|
if (v !== version) {
|
|
12105
12242
|
ui.error(`${rel} is at ${v}, root at ${version} \u2014 lockstep broken; run \`hivelore release bump\` first.`);
|
|
@@ -12145,9 +12282,9 @@ function registerRun(program2) {
|
|
|
12145
12282
|
|
|
12146
12283
|
// src/commands/sensors.ts
|
|
12147
12284
|
import { execFile as execFile7 } from "child_process";
|
|
12148
|
-
import { existsSync as
|
|
12285
|
+
import { existsSync as existsSync44 } from "fs";
|
|
12149
12286
|
import { chmod as chmod2, mkdir as mkdir17, readFile as readFile20, writeFile as writeFile27 } from "fs/promises";
|
|
12150
|
-
import
|
|
12287
|
+
import path42 from "path";
|
|
12151
12288
|
import { promisify as promisify7 } from "util";
|
|
12152
12289
|
import "commander";
|
|
12153
12290
|
import {
|
|
@@ -12160,12 +12297,13 @@ import {
|
|
|
12160
12297
|
judgeProposedSensor,
|
|
12161
12298
|
loadConfig as loadConfig13,
|
|
12162
12299
|
loadSensorLedger as loadSensorLedger4,
|
|
12163
|
-
loadMemoriesFromDir as
|
|
12300
|
+
loadMemoriesFromDir as loadMemoriesFromDir16,
|
|
12164
12301
|
normalizeFramework,
|
|
12165
12302
|
parseLessonFields,
|
|
12166
12303
|
recordPreventionHits as recordPreventionHits2,
|
|
12167
12304
|
resolveHaivePaths as resolveHaivePaths36,
|
|
12168
12305
|
runSensors as runSensors2,
|
|
12306
|
+
buildProposeCommand,
|
|
12169
12307
|
scaffoldPostIncidentTest,
|
|
12170
12308
|
selectCommandSensors as selectCommandSensors2,
|
|
12171
12309
|
TEST_FRAMEWORKS,
|
|
@@ -12212,7 +12350,7 @@ function registerSensors(program2) {
|
|
|
12212
12350
|
const root = findProjectRoot39(opts.dir);
|
|
12213
12351
|
const paths = resolveHaivePaths36(root);
|
|
12214
12352
|
const memories = await runnableSensorMemories(paths);
|
|
12215
|
-
const diff = opts.diffFile ? await readFile20(
|
|
12353
|
+
const diff = opts.diffFile ? await readFile20(path42.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
|
|
12216
12354
|
const targets = scannableSensorTargets(diff);
|
|
12217
12355
|
const hits = runSensors2(memories, targets);
|
|
12218
12356
|
const config = await loadConfig13(paths);
|
|
@@ -12344,7 +12482,7 @@ function registerSensors(program2) {
|
|
|
12344
12482
|
}
|
|
12345
12483
|
const root = findProjectRoot39(opts.dir);
|
|
12346
12484
|
const paths = resolveHaivePaths36(root);
|
|
12347
|
-
const loaded =
|
|
12485
|
+
const loaded = existsSync44(paths.memoriesDir) ? await loadMemoriesFromDir16(paths.memoriesDir) : [];
|
|
12348
12486
|
const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
|
|
12349
12487
|
if (!found) {
|
|
12350
12488
|
ui.error(`No memory found with id ${id}`);
|
|
@@ -12411,7 +12549,7 @@ function registerSensors(program2) {
|
|
|
12411
12549
|
return;
|
|
12412
12550
|
}
|
|
12413
12551
|
const root2 = findProjectRoot39(opts.dir);
|
|
12414
|
-
const { proposeSensor } = await import("./server-
|
|
12552
|
+
const { proposeSensor } = await import("./server-X6S6KTYJ.js");
|
|
12415
12553
|
const out = await proposeSensor(
|
|
12416
12554
|
{
|
|
12417
12555
|
memory_id: id,
|
|
@@ -12455,7 +12593,7 @@ function registerSensors(program2) {
|
|
|
12455
12593
|
}
|
|
12456
12594
|
const root = findProjectRoot39(opts.dir);
|
|
12457
12595
|
const paths = resolveHaivePaths36(root);
|
|
12458
|
-
const loaded =
|
|
12596
|
+
const loaded = existsSync44(paths.memoriesDir) ? await loadMemoriesFromDir16(paths.memoriesDir) : [];
|
|
12459
12597
|
const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
|
|
12460
12598
|
if (!found) {
|
|
12461
12599
|
ui.error(`No memory found with id ${id}`);
|
|
@@ -12499,13 +12637,18 @@ function registerSensors(program2) {
|
|
|
12499
12637
|
ui.info(
|
|
12500
12638
|
`self-check: silent on current=${verdict.self_check.silent_on_current}` + (verdict.self_check.fires_on_bad === null ? "; no bad example tested" : `; fires on bad=${verdict.self_check.fires_on_bad}`)
|
|
12501
12639
|
);
|
|
12640
|
+
if (found.memory.frontmatter.scope === "personal") {
|
|
12641
|
+
ui.warn(
|
|
12642
|
+
`This lesson is personal-scoped \u2014 the sensor guards only YOUR machine (personal memories are gitignored). Promote it so the gate travels with the repo: hivelore memory promote ${id}`
|
|
12643
|
+
);
|
|
12644
|
+
}
|
|
12502
12645
|
});
|
|
12503
12646
|
sensors.command("scaffold").description(
|
|
12504
12647
|
"Generate a PENDING post-incident test from a lesson (mem_tried/attempt/gotcha) \u2014 the on-ramp to\n a command sensor. Writes a test stub carrying the incident's provenance, then prints the exact\n `sensors propose --kind test` line to arm it once you've written the assertion. It never arms a\n sensor itself \u2014 propose_sensor stays the sole validated writer.\n\n Example:\n hivelore sensors scaffold 2026-07-03-attempt-refund-exceeds-capture --framework vitest"
|
|
12505
12648
|
).argument("<memory-id>", "lesson id to scaffold a test from").option("--framework <fw>", `test framework: ${TEST_FRAMEWORKS.join(" | ")} (auto-detected when omitted)`).option("--out <path>", "override the generated test file path (project-relative)").option("--stdout", "print the test to stdout instead of writing a file", false).option("--force", "overwrite an existing file at the target path", false).option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
12506
12649
|
const root = findProjectRoot39(opts.dir);
|
|
12507
12650
|
const paths = resolveHaivePaths36(root);
|
|
12508
|
-
const loaded =
|
|
12651
|
+
const loaded = existsSync44(paths.memoriesDir) ? await loadMemoriesFromDir16(paths.memoriesDir) : [];
|
|
12509
12652
|
const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
|
|
12510
12653
|
if (!found) {
|
|
12511
12654
|
ui.error(`No memory found with id ${id}`);
|
|
@@ -12513,43 +12656,59 @@ function registerSensors(program2) {
|
|
|
12513
12656
|
return;
|
|
12514
12657
|
}
|
|
12515
12658
|
const fm = found.memory.frontmatter;
|
|
12516
|
-
const
|
|
12517
|
-
|
|
12518
|
-
if (!framework) {
|
|
12659
|
+
const forced = opts.framework ? normalizeFramework(opts.framework) : null;
|
|
12660
|
+
if (opts.framework && !forced) {
|
|
12519
12661
|
ui.error(`Unknown --framework "${opts.framework}". Use one of: ${TEST_FRAMEWORKS.join(", ")}.`);
|
|
12520
12662
|
process.exitCode = 1;
|
|
12521
12663
|
return;
|
|
12522
12664
|
}
|
|
12665
|
+
const allGroups = await detectTestFrameworksForAnchors(root, fm.anchor.paths ?? []);
|
|
12666
|
+
const groups = opts.out ? allGroups.slice(0, 1) : allGroups;
|
|
12523
12667
|
const fields = parseLessonFields(found.memory.body);
|
|
12524
|
-
const
|
|
12525
|
-
|
|
12526
|
-
|
|
12527
|
-
|
|
12528
|
-
|
|
12529
|
-
|
|
12530
|
-
|
|
12531
|
-
|
|
12532
|
-
|
|
12533
|
-
{ framework, outPath: opts.out, baseDir:
|
|
12668
|
+
const lesson = {
|
|
12669
|
+
memoryId: id,
|
|
12670
|
+
title: fields.title || id,
|
|
12671
|
+
whyFailed: fields.whyFailed,
|
|
12672
|
+
instead: fields.instead,
|
|
12673
|
+
incident: fm.sensor?.incident,
|
|
12674
|
+
paths: fm.anchor.paths
|
|
12675
|
+
};
|
|
12676
|
+
let scaffolds = groups.map(
|
|
12677
|
+
(g) => scaffoldPostIncidentTest(lesson, { framework: forced ?? g.framework, outPath: opts.out, baseDir: g.baseDir })
|
|
12534
12678
|
);
|
|
12679
|
+
let proposeCmd = scaffolds[0].proposeCommand;
|
|
12680
|
+
if (scaffolds.length > 1) {
|
|
12681
|
+
proposeCmd = buildProposeCommand(lesson, scaffolds.map((s) => s.runCommand).join(" && "));
|
|
12682
|
+
scaffolds = groups.map(
|
|
12683
|
+
(g) => scaffoldPostIncidentTest(lesson, { framework: forced ?? g.framework, baseDir: g.baseDir, proposeCommandOverride: proposeCmd })
|
|
12684
|
+
);
|
|
12685
|
+
}
|
|
12535
12686
|
if (opts.stdout) {
|
|
12536
|
-
|
|
12537
|
-
|
|
12687
|
+
for (const scaffold of scaffolds) {
|
|
12688
|
+
if (scaffolds.length > 1) ui.info(`--- ${scaffold.relPath} ---`);
|
|
12689
|
+
console.log(scaffold.content);
|
|
12690
|
+
}
|
|
12691
|
+
ui.info(`Arm ${scaffolds.length > 1 ? "them" : "it"} once written: ${proposeCmd}`);
|
|
12538
12692
|
return;
|
|
12539
12693
|
}
|
|
12540
|
-
|
|
12541
|
-
|
|
12542
|
-
|
|
12543
|
-
|
|
12544
|
-
|
|
12694
|
+
for (const scaffold of scaffolds) {
|
|
12695
|
+
const abs = path42.isAbsolute(scaffold.relPath) ? scaffold.relPath : path42.resolve(root, scaffold.relPath);
|
|
12696
|
+
if (existsSync44(abs) && !opts.force) {
|
|
12697
|
+
ui.error(`${scaffold.relPath} already exists \u2014 pass --force to overwrite, or --out <path> for a different file.`);
|
|
12698
|
+
process.exitCode = 1;
|
|
12699
|
+
return;
|
|
12700
|
+
}
|
|
12701
|
+
await mkdir17(path42.dirname(abs), { recursive: true });
|
|
12702
|
+
await writeFile27(abs, scaffold.content, "utf8");
|
|
12703
|
+
ui.success(`Wrote ${scaffold.framework} post-incident test \u2192 ${scaffold.relPath}`);
|
|
12704
|
+
}
|
|
12705
|
+
if (scaffolds.length > 1) {
|
|
12706
|
+
ui.info(` Lesson spans ${scaffolds.length} packages \u2014 one pending test per owning package, ONE sensor arms them all.`);
|
|
12545
12707
|
}
|
|
12546
|
-
await mkdir17(path41.dirname(abs), { recursive: true });
|
|
12547
|
-
await writeFile27(abs, scaffold.content, "utf8");
|
|
12548
|
-
ui.success(`Wrote ${framework} post-incident test \u2192 ${scaffold.relPath}`);
|
|
12549
12708
|
ui.info(" 1. Fill in the assertion (RED on the incident, GREEN once fixed).");
|
|
12550
|
-
ui.info(` 2. Run it: ${
|
|
12709
|
+
ui.info(` 2. Run ${scaffolds.length > 1 ? "them" : "it"}: ${scaffolds.map((s) => s.runCommand).join(" && ")}`);
|
|
12551
12710
|
ui.info(" 3. Arm it as a deterministic gate:");
|
|
12552
|
-
console.log(` ${
|
|
12711
|
+
console.log(` ${proposeCmd}`);
|
|
12553
12712
|
});
|
|
12554
12713
|
sensors.command("export").description("Export regex sensors into .ai/generated for external toolchains").option("--format <format>", "grep | eslint", "grep").option("--out-dir <dir>", "output directory", ".ai/generated").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
12555
12714
|
const format = opts.format ?? "grep";
|
|
@@ -12561,13 +12720,13 @@ function registerSensors(program2) {
|
|
|
12561
12720
|
const root = findProjectRoot39(opts.dir);
|
|
12562
12721
|
const paths = resolveHaivePaths36(root);
|
|
12563
12722
|
const rows = await sensorRows(paths);
|
|
12564
|
-
const outDir =
|
|
12723
|
+
const outDir = path42.resolve(root, opts.outDir ?? ".ai/generated");
|
|
12565
12724
|
await mkdir17(outDir, { recursive: true });
|
|
12566
|
-
const outPath =
|
|
12725
|
+
const outPath = path42.join(outDir, format === "grep" ? "haive-sensors-grep.sh" : "haive-sensors-eslint.json");
|
|
12567
12726
|
const content = format === "grep" ? renderGrepScript(rows) : JSON.stringify({ sensors: rows }, null, 2) + "\n";
|
|
12568
12727
|
await writeFile27(outPath, content, "utf8");
|
|
12569
12728
|
if (format === "grep") await chmod2(outPath, 493);
|
|
12570
|
-
ui.success(`Exported ${rows.length} sensor(s): ${
|
|
12729
|
+
ui.success(`Exported ${rows.length} sensor(s): ${path42.relative(root, outPath)}`);
|
|
12571
12730
|
});
|
|
12572
12731
|
}
|
|
12573
12732
|
function deriveProposedMessage(body, pattern, absent) {
|
|
@@ -12600,8 +12759,8 @@ async function sensorRows(paths) {
|
|
|
12600
12759
|
});
|
|
12601
12760
|
}
|
|
12602
12761
|
async function runnableSensorMemories(paths, regexOnly = true) {
|
|
12603
|
-
if (!
|
|
12604
|
-
const loaded = await
|
|
12762
|
+
if (!existsSync44(paths.memoriesDir)) return [];
|
|
12763
|
+
const loaded = await loadMemoriesFromDir16(paths.memoriesDir);
|
|
12605
12764
|
return loaded.map(({ memory: memory2 }) => memory2).filter((memory2) => {
|
|
12606
12765
|
const sensor = memory2.frontmatter.sensor;
|
|
12607
12766
|
if (!sensor) return false;
|
|
@@ -12642,15 +12801,15 @@ function shellQuote(value) {
|
|
|
12642
12801
|
}
|
|
12643
12802
|
|
|
12644
12803
|
// src/commands/ingest.ts
|
|
12645
|
-
import { existsSync as
|
|
12804
|
+
import { existsSync as existsSync45 } from "fs";
|
|
12646
12805
|
import { mkdir as mkdir18, readFile as readFile21, writeFile as writeFile28 } from "fs/promises";
|
|
12647
|
-
import
|
|
12806
|
+
import path43 from "path";
|
|
12648
12807
|
import "commander";
|
|
12649
12808
|
import {
|
|
12650
12809
|
draftsFromFindings,
|
|
12651
12810
|
filterNewDrafts,
|
|
12652
12811
|
findProjectRoot as findProjectRoot40,
|
|
12653
|
-
loadMemoriesFromDir as
|
|
12812
|
+
loadMemoriesFromDir as loadMemoriesFromDir17,
|
|
12654
12813
|
memoryFilePath as memoryFilePath7,
|
|
12655
12814
|
parseFindings,
|
|
12656
12815
|
resolveHaivePaths as resolveHaivePaths37,
|
|
@@ -12680,7 +12839,7 @@ function registerIngest(program2) {
|
|
|
12680
12839
|
}
|
|
12681
12840
|
const root = findProjectRoot40(opts.dir);
|
|
12682
12841
|
const paths = resolveHaivePaths37(root);
|
|
12683
|
-
if (!
|
|
12842
|
+
if (!existsSync45(paths.haiveDir)) {
|
|
12684
12843
|
ui.error(`No .ai/ found at ${root}. Run \`hivelore init\` first.`);
|
|
12685
12844
|
process.exitCode = 1;
|
|
12686
12845
|
return;
|
|
@@ -12701,8 +12860,8 @@ function registerIngest(program2) {
|
|
|
12701
12860
|
process.exitCode = 1;
|
|
12702
12861
|
return;
|
|
12703
12862
|
}
|
|
12704
|
-
const reportPath =
|
|
12705
|
-
if (!
|
|
12863
|
+
const reportPath = path43.resolve(root, file);
|
|
12864
|
+
if (!existsSync45(reportPath)) {
|
|
12706
12865
|
ui.error(`Report file not found: ${reportPath}`);
|
|
12707
12866
|
process.exitCode = 1;
|
|
12708
12867
|
return;
|
|
@@ -12734,7 +12893,7 @@ function registerIngest(program2) {
|
|
|
12734
12893
|
process.exitCode = 1;
|
|
12735
12894
|
return;
|
|
12736
12895
|
}
|
|
12737
|
-
const existing =
|
|
12896
|
+
const existing = existsSync45(paths.memoriesDir) ? await loadMemoriesFromDir17(paths.memoriesDir) : [];
|
|
12738
12897
|
const existingTopics = new Set(
|
|
12739
12898
|
existing.map(({ memory: memory2 }) => memory2.frontmatter.topic).filter((t) => Boolean(t))
|
|
12740
12899
|
);
|
|
@@ -12796,13 +12955,13 @@ function registerIngest(program2) {
|
|
|
12796
12955
|
await writeDraft(paths, draft);
|
|
12797
12956
|
created++;
|
|
12798
12957
|
}
|
|
12799
|
-
ui.success(`Created ${created} proposed memory(ies) under ${
|
|
12958
|
+
ui.success(`Created ${created} proposed memory(ies) under ${path43.relative(root, paths.memoriesDir)}/`);
|
|
12800
12959
|
ui.info("Review with `hivelore memory pending`; promote sensors with `hivelore sensors promote <id> --yes`.");
|
|
12801
12960
|
});
|
|
12802
12961
|
}
|
|
12803
12962
|
async function writeDraft(paths, draft) {
|
|
12804
12963
|
const file = memoryFilePath7(paths, draft.frontmatter.scope, draft.frontmatter.id, draft.frontmatter.module);
|
|
12805
|
-
await mkdir18(
|
|
12964
|
+
await mkdir18(path43.dirname(file), { recursive: true });
|
|
12806
12965
|
await writeFile28(file, serializeMemory16({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
|
|
12807
12966
|
return file;
|
|
12808
12967
|
}
|
|
@@ -12844,13 +13003,13 @@ async function fetchSonarIssues(opts) {
|
|
|
12844
13003
|
}
|
|
12845
13004
|
|
|
12846
13005
|
// src/commands/dashboard.ts
|
|
12847
|
-
import { existsSync as
|
|
13006
|
+
import { existsSync as existsSync46 } from "fs";
|
|
12848
13007
|
import "commander";
|
|
12849
13008
|
import {
|
|
12850
13009
|
buildDashboard,
|
|
12851
13010
|
findProjectRoot as findProjectRoot41,
|
|
12852
13011
|
loadConfig as loadConfig14,
|
|
12853
|
-
loadMemoriesFromDir as
|
|
13012
|
+
loadMemoriesFromDir as loadMemoriesFromDir18,
|
|
12854
13013
|
loadPreventionEvents as loadPreventionEvents4,
|
|
12855
13014
|
loadUsageIndex as loadUsageIndex16,
|
|
12856
13015
|
resolveHaivePaths as resolveHaivePaths38
|
|
@@ -12861,12 +13020,12 @@ function registerDashboard(program2) {
|
|
|
12861
13020
|
).option("--json", "emit the full report as JSON", false).option("--top <n>", "rows per top-list", "10").option("--dormant-days <n>", "dormancy window for impact scoring").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
12862
13021
|
const root = findProjectRoot41(opts.dir);
|
|
12863
13022
|
const paths = resolveHaivePaths38(root);
|
|
12864
|
-
if (!
|
|
13023
|
+
if (!existsSync46(paths.haiveDir)) {
|
|
12865
13024
|
ui.error(`No .ai/ found at ${root}. Run \`hivelore init\` first.`);
|
|
12866
13025
|
process.exitCode = 1;
|
|
12867
13026
|
return;
|
|
12868
13027
|
}
|
|
12869
|
-
const memories =
|
|
13028
|
+
const memories = existsSync46(paths.memoriesDir) ? await loadMemoriesFromDir18(paths.memoriesDir) : [];
|
|
12870
13029
|
const usage = await loadUsageIndex16(paths);
|
|
12871
13030
|
const preventionEvents = await loadPreventionEvents4(paths);
|
|
12872
13031
|
const config = await loadConfig14(paths);
|
|
@@ -12992,8 +13151,8 @@ function warnNum(n) {
|
|
|
12992
13151
|
// src/commands/dev-link.ts
|
|
12993
13152
|
import { execFile as execFile8 } from "child_process";
|
|
12994
13153
|
import { cp, readFile as readFile22 } from "fs/promises";
|
|
12995
|
-
import { existsSync as
|
|
12996
|
-
import
|
|
13154
|
+
import { existsSync as existsSync47 } from "fs";
|
|
13155
|
+
import path44 from "path";
|
|
12997
13156
|
import { promisify as promisify8 } from "util";
|
|
12998
13157
|
import "commander";
|
|
12999
13158
|
import { findProjectRoot as findProjectRoot42 } from "@hivelore/core";
|
|
@@ -13002,7 +13161,7 @@ function registerDevLink(program2) {
|
|
|
13002
13161
|
const dev = program2.commands.find((c) => c.name() === "dev") ?? program2.command("dev").description("Developer utilities for working on Hivelore itself.");
|
|
13003
13162
|
dev.command("link").description("Hot-swap this repo's built dist into the global @hivelore (or legacy @hiveai) install so the global binary runs your local code.").option("-d, --dir <dir>", "repo root (default: discovered from cwd)").option("--json", "emit a machine-readable summary", false).action(async (opts) => {
|
|
13004
13163
|
const root = findProjectRoot42(opts.dir);
|
|
13005
|
-
if (!
|
|
13164
|
+
if (!existsSync47(path44.join(root, "packages", "cli", "dist", "index.js"))) {
|
|
13006
13165
|
ui.error(`Not the Hivelore monorepo (no packages/cli/dist) at ${root}. Run \`pnpm -r build\` first, or pass --dir.`);
|
|
13007
13166
|
process.exitCode = 1;
|
|
13008
13167
|
return;
|
|
@@ -13011,9 +13170,9 @@ function registerDevLink(program2) {
|
|
|
13011
13170
|
try {
|
|
13012
13171
|
globalModules = (await exec6("npm", ["root", "-g"])).stdout.trim();
|
|
13013
13172
|
} catch {
|
|
13014
|
-
globalModules =
|
|
13173
|
+
globalModules = path44.join(path44.dirname(path44.dirname(process.execPath)), "lib", "node_modules");
|
|
13015
13174
|
}
|
|
13016
|
-
const scopeDirs = ["@hivelore", "@hiveai"].map((scope) =>
|
|
13175
|
+
const scopeDirs = ["@hivelore", "@hiveai"].map((scope) => path44.join(globalModules, scope)).filter((dir) => existsSync47(dir));
|
|
13017
13176
|
if (scopeDirs.length === 0) {
|
|
13018
13177
|
ui.error(`No global @hivelore (or legacy @hiveai) install under ${globalModules}. Install once with \`npm i -g @hivelore/cli\`, then re-run.`);
|
|
13019
13178
|
process.exitCode = 1;
|
|
@@ -13021,24 +13180,24 @@ function registerDevLink(program2) {
|
|
|
13021
13180
|
}
|
|
13022
13181
|
const linked = [];
|
|
13023
13182
|
const copyDist = async (fromPkg, toDistDir) => {
|
|
13024
|
-
const from =
|
|
13025
|
-
if (!
|
|
13183
|
+
const from = path44.join(root, "packages", fromPkg, "dist");
|
|
13184
|
+
if (!existsSync47(from) || !existsSync47(path44.dirname(toDistDir))) return;
|
|
13026
13185
|
await cp(from, toDistDir, { recursive: true });
|
|
13027
|
-
linked.push(
|
|
13186
|
+
linked.push(path44.relative(globalModules, toDistDir));
|
|
13028
13187
|
};
|
|
13029
13188
|
for (const globalHive of scopeDirs) {
|
|
13030
|
-
const nestedScope =
|
|
13189
|
+
const nestedScope = path44.basename(globalHive);
|
|
13031
13190
|
for (const pkg of ["cli", "mcp"]) {
|
|
13032
|
-
await copyDist(pkg,
|
|
13191
|
+
await copyDist(pkg, path44.join(globalHive, pkg, "dist"));
|
|
13033
13192
|
for (const nested of ["core", "embeddings"]) {
|
|
13034
|
-
await copyDist(nested,
|
|
13193
|
+
await copyDist(nested, path44.join(globalHive, pkg, "node_modules", nestedScope, nested, "dist"));
|
|
13035
13194
|
}
|
|
13036
13195
|
}
|
|
13037
|
-
await copyDist("core",
|
|
13196
|
+
await copyDist("core", path44.join(globalHive, "core", "dist"));
|
|
13038
13197
|
}
|
|
13039
13198
|
let version = "unknown";
|
|
13040
13199
|
try {
|
|
13041
|
-
version = JSON.parse(await readFile22(
|
|
13200
|
+
version = JSON.parse(await readFile22(path44.join(root, "package.json"), "utf8")).version ?? "unknown";
|
|
13042
13201
|
} catch {
|
|
13043
13202
|
}
|
|
13044
13203
|
if (opts.json) {
|
|
@@ -13049,7 +13208,7 @@ function registerDevLink(program2) {
|
|
|
13049
13208
|
ui.warn("Nothing linked \u2014 no matching dist targets were found in the global install.");
|
|
13050
13209
|
return;
|
|
13051
13210
|
}
|
|
13052
|
-
ui.success(`Linked local dist (v${version}) into the global install(s): ${scopeDirs.map((d) =>
|
|
13211
|
+
ui.success(`Linked local dist (v${version}) into the global install(s): ${scopeDirs.map((d) => path44.basename(d)).join(", ")}`);
|
|
13053
13212
|
for (const t of linked) console.log(` ${ui.dim("\u2192")} ${t}`);
|
|
13054
13213
|
console.log(ui.dim("The global binary now runs your local build (git hooks + MCP included)."));
|
|
13055
13214
|
});
|
|
@@ -13057,8 +13216,8 @@ function registerDevLink(program2) {
|
|
|
13057
13216
|
|
|
13058
13217
|
// src/commands/coverage.ts
|
|
13059
13218
|
import { readFile as readFile23 } from "fs/promises";
|
|
13060
|
-
import { existsSync as
|
|
13061
|
-
import
|
|
13219
|
+
import { existsSync as existsSync48 } from "fs";
|
|
13220
|
+
import path45 from "path";
|
|
13062
13221
|
import "commander";
|
|
13063
13222
|
import {
|
|
13064
13223
|
findCoverageGaps,
|
|
@@ -13068,7 +13227,7 @@ import {
|
|
|
13068
13227
|
tallyHotFiles
|
|
13069
13228
|
} from "@hivelore/core";
|
|
13070
13229
|
async function readAgentHotFiles(root, cacheFile, sinceMs) {
|
|
13071
|
-
if (!
|
|
13230
|
+
if (!existsSync48(cacheFile)) return [];
|
|
13072
13231
|
const raw = await readFile23(cacheFile, "utf8").catch(() => "");
|
|
13073
13232
|
const files = [];
|
|
13074
13233
|
for (const line of raw.split("\n")) {
|
|
@@ -13082,7 +13241,7 @@ async function readAgentHotFiles(root, cacheFile, sinceMs) {
|
|
|
13082
13241
|
}
|
|
13083
13242
|
for (const f of obs.files ?? []) {
|
|
13084
13243
|
if (typeof f !== "string" || !f) continue;
|
|
13085
|
-
const rel =
|
|
13244
|
+
const rel = path45.isAbsolute(f) ? path45.relative(root, f) : f;
|
|
13086
13245
|
if (rel.startsWith("..")) continue;
|
|
13087
13246
|
files.push(rel);
|
|
13088
13247
|
}
|
|
@@ -13126,7 +13285,7 @@ function registerCoverage(program2) {
|
|
|
13126
13285
|
}) : null;
|
|
13127
13286
|
const gitHotFiles = (radar?.hotFiles ?? []).filter((h) => !isNoisePath(h.path)).map((h) => ({ path: h.path, changes: h.changes, source: "git" }));
|
|
13128
13287
|
const sinceMs = Date.now() - days * 864e5;
|
|
13129
|
-
const agentHotFiles = useAgent ? (await readAgentHotFiles(root,
|
|
13288
|
+
const agentHotFiles = useAgent ? (await readAgentHotFiles(root, path45.join(paths.haiveDir, ".cache", "observations.jsonl"), sinceMs)).filter((h) => !isNoisePath(h.path)) : [];
|
|
13130
13289
|
const hotFiles = mergeHotFiles(gitHotFiles, agentHotFiles);
|
|
13131
13290
|
const memories = await loadMemoriesFromDir(paths.memoriesDir);
|
|
13132
13291
|
const gaps = findCoverageGaps(hotFiles, memories, { minChanges, limit });
|
|
@@ -13164,8 +13323,8 @@ function registerCoverage(program2) {
|
|
|
13164
13323
|
|
|
13165
13324
|
// src/commands/merge-driver.ts
|
|
13166
13325
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
13167
|
-
import { readFileSync, writeFileSync, existsSync as
|
|
13168
|
-
import
|
|
13326
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync49 } from "fs";
|
|
13327
|
+
import path46 from "path";
|
|
13169
13328
|
import "commander";
|
|
13170
13329
|
import { findProjectRoot as findProjectRoot44, mergeMemoryVersions } from "@hivelore/core";
|
|
13171
13330
|
var GITATTRIBUTES_MARK = "# Hivelore merge driver";
|
|
@@ -13178,8 +13337,8 @@ function registerMergeDriver(program2) {
|
|
|
13178
13337
|
const cmd = program2.command("merge-driver").description("Deterministic git merge driver for Hivelore memory files (kills .ai/ conflict markers)");
|
|
13179
13338
|
cmd.command("run <base> <ours> <theirs>").description("Git merge-driver entrypoint: resolve ours/theirs by frontmatter order, write into <ours>").action((base, ours, theirs) => {
|
|
13180
13339
|
try {
|
|
13181
|
-
const oursContent =
|
|
13182
|
-
const theirsContent =
|
|
13340
|
+
const oursContent = readFileSync2(ours, "utf8");
|
|
13341
|
+
const theirsContent = readFileSync2(theirs, "utf8");
|
|
13183
13342
|
const result = mergeMemoryVersions(oursContent, theirsContent);
|
|
13184
13343
|
if (result.content !== oursContent) writeFileSync(ours, result.content, "utf8");
|
|
13185
13344
|
process.exit(0);
|
|
@@ -13197,8 +13356,8 @@ function registerMergeDriver(program2) {
|
|
|
13197
13356
|
process.exitCode = 1;
|
|
13198
13357
|
return;
|
|
13199
13358
|
}
|
|
13200
|
-
const gaPath =
|
|
13201
|
-
let content =
|
|
13359
|
+
const gaPath = path46.join(root, ".gitattributes");
|
|
13360
|
+
let content = existsSync49(gaPath) ? readFileSync2(gaPath, "utf8") : "";
|
|
13202
13361
|
if (!content.includes(GITATTRIBUTES_MARK)) {
|
|
13203
13362
|
if (content.length > 0 && !content.endsWith("\n")) content += "\n";
|
|
13204
13363
|
content += GITATTRIBUTES_BLOCK + "\n";
|
|
@@ -13212,8 +13371,8 @@ function registerMergeDriver(program2) {
|
|
|
13212
13371
|
}
|
|
13213
13372
|
|
|
13214
13373
|
// src/commands/bridges.ts
|
|
13215
|
-
import { existsSync as
|
|
13216
|
-
import
|
|
13374
|
+
import { existsSync as existsSync50 } from "fs";
|
|
13375
|
+
import path47 from "path";
|
|
13217
13376
|
import "commander";
|
|
13218
13377
|
import {
|
|
13219
13378
|
findProjectRoot as findProjectRoot45,
|
|
@@ -13234,7 +13393,7 @@ function registerBridges(program2) {
|
|
|
13234
13393
|
const root = findProjectRoot45(opts.dir);
|
|
13235
13394
|
const paths = resolveHaivePaths40(root);
|
|
13236
13395
|
const dryRun = opts.dryRun === true;
|
|
13237
|
-
if (!
|
|
13396
|
+
if (!existsSync50(paths.memoriesDir)) {
|
|
13238
13397
|
ui.warn(`No .ai/memories at ${root}. Run \`hivelore init\` first.`);
|
|
13239
13398
|
process.exitCode = 1;
|
|
13240
13399
|
return;
|
|
@@ -13253,7 +13412,7 @@ function registerBridges(program2) {
|
|
|
13253
13412
|
targets = BRIDGE_TARGETS4;
|
|
13254
13413
|
} else {
|
|
13255
13414
|
targets = BRIDGE_TARGETS4.filter(
|
|
13256
|
-
(t) =>
|
|
13415
|
+
(t) => existsSync50(path47.join(root, BRIDGE_TARGET_PATH3[t]))
|
|
13257
13416
|
);
|
|
13258
13417
|
if (targets.length === 0) {
|
|
13259
13418
|
ui.info(
|
|
@@ -13301,7 +13460,7 @@ function registerBridges(program2) {
|
|
|
13301
13460
|
|
|
13302
13461
|
// src/index.ts
|
|
13303
13462
|
var program = new Command48();
|
|
13304
|
-
program.name("hivelore").description("Hivelore - the deterministic policy gate for agent-written code (rules live as repo-native team memory)").version("0.
|
|
13463
|
+
program.name("hivelore").description("Hivelore - the deterministic policy gate for agent-written code (rules live as repo-native team memory)").version("0.39.1").option("--advanced", "show maintenance and experimental commands in help").showSuggestionAfterError(true);
|
|
13305
13464
|
registerInit(program);
|
|
13306
13465
|
registerResolveProject(program);
|
|
13307
13466
|
registerEnforce(program);
|