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