@hivelore/cli 0.37.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-VLRQ4MRO.js → chunk-I4VELI5K.js} +366 -188
- package/dist/chunk-I4VELI5K.js.map +1 -0
- package/dist/index.js +366 -232
- package/dist/index.js.map +1 -1
- package/dist/{server-J6TDFG2C.js → server-47VOVJJT.js} +10 -4
- package/package.json +4 -4
- package/dist/chunk-VLRQ4MRO.js.map +0 -1
- /package/dist/{server-J6TDFG2C.js.map → server-47VOVJJT.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
antiPatternsCheck,
|
|
4
4
|
codeMapTool,
|
|
5
5
|
codeSearch,
|
|
6
|
+
detectTestFrameworksForAnchors,
|
|
6
7
|
getBriefing,
|
|
7
8
|
getRecap,
|
|
8
9
|
memRelevantTo,
|
|
@@ -10,7 +11,7 @@ import {
|
|
|
10
11
|
preCommitCheck,
|
|
11
12
|
readPresumedCorrectTargets,
|
|
12
13
|
runHaiveMcpStdio
|
|
13
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-I4VELI5K.js";
|
|
14
15
|
import {
|
|
15
16
|
registerMemoryPending
|
|
16
17
|
} from "./chunk-OYJKHD22.js";
|
|
@@ -208,7 +209,7 @@ async function getHotFiles(root, daysBack, maxHotFiles, filePaths) {
|
|
|
208
209
|
if (!f) continue;
|
|
209
210
|
counts.set(f, (counts.get(f) ?? 0) + 1);
|
|
210
211
|
}
|
|
211
|
-
let entries = [...counts.entries()].map(([
|
|
212
|
+
let entries = [...counts.entries()].map(([path48, changes]) => ({ path: path48, changes }));
|
|
212
213
|
const lowerPaths = filePaths.map((p) => p.toLowerCase());
|
|
213
214
|
if (lowerPaths.length > 0) {
|
|
214
215
|
entries = entries.filter((e) => lowerPaths.some((p) => e.path.toLowerCase().includes(p)));
|
|
@@ -3755,7 +3756,7 @@ ${SEED_FOOTER(stack)}` });
|
|
|
3755
3756
|
|
|
3756
3757
|
// src/commands/init.ts
|
|
3757
3758
|
var execFileAsync = promisify2(execFile2);
|
|
3758
|
-
var HAIVE_GITHUB_ACTION_REF = `v${"0.
|
|
3759
|
+
var HAIVE_GITHUB_ACTION_REF = `v${"0.39.0"}`;
|
|
3759
3760
|
var PROJECT_CONTEXT_TEMPLATE = `# Project context
|
|
3760
3761
|
|
|
3761
3762
|
> Generated by \`hivelore init\`. Run \`hivelore init --bootstrap\` to auto-fill from your codebase,
|
|
@@ -6022,7 +6023,7 @@ function parseCsv4(raw) {
|
|
|
6022
6023
|
function registerMemoryTried(memory2) {
|
|
6023
6024
|
memory2.command("tried").description(
|
|
6024
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'
|
|
6025
|
-
).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) => {
|
|
6026
6027
|
const root = findProjectRoot15(opts.dir);
|
|
6027
6028
|
const paths = resolveHaivePaths14(root);
|
|
6028
6029
|
if (!existsSync20(paths.haiveDir)) {
|
|
@@ -6039,7 +6040,8 @@ function registerMemoryTried(memory2) {
|
|
|
6039
6040
|
why_failed: opts.whyFailed,
|
|
6040
6041
|
instead: opts.instead,
|
|
6041
6042
|
// "shared" is a legacy MemoryScope alias not accepted by mem_tried — normalize to team.
|
|
6042
|
-
|
|
6043
|
+
// Undefined stays undefined: mem_tried defaults it (team when a sensor is attached).
|
|
6044
|
+
scope: opts.scope === "shared" ? "team" : opts.scope,
|
|
6043
6045
|
module: opts.module,
|
|
6044
6046
|
tags: parseCsv4(opts.tags),
|
|
6045
6047
|
paths: parseCsv4(opts.paths ?? opts.files),
|
|
@@ -6072,7 +6074,7 @@ function registerMemoryTried(memory2) {
|
|
|
6072
6074
|
return;
|
|
6073
6075
|
}
|
|
6074
6076
|
ui.success(`Recorded: ${path21.relative(root, result.file_path)}`);
|
|
6075
|
-
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)`);
|
|
6076
6078
|
if (result.sensor_result) {
|
|
6077
6079
|
if (result.sensor_result.accepted) {
|
|
6078
6080
|
ui.success(`Loop closed: sensor attached (${result.sensor_result.severity}) \u2014 the gate now refuses a repeat deterministically.`);
|
|
@@ -8574,9 +8576,9 @@ function parseDays(input) {
|
|
|
8574
8576
|
}
|
|
8575
8577
|
|
|
8576
8578
|
// src/commands/doctor.ts
|
|
8577
|
-
import { existsSync as
|
|
8579
|
+
import { existsSync as existsSync40, statSync as statSync3 } from "fs";
|
|
8578
8580
|
import { readFile as readFile16, stat, writeFile as writeFile23 } from "fs/promises";
|
|
8579
|
-
import
|
|
8581
|
+
import path37 from "path";
|
|
8580
8582
|
import { execFileSync, execSync } from "child_process";
|
|
8581
8583
|
import "commander";
|
|
8582
8584
|
import {
|
|
@@ -8598,6 +8600,85 @@ import {
|
|
|
8598
8600
|
readUsageEvents as readUsageEvents3,
|
|
8599
8601
|
resolveHaivePaths as resolveHaivePaths33
|
|
8600
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
|
|
8601
8682
|
var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
|
|
8602
8683
|
function registerDoctor(program2) {
|
|
8603
8684
|
program2.command("doctor").description(
|
|
@@ -8608,7 +8689,7 @@ function registerDoctor(program2) {
|
|
|
8608
8689
|
const findings = [];
|
|
8609
8690
|
const repairs = [];
|
|
8610
8691
|
const config = await loadConfig10(paths);
|
|
8611
|
-
if (!
|
|
8692
|
+
if (!existsSync40(paths.haiveDir)) {
|
|
8612
8693
|
if (opts.json) {
|
|
8613
8694
|
console.log(JSON.stringify({
|
|
8614
8695
|
initialized: false,
|
|
@@ -8633,7 +8714,7 @@ function registerDoctor(program2) {
|
|
|
8633
8714
|
})
|
|
8634
8715
|
);
|
|
8635
8716
|
}
|
|
8636
|
-
if (!
|
|
8717
|
+
if (!existsSync40(paths.projectContext)) {
|
|
8637
8718
|
findings.push({
|
|
8638
8719
|
severity: "warn",
|
|
8639
8720
|
code: "no-project-context",
|
|
@@ -8653,7 +8734,7 @@ function registerDoctor(program2) {
|
|
|
8653
8734
|
});
|
|
8654
8735
|
} else {
|
|
8655
8736
|
const referenced = extractReferencedPaths(content);
|
|
8656
|
-
const missing = referenced.filter((p) => !
|
|
8737
|
+
const missing = referenced.filter((p) => !existsSync40(path37.resolve(root, p)));
|
|
8657
8738
|
const grounded = referenced.length - missing.length;
|
|
8658
8739
|
if (referenced.length >= 3 && grounded / referenced.length < 0.5) {
|
|
8659
8740
|
findings.push({
|
|
@@ -8675,11 +8756,11 @@ function registerDoctor(program2) {
|
|
|
8675
8756
|
});
|
|
8676
8757
|
}
|
|
8677
8758
|
}
|
|
8678
|
-
const memoriesDetailed =
|
|
8759
|
+
const memoriesDetailed = existsSync40(paths.memoriesDir) ? await loadMemoriesFromDirDetailed(paths.memoriesDir) : { loaded: [], invalid: [] };
|
|
8679
8760
|
const memories = memoriesDetailed.loaded;
|
|
8680
8761
|
const now = Date.now();
|
|
8681
8762
|
if (memoriesDetailed.invalid.length > 0) {
|
|
8682
|
-
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("; ");
|
|
8683
8764
|
findings.push({
|
|
8684
8765
|
severity: "warn",
|
|
8685
8766
|
code: "invalid-memory-files",
|
|
@@ -8820,8 +8901,8 @@ function registerDoctor(program2) {
|
|
|
8820
8901
|
const anchorPaths = s.paths.length > 0 ? s.paths : m.memory.frontmatter.anchor.paths;
|
|
8821
8902
|
const targets = [];
|
|
8822
8903
|
for (const rel of anchorPaths) {
|
|
8823
|
-
const abs =
|
|
8824
|
-
if (!
|
|
8904
|
+
const abs = path37.resolve(root, rel);
|
|
8905
|
+
if (!existsSync40(abs)) continue;
|
|
8825
8906
|
try {
|
|
8826
8907
|
targets.push({ path: rel, content: await readFile16(abs, "utf8") });
|
|
8827
8908
|
} catch {
|
|
@@ -8896,6 +8977,18 @@ function registerDoctor(program2) {
|
|
|
8896
8977
|
}
|
|
8897
8978
|
findings.push(...await collectHarnessCoverageFindings(codeMap, memories));
|
|
8898
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
|
+
}
|
|
8899
8992
|
const events = await readUsageEvents3(paths);
|
|
8900
8993
|
if (events.length === 0) {
|
|
8901
8994
|
findings.push({
|
|
@@ -8932,9 +9025,9 @@ function registerDoctor(program2) {
|
|
|
8932
9025
|
}
|
|
8933
9026
|
}
|
|
8934
9027
|
if (config.enforcement?.requireBriefingFirst) {
|
|
8935
|
-
const claudeSettings =
|
|
9028
|
+
const claudeSettings = path37.join(root, ".claude", "settings.local.json");
|
|
8936
9029
|
let hasClaudeEnforcement = false;
|
|
8937
|
-
if (
|
|
9030
|
+
if (existsSync40(claudeSettings)) {
|
|
8938
9031
|
try {
|
|
8939
9032
|
const { readFile: readFile24 } = await import("fs/promises");
|
|
8940
9033
|
const raw = await readFile24(claudeSettings, "utf8");
|
|
@@ -8960,7 +9053,7 @@ function registerDoctor(program2) {
|
|
|
8960
9053
|
fix: "Edit .ai/haive.config.json: set autoSessionEnd: true (or re-run `hivelore init` without --manual)."
|
|
8961
9054
|
});
|
|
8962
9055
|
}
|
|
8963
|
-
findings.push(...await collectInstallFindings(root, "0.
|
|
9056
|
+
findings.push(...await collectInstallFindings(root, "0.39.0"));
|
|
8964
9057
|
findings.push(...await collectToolchainFindings(root));
|
|
8965
9058
|
try {
|
|
8966
9059
|
const legacyRaw = execSync("haive-mcp --version", {
|
|
@@ -8968,7 +9061,7 @@ function registerDoctor(program2) {
|
|
|
8968
9061
|
timeout: 3e3,
|
|
8969
9062
|
stdio: ["ignore", "pipe", "ignore"]
|
|
8970
9063
|
}).trim();
|
|
8971
|
-
const cliVersion = "0.
|
|
9064
|
+
const cliVersion = "0.39.0";
|
|
8972
9065
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
8973
9066
|
findings.push({
|
|
8974
9067
|
severity: "warn",
|
|
@@ -8984,17 +9077,17 @@ npm uninstall -g @hivelore/mcp`
|
|
|
8984
9077
|
}
|
|
8985
9078
|
{
|
|
8986
9079
|
const configPaths = [
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
9080
|
+
path37.join(root, ".mcp.json"),
|
|
9081
|
+
path37.join(root, ".cursor", "mcp.json"),
|
|
9082
|
+
path37.join(root, ".vscode", "mcp.json")
|
|
8990
9083
|
];
|
|
8991
9084
|
const staleConfigs = [];
|
|
8992
9085
|
for (const cfgPath of configPaths) {
|
|
8993
|
-
if (!
|
|
9086
|
+
if (!existsSync40(cfgPath)) continue;
|
|
8994
9087
|
try {
|
|
8995
9088
|
const raw = await readFile16(cfgPath, "utf8");
|
|
8996
9089
|
if (raw.includes('"haive-mcp"') || raw.includes("'haive-mcp'")) {
|
|
8997
|
-
staleConfigs.push(
|
|
9090
|
+
staleConfigs.push(path37.relative(root, cfgPath));
|
|
8998
9091
|
if (opts.fix && !opts.dryRun) {
|
|
8999
9092
|
const updated = raw.replace(/"command"\s*:\s*"haive-mcp"/g, '"command": "hivelore"').replace(/"args"\s*:\s*\[\]/g, '"args": ["mcp", "--stdio"]');
|
|
9000
9093
|
await writeFile23(cfgPath, updated, "utf8");
|
|
@@ -9287,8 +9380,8 @@ which -a hivelore haive`
|
|
|
9287
9380
|
const missingBins = /* @__PURE__ */ new Map();
|
|
9288
9381
|
const staleBins = /* @__PURE__ */ new Map();
|
|
9289
9382
|
for (const rel of integrationFiles) {
|
|
9290
|
-
const file =
|
|
9291
|
-
if (!
|
|
9383
|
+
const file = path37.join(root, rel);
|
|
9384
|
+
if (!existsSync40(file)) continue;
|
|
9292
9385
|
const text = await readFile16(file, "utf8").catch(() => "");
|
|
9293
9386
|
for (const bin of extractAbsoluteHaiveBins(text)) {
|
|
9294
9387
|
const version = versionForBinary(bin);
|
|
@@ -9321,7 +9414,7 @@ which -a hivelore haive`
|
|
|
9321
9414
|
async function collectToolchainFindings(root) {
|
|
9322
9415
|
const findings = [];
|
|
9323
9416
|
const pkg = await readJson(
|
|
9324
|
-
|
|
9417
|
+
path37.join(root, "package.json")
|
|
9325
9418
|
);
|
|
9326
9419
|
const wantsPnpm = pkg?.packageManager?.startsWith("pnpm@") || Object.values(pkg?.scripts ?? {}).some((script) => /\bpnpm\b/.test(script));
|
|
9327
9420
|
if (!wantsPnpm) return findings;
|
|
@@ -9340,10 +9433,10 @@ async function collectToolchainFindings(root) {
|
|
|
9340
9433
|
}
|
|
9341
9434
|
async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
9342
9435
|
const findings = [];
|
|
9343
|
-
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 ?? "");
|
|
9344
9437
|
if (!isHaiveWorkspace) return findings;
|
|
9345
|
-
const cliDist =
|
|
9346
|
-
if (!
|
|
9438
|
+
const cliDist = path37.join(root, "packages/cli/dist/index.js");
|
|
9439
|
+
if (!existsSync40(cliDist)) {
|
|
9347
9440
|
findings.push({
|
|
9348
9441
|
severity: "warn",
|
|
9349
9442
|
code: "workspace-dist-missing",
|
|
@@ -9367,10 +9460,10 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
|
9367
9460
|
"packages/core/src/index.ts",
|
|
9368
9461
|
"packages/mcp/src/server.ts",
|
|
9369
9462
|
"packages/cli/src/index.ts"
|
|
9370
|
-
].map((rel) =>
|
|
9463
|
+
].map((rel) => path37.join(root, rel)).filter(existsSync40);
|
|
9371
9464
|
if (sourceFiles.length > 0) {
|
|
9372
|
-
const distMtime =
|
|
9373
|
-
const newestSource = Math.max(...sourceFiles.map((file) =>
|
|
9465
|
+
const distMtime = statSync3(cliDist).mtimeMs;
|
|
9466
|
+
const newestSource = Math.max(...sourceFiles.map((file) => statSync3(file).mtimeMs));
|
|
9374
9467
|
if (newestSource > distMtime + 1e3) {
|
|
9375
9468
|
findings.push({
|
|
9376
9469
|
severity: "info",
|
|
@@ -9385,7 +9478,7 @@ async function collectDistFreshnessFindings(root, expectedVersion) {
|
|
|
9385
9478
|
}
|
|
9386
9479
|
async function collectWorkspaceVersionFindings(root, expectedVersion) {
|
|
9387
9480
|
const findings = [];
|
|
9388
|
-
const rootPkg = await readJson(
|
|
9481
|
+
const rootPkg = await readJson(path37.join(root, "package.json"));
|
|
9389
9482
|
const workspacePackages = [
|
|
9390
9483
|
"packages/core/package.json",
|
|
9391
9484
|
"packages/embeddings/package.json",
|
|
@@ -9394,7 +9487,7 @@ async function collectWorkspaceVersionFindings(root, expectedVersion) {
|
|
|
9394
9487
|
];
|
|
9395
9488
|
const existing = (await Promise.all(workspacePackages.map(async (rel) => ({
|
|
9396
9489
|
rel,
|
|
9397
|
-
pkg: await readJson(
|
|
9490
|
+
pkg: await readJson(path37.join(root, rel))
|
|
9398
9491
|
})))).filter((item) => item.pkg);
|
|
9399
9492
|
const isHaiveWorkspace = rootPkg?.name === "hivelore-monorepo" || rootPkg?.name === "haive-monorepo" || existing.some((item) => item.pkg?.name?.startsWith("@hivelore/"));
|
|
9400
9493
|
if (!isHaiveWorkspace) return findings;
|
|
@@ -9456,7 +9549,7 @@ function collectGlobalHivemoduleFindings(expectedVersion) {
|
|
|
9456
9549
|
}
|
|
9457
9550
|
}
|
|
9458
9551
|
async function readJson(file) {
|
|
9459
|
-
if (!
|
|
9552
|
+
if (!existsSync40(file)) return null;
|
|
9460
9553
|
try {
|
|
9461
9554
|
return JSON.parse(await readFile16(file, "utf8"));
|
|
9462
9555
|
} catch {
|
|
@@ -9521,7 +9614,7 @@ function extractAbsoluteHaiveBins(text) {
|
|
|
9521
9614
|
const p = match[2];
|
|
9522
9615
|
if (!p) continue;
|
|
9523
9616
|
try {
|
|
9524
|
-
if (
|
|
9617
|
+
if (statSync3(p).isDirectory()) continue;
|
|
9525
9618
|
} catch {
|
|
9526
9619
|
}
|
|
9527
9620
|
out.add(p);
|
|
@@ -9685,23 +9778,23 @@ function runCommand(cmd, args, cwd) {
|
|
|
9685
9778
|
}
|
|
9686
9779
|
|
|
9687
9780
|
// src/commands/resolve-project.ts
|
|
9688
|
-
import
|
|
9781
|
+
import path38 from "path";
|
|
9689
9782
|
import "commander";
|
|
9690
9783
|
import { resolveProjectInfo } from "@hivelore/core";
|
|
9691
9784
|
function registerResolveProject(program2) {
|
|
9692
9785
|
program2.command("resolve-project").description(
|
|
9693
9786
|
"Print JSON for Hivelore project root resolution (HAIVE_PROJECT_ROOT, markers, .ai layout)."
|
|
9694
9787
|
).option("-d, --dir <dir>", "working directory", process.cwd()).action((opts) => {
|
|
9695
|
-
const info = resolveProjectInfo({ cwd:
|
|
9788
|
+
const info = resolveProjectInfo({ cwd: path38.resolve(opts.dir) });
|
|
9696
9789
|
console.log(JSON.stringify({ ok: true, info }, null, 2));
|
|
9697
9790
|
});
|
|
9698
9791
|
}
|
|
9699
9792
|
|
|
9700
9793
|
// src/commands/enforce.ts
|
|
9701
9794
|
import { execFile as execFile5, execFileSync as execFileSync2, spawn as spawn4 } from "child_process";
|
|
9702
|
-
import { existsSync as
|
|
9795
|
+
import { existsSync as existsSync42, statSync as statSync4 } from "fs";
|
|
9703
9796
|
import { chmod, mkdir as mkdir16, readFile as readFile18, readdir as readdir4, rm as rm2, writeFile as writeFile25 } from "fs/promises";
|
|
9704
|
-
import
|
|
9797
|
+
import path40 from "path";
|
|
9705
9798
|
import { promisify as promisify5 } from "util";
|
|
9706
9799
|
import "commander";
|
|
9707
9800
|
import {
|
|
@@ -9710,6 +9803,7 @@ import {
|
|
|
9710
9803
|
assessSensorHealth as assessSensorHealth3,
|
|
9711
9804
|
sensorPromotedAtMap as sensorPromotedAtMap3,
|
|
9712
9805
|
assessBootstrapState,
|
|
9806
|
+
detectSensorWeakening,
|
|
9713
9807
|
isSensorScannablePath,
|
|
9714
9808
|
findProjectRoot as findProjectRoot37,
|
|
9715
9809
|
loadCodeMap as loadCodeMap7,
|
|
@@ -9721,7 +9815,7 @@ import {
|
|
|
9721
9815
|
isRetiredMemory as isRetiredMemory2,
|
|
9722
9816
|
loadConfig as loadConfig12,
|
|
9723
9817
|
detectAgentContext,
|
|
9724
|
-
loadMemoriesFromDir as
|
|
9818
|
+
loadMemoriesFromDir as loadMemoriesFromDir15,
|
|
9725
9819
|
loadSensorLedger as loadSensorLedger3,
|
|
9726
9820
|
memoryMatchesAnchorPaths as memoryMatchesAnchorPaths2,
|
|
9727
9821
|
readRecentBriefingMarker,
|
|
@@ -9740,9 +9834,9 @@ import {
|
|
|
9740
9834
|
} from "@hivelore/core";
|
|
9741
9835
|
|
|
9742
9836
|
// src/utils/claude-hooks.ts
|
|
9743
|
-
import { existsSync as
|
|
9837
|
+
import { existsSync as existsSync41 } from "fs";
|
|
9744
9838
|
import { mkdir as mkdir15, readFile as readFile17, writeFile as writeFile24 } from "fs/promises";
|
|
9745
|
-
import
|
|
9839
|
+
import path39 from "path";
|
|
9746
9840
|
var HAIVE_HOOK_TAG = "haive-enforcement";
|
|
9747
9841
|
var POST_TOOL_USE_GROUP = {
|
|
9748
9842
|
matcher: "Edit|Write|Bash",
|
|
@@ -9828,7 +9922,7 @@ function unpatchClaudeSettings(input) {
|
|
|
9828
9922
|
async function installClaudeHooksAtPath(settingsPath) {
|
|
9829
9923
|
let raw = null;
|
|
9830
9924
|
let created = false;
|
|
9831
|
-
if (
|
|
9925
|
+
if (existsSync41(settingsPath)) {
|
|
9832
9926
|
try {
|
|
9833
9927
|
raw = JSON.parse(await readFile17(settingsPath, "utf8"));
|
|
9834
9928
|
} catch {
|
|
@@ -9838,12 +9932,12 @@ async function installClaudeHooksAtPath(settingsPath) {
|
|
|
9838
9932
|
created = true;
|
|
9839
9933
|
}
|
|
9840
9934
|
const patched = patchClaudeSettings(raw);
|
|
9841
|
-
await mkdir15(
|
|
9935
|
+
await mkdir15(path39.dirname(settingsPath), { recursive: true });
|
|
9842
9936
|
await writeFile24(settingsPath, JSON.stringify(patched, null, 2) + "\n", "utf8");
|
|
9843
9937
|
return { settingsPath, created };
|
|
9844
9938
|
}
|
|
9845
9939
|
async function uninstallClaudeHooksAtPath(settingsPath) {
|
|
9846
|
-
if (!
|
|
9940
|
+
if (!existsSync41(settingsPath)) {
|
|
9847
9941
|
return { settingsPath, created: false };
|
|
9848
9942
|
}
|
|
9849
9943
|
const raw = JSON.parse(await readFile17(settingsPath, "utf8"));
|
|
@@ -9854,9 +9948,9 @@ async function uninstallClaudeHooksAtPath(settingsPath) {
|
|
|
9854
9948
|
function defaultClaudeSettingsPath(scope, projectRoot) {
|
|
9855
9949
|
if (scope === "user") {
|
|
9856
9950
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
9857
|
-
return
|
|
9951
|
+
return path39.join(home, ".claude", "settings.json");
|
|
9858
9952
|
}
|
|
9859
|
-
return
|
|
9953
|
+
return path39.join(projectRoot, ".claude", "settings.local.json");
|
|
9860
9954
|
}
|
|
9861
9955
|
|
|
9862
9956
|
// src/utils/command-sensors.ts
|
|
@@ -10043,7 +10137,7 @@ function registerEnforce(program2) {
|
|
|
10043
10137
|
ui.success(`Removed Hivelore hooks from ${result.settingsPath}`);
|
|
10044
10138
|
} else {
|
|
10045
10139
|
const result = await installClaudeHooksAtPath(settingsPath);
|
|
10046
|
-
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})`);
|
|
10047
10141
|
}
|
|
10048
10142
|
} catch (err) {
|
|
10049
10143
|
ui.warn(`Claude Code hooks not ${opts.removeClaude ? "removed" : "installed"}: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -10065,19 +10159,19 @@ function registerEnforce(program2) {
|
|
|
10065
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) => {
|
|
10066
10160
|
const root = findProjectRoot37(opts.dir);
|
|
10067
10161
|
const paths = resolveHaivePaths35(root);
|
|
10068
|
-
const cacheDir =
|
|
10069
|
-
if (
|
|
10070
|
-
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)`);
|
|
10071
10165
|
else {
|
|
10072
10166
|
const removed = await cleanupCacheDir(cacheDir);
|
|
10073
|
-
ui.success(`cleaned ${
|
|
10167
|
+
ui.success(`cleaned ${path40.relative(root, cacheDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
|
|
10074
10168
|
}
|
|
10075
10169
|
}
|
|
10076
|
-
if (
|
|
10077
|
-
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)`);
|
|
10078
10172
|
else {
|
|
10079
10173
|
const removed = await cleanupRuntimeDir(paths.runtimeDir);
|
|
10080
|
-
ui.success(`cleaned ${
|
|
10174
|
+
ui.success(`cleaned ${path40.relative(root, paths.runtimeDir)}${removed > 0 ? ` (${removed} item${removed === 1 ? "" : "s"} removed)` : ""}`);
|
|
10081
10175
|
}
|
|
10082
10176
|
}
|
|
10083
10177
|
});
|
|
@@ -10121,7 +10215,7 @@ function registerEnforce(program2) {
|
|
|
10121
10215
|
const root = resolveRoot(opts.dir, payload);
|
|
10122
10216
|
if (!root) return;
|
|
10123
10217
|
const paths = resolveHaivePaths35(root);
|
|
10124
|
-
if (!
|
|
10218
|
+
if (!existsSync42(paths.haiveDir)) return;
|
|
10125
10219
|
await mkdir16(paths.runtimeDir, { recursive: true });
|
|
10126
10220
|
const sessionId = opts.sessionId ?? payload.session_id;
|
|
10127
10221
|
const task = opts.task ?? payload.prompt ?? "Start an AI coding session in this Hivelore-initialized project.";
|
|
@@ -10184,7 +10278,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
10184
10278
|
const root = resolveRoot(opts.dir, payload);
|
|
10185
10279
|
if (!root) return;
|
|
10186
10280
|
const paths = resolveHaivePaths35(root);
|
|
10187
|
-
if (!
|
|
10281
|
+
if (!existsSync42(paths.haiveDir)) return;
|
|
10188
10282
|
if (!isWriteLikeTool(payload)) return;
|
|
10189
10283
|
const config = await loadConfig12(paths);
|
|
10190
10284
|
if (config.enforcement?.requireBriefingFirst === false) return;
|
|
@@ -10246,10 +10340,26 @@ function emitPreToolUseContext(text) {
|
|
|
10246
10340
|
})
|
|
10247
10341
|
);
|
|
10248
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
|
+
}
|
|
10249
10359
|
async function buildFinishReport(dir) {
|
|
10250
10360
|
const root = findProjectRoot37(dir);
|
|
10251
10361
|
const paths = resolveHaivePaths35(root);
|
|
10252
|
-
const initialized =
|
|
10362
|
+
const initialized = existsSync42(paths.haiveDir);
|
|
10253
10363
|
const config = initialized ? await loadConfig12(paths) : {};
|
|
10254
10364
|
const mode = config.enforcement?.mode ?? "strict";
|
|
10255
10365
|
const findings = [];
|
|
@@ -10270,6 +10380,7 @@ async function buildFinishReport(dir) {
|
|
|
10270
10380
|
});
|
|
10271
10381
|
}
|
|
10272
10382
|
findings.push(...await checkFailureCapture(paths, config));
|
|
10383
|
+
findings.push(...await checkPostIncidentScaffolds(paths));
|
|
10273
10384
|
findings.push(...await checkBootstrapComplete(paths, config, true));
|
|
10274
10385
|
const status = await getGitSyncStatus(root);
|
|
10275
10386
|
if (!status.available) {
|
|
@@ -10441,8 +10552,8 @@ async function buildFinishReport(dir) {
|
|
|
10441
10552
|
async function checkFailureCapture(paths, config) {
|
|
10442
10553
|
const gate = config.enforcement?.failureCaptureGate ?? "warn";
|
|
10443
10554
|
if (gate === "off") return [];
|
|
10444
|
-
const obsFile =
|
|
10445
|
-
if (!
|
|
10555
|
+
const obsFile = path40.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
10556
|
+
if (!existsSync42(obsFile)) return [];
|
|
10446
10557
|
const failures = [];
|
|
10447
10558
|
try {
|
|
10448
10559
|
const raw = await readFile18(obsFile, "utf8");
|
|
@@ -10459,7 +10570,7 @@ async function checkFailureCapture(paths, config) {
|
|
|
10459
10570
|
return [];
|
|
10460
10571
|
}
|
|
10461
10572
|
if (failures.length === 0) return [];
|
|
10462
|
-
const memories =
|
|
10573
|
+
const memories = existsSync42(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
|
|
10463
10574
|
const captureTimes = memories.filter(({ memory: memory2 }) => ["attempt", "gotcha"].includes(memory2.frontmatter.type)).map(({ memory: memory2 }) => memory2.frontmatter.created_at);
|
|
10464
10575
|
const uncaptured = findUncapturedFailures(failures, captureTimes);
|
|
10465
10576
|
if (uncaptured.length === 0) {
|
|
@@ -10494,7 +10605,7 @@ function finishReport(root, initialized, mode, findings, config) {
|
|
|
10494
10605
|
async function runWithEnforcement(command, args, opts) {
|
|
10495
10606
|
const root = findProjectRoot37(opts.dir);
|
|
10496
10607
|
const paths = resolveHaivePaths35(root);
|
|
10497
|
-
if (!
|
|
10608
|
+
if (!existsSync42(paths.haiveDir)) {
|
|
10498
10609
|
ui.error(`No .ai/ found at ${root}. Run \`hivelore init\` first.`);
|
|
10499
10610
|
process.exit(1);
|
|
10500
10611
|
}
|
|
@@ -10513,7 +10624,7 @@ async function runWithEnforcement(command, args, opts) {
|
|
|
10513
10624
|
process.exit(2);
|
|
10514
10625
|
}
|
|
10515
10626
|
ui.info(`Hivelore briefing marker created for wrapped agent session: ${sessionId}`);
|
|
10516
|
-
ui.info(`Briefing written to ${
|
|
10627
|
+
ui.info(`Briefing written to ${path40.relative(root, briefingFile)} and exported as HAIVE_BRIEFING_FILE`);
|
|
10517
10628
|
const child = spawn4(command, args, {
|
|
10518
10629
|
cwd: root,
|
|
10519
10630
|
stdio: "inherit",
|
|
@@ -10565,9 +10676,9 @@ async function writeWrapperBriefing(paths, sessionId, task) {
|
|
|
10565
10676
|
source: "haive-run",
|
|
10566
10677
|
memoryIds: briefing.memories.map((m) => m.id)
|
|
10567
10678
|
});
|
|
10568
|
-
const dir =
|
|
10679
|
+
const dir = path40.join(paths.runtimeDir, "enforcement", "briefings");
|
|
10569
10680
|
await mkdir16(dir, { recursive: true });
|
|
10570
|
-
const file =
|
|
10681
|
+
const file = path40.join(dir, `${sessionId}.md`);
|
|
10571
10682
|
const parts = [
|
|
10572
10683
|
"# Hivelore Briefing",
|
|
10573
10684
|
"",
|
|
@@ -10605,7 +10716,7 @@ async function checkBootstrapComplete(paths, config, productionCodeChanged) {
|
|
|
10605
10716
|
projectContextRaw = await readFile18(paths.projectContext, "utf8");
|
|
10606
10717
|
} catch {
|
|
10607
10718
|
}
|
|
10608
|
-
const memories =
|
|
10719
|
+
const memories = existsSync42(paths.memoriesDir) ? await loadMemoriesFromDir15(paths.memoriesDir) : [];
|
|
10609
10720
|
const codeMap = await loadCodeMap7(paths);
|
|
10610
10721
|
const codeFiles = codeMap ? Object.keys(codeMap.files) : [];
|
|
10611
10722
|
let existingModules = [];
|
|
@@ -10637,7 +10748,7 @@ ${renderBootstrapChecklist(assessment)}`,
|
|
|
10637
10748
|
async function buildEnforcementReport(dir, stage, sessionId) {
|
|
10638
10749
|
const root = findProjectRoot37(dir);
|
|
10639
10750
|
const paths = resolveHaivePaths35(root);
|
|
10640
|
-
const initialized =
|
|
10751
|
+
const initialized = existsSync42(paths.haiveDir);
|
|
10641
10752
|
const config = initialized ? await loadConfig12(paths) : {};
|
|
10642
10753
|
if (initialized) {
|
|
10643
10754
|
await applyLightweightRepairs(root, paths);
|
|
@@ -10671,7 +10782,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
10671
10782
|
findings: [{ severity: "info", code: "enforcement-off", message: "Hivelore enforcement is disabled." }]
|
|
10672
10783
|
});
|
|
10673
10784
|
}
|
|
10674
|
-
findings.push(...await inspectIntegrationVersions(root, "0.
|
|
10785
|
+
findings.push(...await inspectIntegrationVersions(root, "0.39.0"));
|
|
10675
10786
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
10676
10787
|
const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
|
|
10677
10788
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent Hivelore briefing marker exists." } : {
|
|
@@ -10742,10 +10853,14 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
10742
10853
|
}
|
|
10743
10854
|
const score = buildScore(effectiveFindings, config.enforcement?.scoreThreshold);
|
|
10744
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);
|
|
10745
10860
|
effectiveFindings = [...effectiveFindings, {
|
|
10746
10861
|
severity: "error",
|
|
10747
10862
|
code: "enforcement-score-below-threshold",
|
|
10748
|
-
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(", ")}` : "") + ".",
|
|
10749
10864
|
fix: "Load the relevant briefing, address policy findings, then rerun `hivelore enforce check`.",
|
|
10750
10865
|
impact: 0
|
|
10751
10866
|
}];
|
|
@@ -10786,8 +10901,8 @@ function withCategories(report) {
|
|
|
10786
10901
|
async function hasRecentSessionRecap(paths) {
|
|
10787
10902
|
const handoffAge = await handoffAgeMs(paths.root);
|
|
10788
10903
|
if (handoffAge !== null && handoffAge < SESSION_RECAP_TTL_MS) return true;
|
|
10789
|
-
if (!
|
|
10790
|
-
const all = await
|
|
10904
|
+
if (!existsSync42(paths.memoriesDir)) return false;
|
|
10905
|
+
const all = await loadMemoriesFromDir15(paths.memoriesDir);
|
|
10791
10906
|
return all.some(({ memory: memory2 }) => {
|
|
10792
10907
|
const fm = memory2.frontmatter;
|
|
10793
10908
|
const freshnessDate = fm.verified_at ?? fm.created_at;
|
|
@@ -10795,8 +10910,8 @@ async function hasRecentSessionRecap(paths) {
|
|
|
10795
10910
|
});
|
|
10796
10911
|
}
|
|
10797
10912
|
async function verifyMemoryPolicy(paths, config) {
|
|
10798
|
-
if (!
|
|
10799
|
-
const all = await
|
|
10913
|
+
if (!existsSync42(paths.memoriesDir)) return [];
|
|
10914
|
+
const all = await loadMemoriesFromDir15(paths.memoriesDir);
|
|
10800
10915
|
const findings = [];
|
|
10801
10916
|
const staleImportant = [];
|
|
10802
10917
|
let verified = 0;
|
|
@@ -10833,12 +10948,12 @@ async function verifyMemoryPolicy(paths, config) {
|
|
|
10833
10948
|
return findings;
|
|
10834
10949
|
}
|
|
10835
10950
|
async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
10836
|
-
if (!
|
|
10951
|
+
if (!existsSync42(paths.memoriesDir)) return [];
|
|
10837
10952
|
const changedFiles = (await getChangedFiles(paths.root, stage)).filter((f) => !isGeneratedArtifact(f));
|
|
10838
10953
|
if (changedFiles.length === 0) {
|
|
10839
10954
|
return [{ severity: "info", code: "decision-coverage-no-changes", message: "No changed files to match against policy memories." }];
|
|
10840
10955
|
}
|
|
10841
|
-
const all = await
|
|
10956
|
+
const all = await loadMemoriesFromDir15(paths.memoriesDir);
|
|
10842
10957
|
const changedSet = new Set(changedFiles);
|
|
10843
10958
|
const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention"]);
|
|
10844
10959
|
const relevant = all.filter(({ memory: memory2 }) => {
|
|
@@ -10867,7 +10982,7 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
|
10867
10982
|
const consulted = new Set(marker?.memory_ids ?? []);
|
|
10868
10983
|
const missing = relevant.filter(({ memory: memory2, filePath }) => {
|
|
10869
10984
|
if (consulted.has(memory2.frontmatter.id)) return false;
|
|
10870
|
-
if (changedSet.has(
|
|
10985
|
+
if (changedSet.has(path40.relative(paths.root, filePath))) return false;
|
|
10871
10986
|
return true;
|
|
10872
10987
|
}).map(({ memory: memory2 }) => memory2);
|
|
10873
10988
|
if (missing.length === 0) {
|
|
@@ -10909,15 +11024,27 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
|
10909
11024
|
}];
|
|
10910
11025
|
}
|
|
10911
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
|
+
}] : [];
|
|
10912
11037
|
if (gate === "off") {
|
|
10913
|
-
return [
|
|
11038
|
+
return [
|
|
11039
|
+
{ severity: "info", code: "precommit-policy-off", message: "Anti-pattern gate is disabled (enforcement.antiPatternGate=off)." },
|
|
11040
|
+
...weakeningFindings
|
|
11041
|
+
];
|
|
10914
11042
|
}
|
|
10915
|
-
const snapshot = await getPolicyDiffSnapshot(paths.root, stage);
|
|
10916
11043
|
const touchedPaths = snapshot.paths;
|
|
10917
11044
|
if (touchedPaths.length === 0) {
|
|
10918
11045
|
const code = stage === "ci" ? "no-ci-diff-changes" : "no-staged-changes";
|
|
10919
11046
|
const message = stage === "ci" ? "No changed files found for CI policy diff." : "No staged changes found for pre-commit policy.";
|
|
10920
|
-
return [{ severity: "info", code, message }];
|
|
11047
|
+
return [{ severity: "info", code, message }, ...weakeningFindings];
|
|
10921
11048
|
}
|
|
10922
11049
|
const { block_on, anchored_blocks } = antiPatternGateParams2(gate);
|
|
10923
11050
|
const result = await preCommitCheck({
|
|
@@ -10932,7 +11059,7 @@ async function runPrecommitPolicy(paths, gate, stage) {
|
|
|
10932
11059
|
(w) => w.level === "review" && !w.reasons.includes("sensor")
|
|
10933
11060
|
);
|
|
10934
11061
|
const REVIEW_SEEN_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
10935
|
-
const reviewSeenFile =
|
|
11062
|
+
const reviewSeenFile = path40.join(paths.runtimeDir, "enforcement", "review-seen.json");
|
|
10936
11063
|
let reviewSeen = {};
|
|
10937
11064
|
try {
|
|
10938
11065
|
reviewSeen = JSON.parse(await readFile18(reviewSeenFile, "utf8"));
|
|
@@ -10959,7 +11086,7 @@ async function runPrecommitPolicy(paths, gate, stage) {
|
|
|
10959
11086
|
for (const [id, at] of Object.entries(reviewSeen)) {
|
|
10960
11087
|
if (!Number.isFinite(Date.parse(at)) || now - Date.parse(at) >= REVIEW_SEEN_TTL_MS) delete reviewSeen[id];
|
|
10961
11088
|
}
|
|
10962
|
-
await mkdir16(
|
|
11089
|
+
await mkdir16(path40.dirname(reviewSeenFile), { recursive: true });
|
|
10963
11090
|
await writeFile25(reviewSeenFile, JSON.stringify(reviewSeen, null, 2), "utf8");
|
|
10964
11091
|
} catch {
|
|
10965
11092
|
}
|
|
@@ -10972,7 +11099,8 @@ async function runPrecommitPolicy(paths, gate, stage) {
|
|
|
10972
11099
|
message: `${stage === "ci" ? "CI" : "Pre-commit"} policy passed for ${touchedPaths.length} changed file(s).`
|
|
10973
11100
|
},
|
|
10974
11101
|
...reviewFinding,
|
|
10975
|
-
...sensorFindings
|
|
11102
|
+
...sensorFindings,
|
|
11103
|
+
...weakeningFindings
|
|
10976
11104
|
];
|
|
10977
11105
|
}
|
|
10978
11106
|
const blockingWarnings = result.warnings.filter((w) => w.level === "blocking");
|
|
@@ -10987,13 +11115,14 @@ async function runPrecommitPolicy(paths, gate, stage) {
|
|
|
10987
11115
|
impact: 45
|
|
10988
11116
|
},
|
|
10989
11117
|
...reviewFinding,
|
|
10990
|
-
...sensorFindings
|
|
11118
|
+
...sensorFindings,
|
|
11119
|
+
...weakeningFindings
|
|
10991
11120
|
];
|
|
10992
11121
|
}
|
|
10993
11122
|
async function runSensorGate(paths, diff, stage) {
|
|
10994
|
-
if (!diff || !
|
|
11123
|
+
if (!diff || !existsSync42(paths.memoriesDir)) return [];
|
|
10995
11124
|
try {
|
|
10996
|
-
const loaded = await
|
|
11125
|
+
const loaded = await loadMemoriesFromDir15(paths.memoriesDir);
|
|
10997
11126
|
const scannable = loaded.map((l) => l.memory).filter((m) => Boolean(m.frontmatter.sensor) && !isRetiredMemory2(m.frontmatter, m.body));
|
|
10998
11127
|
if (scannable.length === 0) return [];
|
|
10999
11128
|
const targets = sensorTargetsFromDiff(diff).filter((t) => isSensorScannablePath(t.path));
|
|
@@ -11125,8 +11254,14 @@ command: ${run.command} (exit ${run.exit_code}, ${run.duration_ms}ms)${outputBlo
|
|
|
11125
11254
|
});
|
|
11126
11255
|
}
|
|
11127
11256
|
return findings;
|
|
11128
|
-
} catch {
|
|
11129
|
-
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
|
+
}];
|
|
11130
11265
|
}
|
|
11131
11266
|
}
|
|
11132
11267
|
async function findGeneratedArtifacts(paths) {
|
|
@@ -11159,16 +11294,16 @@ async function cleanupRuntimeDir(runtimeDir) {
|
|
|
11159
11294
|
for (const entry of entries) {
|
|
11160
11295
|
if (entry.name === ".gitignore" || entry.name === "README.md") continue;
|
|
11161
11296
|
if (entry.name === "enforcement") {
|
|
11162
|
-
removed += await cleanupEnforcementDir(
|
|
11297
|
+
removed += await cleanupEnforcementDir(path40.join(runtimeDir, entry.name));
|
|
11163
11298
|
continue;
|
|
11164
11299
|
}
|
|
11165
|
-
await rm2(
|
|
11300
|
+
await rm2(path40.join(runtimeDir, entry.name), { recursive: true, force: true });
|
|
11166
11301
|
removed++;
|
|
11167
11302
|
}
|
|
11168
|
-
await writeFile25(
|
|
11169
|
-
if (!
|
|
11303
|
+
await writeFile25(path40.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
|
|
11304
|
+
if (!existsSync42(path40.join(runtimeDir, "README.md"))) {
|
|
11170
11305
|
await writeFile25(
|
|
11171
|
-
|
|
11306
|
+
path40.join(runtimeDir, "README.md"),
|
|
11172
11307
|
"# .ai/.runtime \u2014 disposable local layer\n\nRuntime data is local. Hivelore cleanup preserves briefing markers so enforcement state remains valid.\n",
|
|
11173
11308
|
"utf8"
|
|
11174
11309
|
);
|
|
@@ -11181,10 +11316,10 @@ async function cleanupCacheDir(cacheDir) {
|
|
|
11181
11316
|
const entries = await readdir4(cacheDir, { withFileTypes: true }).catch(() => []);
|
|
11182
11317
|
for (const entry of entries) {
|
|
11183
11318
|
if (entry.name === ".gitignore") continue;
|
|
11184
|
-
await rm2(
|
|
11319
|
+
await rm2(path40.join(cacheDir, entry.name), { recursive: true, force: true });
|
|
11185
11320
|
removed++;
|
|
11186
11321
|
}
|
|
11187
|
-
await writeFile25(
|
|
11322
|
+
await writeFile25(path40.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
|
|
11188
11323
|
return removed;
|
|
11189
11324
|
}
|
|
11190
11325
|
async function cleanupEnforcementDir(enforcementDir) {
|
|
@@ -11192,7 +11327,7 @@ async function cleanupEnforcementDir(enforcementDir) {
|
|
|
11192
11327
|
const entries = await readdir4(enforcementDir, { withFileTypes: true }).catch(() => []);
|
|
11193
11328
|
for (const entry of entries) {
|
|
11194
11329
|
if (entry.name === "briefings") continue;
|
|
11195
|
-
await rm2(
|
|
11330
|
+
await rm2(path40.join(enforcementDir, entry.name), { recursive: true, force: true });
|
|
11196
11331
|
removed++;
|
|
11197
11332
|
}
|
|
11198
11333
|
return removed;
|
|
@@ -11210,8 +11345,8 @@ async function inspectIntegrationVersions(root, expectedVersion) {
|
|
|
11210
11345
|
const missingBins = /* @__PURE__ */ new Map();
|
|
11211
11346
|
const staleBins = /* @__PURE__ */ new Map();
|
|
11212
11347
|
for (const rel of files) {
|
|
11213
|
-
const file =
|
|
11214
|
-
if (!
|
|
11348
|
+
const file = path40.join(root, rel);
|
|
11349
|
+
if (!existsSync42(file)) continue;
|
|
11215
11350
|
const text = await readFile18(file, "utf8").catch(() => "");
|
|
11216
11351
|
for (const bin of extractAbsoluteHaiveBins2(text)) {
|
|
11217
11352
|
const version = versionForBinary2(bin);
|
|
@@ -11258,7 +11393,7 @@ function extractAbsoluteHaiveBins2(text) {
|
|
|
11258
11393
|
const p = match[2];
|
|
11259
11394
|
if (!p) continue;
|
|
11260
11395
|
try {
|
|
11261
|
-
if (
|
|
11396
|
+
if (statSync4(p).isDirectory()) continue;
|
|
11262
11397
|
} catch {
|
|
11263
11398
|
}
|
|
11264
11399
|
out.add(p);
|
|
@@ -11328,7 +11463,7 @@ async function resolveCiDiffRange(root) {
|
|
|
11328
11463
|
}
|
|
11329
11464
|
async function resolveGithubEventRange(root) {
|
|
11330
11465
|
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
11331
|
-
if (!eventPath || !
|
|
11466
|
+
if (!eventPath || !existsSync42(eventPath)) return null;
|
|
11332
11467
|
try {
|
|
11333
11468
|
const event = JSON.parse(await readFile18(eventPath, "utf8"));
|
|
11334
11469
|
const prBase = cleanGitSha(event.pull_request?.base?.sha);
|
|
@@ -11433,7 +11568,7 @@ function isShippablePath(file) {
|
|
|
11433
11568
|
}
|
|
11434
11569
|
var CI_SKIP_DIRECTIVE = /\[skip ci\]|\[ci skip\]|\[no ci\]|\[skip actions\]|\*\*\*NO_CI\*\*\*|skip-checks: *true/i;
|
|
11435
11570
|
async function checkCommitMessageSkipCi(root, msgfile) {
|
|
11436
|
-
const file =
|
|
11571
|
+
const file = path40.isAbsolute(msgfile) ? msgfile : path40.join(root, msgfile);
|
|
11437
11572
|
const raw = await readFile18(file, "utf8").catch(() => "");
|
|
11438
11573
|
const cleaned = raw.split("\n").filter((line) => !line.startsWith("#")).join("\n");
|
|
11439
11574
|
if (!CI_SKIP_DIRECTIVE.test(cleaned)) return { block: false, message: "" };
|
|
@@ -11462,7 +11597,7 @@ async function inspectReleaseVersionState(root, upstream) {
|
|
|
11462
11597
|
}
|
|
11463
11598
|
async function readPackageVersion(root, relPath) {
|
|
11464
11599
|
try {
|
|
11465
|
-
const data = JSON.parse(await readFile18(
|
|
11600
|
+
const data = JSON.parse(await readFile18(path40.join(root, relPath), "utf8"));
|
|
11466
11601
|
return typeof data.version === "string" ? data.version : void 0;
|
|
11467
11602
|
} catch {
|
|
11468
11603
|
return void 0;
|
|
@@ -11650,8 +11785,8 @@ function buildScore(findings, threshold = 80) {
|
|
|
11650
11785
|
};
|
|
11651
11786
|
}
|
|
11652
11787
|
async function installGitEnforcement(root) {
|
|
11653
|
-
const hooksDir =
|
|
11654
|
-
if (!
|
|
11788
|
+
const hooksDir = path40.join(root, ".git", "hooks");
|
|
11789
|
+
if (!existsSync42(path40.join(root, ".git"))) {
|
|
11655
11790
|
ui.warn("No .git directory found; git enforcement hooks skipped.");
|
|
11656
11791
|
return;
|
|
11657
11792
|
}
|
|
@@ -11707,8 +11842,8 @@ _hivelore sync --quiet --since ORIG_HEAD || true
|
|
|
11707
11842
|
}
|
|
11708
11843
|
];
|
|
11709
11844
|
for (const hook of hooks) {
|
|
11710
|
-
const file =
|
|
11711
|
-
if (
|
|
11845
|
+
const file = path40.join(hooksDir, hook.name);
|
|
11846
|
+
if (existsSync42(file)) {
|
|
11712
11847
|
const current = await readFile18(file, "utf8").catch(() => "");
|
|
11713
11848
|
if (current.includes(ENFORCE_HOOK_MARKER)) {
|
|
11714
11849
|
await writeFile25(file, hook.body, "utf8");
|
|
@@ -11725,10 +11860,10 @@ ${hook.body}`, "utf8");
|
|
|
11725
11860
|
ui.success("Installed git hooks: pre-commit, pre-push, commit-msg (blocking) + post-merge, post-rewrite (sync)");
|
|
11726
11861
|
}
|
|
11727
11862
|
async function installCiEnforcement(root) {
|
|
11728
|
-
const workflowPath =
|
|
11729
|
-
await mkdir16(
|
|
11863
|
+
const workflowPath = path40.join(root, ".github", "workflows", "haive-enforcement.yml");
|
|
11864
|
+
await mkdir16(path40.dirname(workflowPath), { recursive: true });
|
|
11730
11865
|
const workflow = renderCiEnforcementWorkflow();
|
|
11731
|
-
if (
|
|
11866
|
+
if (existsSync42(workflowPath)) {
|
|
11732
11867
|
const existing = await readFile18(workflowPath, "utf8");
|
|
11733
11868
|
const start = "# haive:enforcement-workflow:start";
|
|
11734
11869
|
const end = "# haive:enforcement-workflow:end";
|
|
@@ -11736,14 +11871,14 @@ async function installCiEnforcement(root) {
|
|
|
11736
11871
|
const endAt = existing.indexOf(end);
|
|
11737
11872
|
if (startAt >= 0 && endAt > startAt) {
|
|
11738
11873
|
await writeFile25(workflowPath, existing.slice(0, startAt) + workflow + existing.slice(endAt + end.length), "utf8");
|
|
11739
|
-
ui.success(`Updated ${
|
|
11874
|
+
ui.success(`Updated ${path40.relative(root, workflowPath)} managed block`);
|
|
11740
11875
|
} else {
|
|
11741
11876
|
ui.info("GitHub Actions enforcement workflow already exists without Hivelore markers \u2014 preserved");
|
|
11742
11877
|
}
|
|
11743
11878
|
return;
|
|
11744
11879
|
}
|
|
11745
11880
|
await writeFile25(workflowPath, workflow, "utf8");
|
|
11746
|
-
ui.success(`Created ${
|
|
11881
|
+
ui.success(`Created ${path40.relative(root, workflowPath)}`);
|
|
11747
11882
|
}
|
|
11748
11883
|
function renderCiEnforcementWorkflow() {
|
|
11749
11884
|
return `# haive:enforcement-workflow:start
|
|
@@ -11946,15 +12081,15 @@ function extractToolPaths(payload, root) {
|
|
|
11946
12081
|
}
|
|
11947
12082
|
function normalizeToolPath(file, root) {
|
|
11948
12083
|
const normalized = file.replace(/\\/g, "/");
|
|
11949
|
-
if (!
|
|
11950
|
-
return
|
|
12084
|
+
if (!path40.isAbsolute(normalized)) return normalized.replace(/^\.\//, "");
|
|
12085
|
+
return path40.relative(root, normalized).replace(/\\/g, "/");
|
|
11951
12086
|
}
|
|
11952
12087
|
async function missingRequiredMemoriesForFiles(paths, files, sessionId) {
|
|
11953
|
-
if (!
|
|
12088
|
+
if (!existsSync42(paths.memoriesDir)) return [];
|
|
11954
12089
|
const marker = await readRecentBriefingMarker(paths, sessionId);
|
|
11955
12090
|
const consulted = new Set(marker?.memory_ids ?? []);
|
|
11956
12091
|
const policyTypes = /* @__PURE__ */ new Set(["decision", "gotcha", "architecture", "convention", "attempt"]);
|
|
11957
|
-
const all = await
|
|
12092
|
+
const all = await loadMemoriesFromDir15(paths.memoriesDir);
|
|
11958
12093
|
return all.filter(({ memory: memory2 }) => {
|
|
11959
12094
|
const fm = memory2.frontmatter;
|
|
11960
12095
|
if (!policyTypes.has(fm.type)) return false;
|
|
@@ -11995,7 +12130,7 @@ async function readStdin2(maxBytes) {
|
|
|
11995
12130
|
}
|
|
11996
12131
|
var ATOMIC_STAGE_EXCLUDE = ["/.usage/", "/.runtime/", "/.cache/"];
|
|
11997
12132
|
async function stageResyncedArtifacts(root, paths) {
|
|
11998
|
-
const aiRel =
|
|
12133
|
+
const aiRel = path40.relative(root, paths.haiveDir);
|
|
11999
12134
|
const out = await runCommand2("git", ["diff", "--name-only", "--", aiRel], root).catch(() => "");
|
|
12000
12135
|
const toStage = out.split("\n").map((line) => line.trim()).filter(Boolean).filter((file) => !ATOMIC_STAGE_EXCLUDE.some((excl) => `/${file}`.includes(excl)));
|
|
12001
12136
|
if (toStage.length === 0) return;
|
|
@@ -12022,9 +12157,9 @@ function runCommand2(cmd, args, cwd) {
|
|
|
12022
12157
|
}
|
|
12023
12158
|
|
|
12024
12159
|
// src/commands/release.ts
|
|
12025
|
-
import { existsSync as
|
|
12160
|
+
import { existsSync as existsSync43 } from "fs";
|
|
12026
12161
|
import { readFile as readFile19, writeFile as writeFile26 } from "fs/promises";
|
|
12027
|
-
import
|
|
12162
|
+
import path41 from "path";
|
|
12028
12163
|
import "commander";
|
|
12029
12164
|
import { findProjectRoot as findProjectRoot38 } from "@hivelore/core";
|
|
12030
12165
|
import { execFile as execFile6 } from "child_process";
|
|
@@ -12038,7 +12173,7 @@ var VERSION_FILES2 = [
|
|
|
12038
12173
|
"packages/embeddings/package.json"
|
|
12039
12174
|
];
|
|
12040
12175
|
async function readCurrentVersion(root) {
|
|
12041
|
-
const pkg = JSON.parse(await readFile19(
|
|
12176
|
+
const pkg = JSON.parse(await readFile19(path41.join(root, "package.json"), "utf8"));
|
|
12042
12177
|
if (!pkg.version) throw new Error("Root package.json has no version field.");
|
|
12043
12178
|
return pkg.version;
|
|
12044
12179
|
}
|
|
@@ -12059,8 +12194,8 @@ function registerRelease(program2) {
|
|
|
12059
12194
|
const current = await readCurrentVersion(root);
|
|
12060
12195
|
const next = nextVersion(current, spec);
|
|
12061
12196
|
for (const rel of VERSION_FILES2) {
|
|
12062
|
-
const file =
|
|
12063
|
-
if (!
|
|
12197
|
+
const file = path41.join(root, rel);
|
|
12198
|
+
if (!existsSync43(file)) {
|
|
12064
12199
|
ui.warn(`skip ${rel} (missing)`);
|
|
12065
12200
|
continue;
|
|
12066
12201
|
}
|
|
@@ -12073,8 +12208,8 @@ function registerRelease(program2) {
|
|
|
12073
12208
|
}
|
|
12074
12209
|
await writeFile26(file, updated, "utf8");
|
|
12075
12210
|
}
|
|
12076
|
-
const changelog =
|
|
12077
|
-
if (
|
|
12211
|
+
const changelog = path41.join(root, "CHANGELOG.md");
|
|
12212
|
+
if (existsSync43(changelog)) {
|
|
12078
12213
|
const raw = await readFile19(changelog, "utf8");
|
|
12079
12214
|
const heading = `## [${next}]${opts.title ? ` \u2014 ${opts.title}` : ""}`;
|
|
12080
12215
|
if (!raw.includes(`## [${next}]`)) {
|
|
@@ -12097,8 +12232,8 @@ ${heading}
|
|
|
12097
12232
|
const root = findProjectRoot38(opts.dir);
|
|
12098
12233
|
const version = await readCurrentVersion(root);
|
|
12099
12234
|
for (const rel of VERSION_FILES2.slice(1)) {
|
|
12100
|
-
const file =
|
|
12101
|
-
if (!
|
|
12235
|
+
const file = path41.join(root, rel);
|
|
12236
|
+
if (!existsSync43(file)) continue;
|
|
12102
12237
|
const v = JSON.parse(await readFile19(file, "utf8")).version;
|
|
12103
12238
|
if (v !== version) {
|
|
12104
12239
|
ui.error(`${rel} is at ${v}, root at ${version} \u2014 lockstep broken; run \`hivelore release bump\` first.`);
|
|
@@ -12144,9 +12279,9 @@ function registerRun(program2) {
|
|
|
12144
12279
|
|
|
12145
12280
|
// src/commands/sensors.ts
|
|
12146
12281
|
import { execFile as execFile7 } from "child_process";
|
|
12147
|
-
import { existsSync as
|
|
12282
|
+
import { existsSync as existsSync44 } from "fs";
|
|
12148
12283
|
import { chmod as chmod2, mkdir as mkdir17, readFile as readFile20, writeFile as writeFile27 } from "fs/promises";
|
|
12149
|
-
import
|
|
12284
|
+
import path42 from "path";
|
|
12150
12285
|
import { promisify as promisify7 } from "util";
|
|
12151
12286
|
import "commander";
|
|
12152
12287
|
import {
|
|
@@ -12159,11 +12294,13 @@ import {
|
|
|
12159
12294
|
judgeProposedSensor,
|
|
12160
12295
|
loadConfig as loadConfig13,
|
|
12161
12296
|
loadSensorLedger as loadSensorLedger4,
|
|
12162
|
-
loadMemoriesFromDir as
|
|
12297
|
+
loadMemoriesFromDir as loadMemoriesFromDir16,
|
|
12298
|
+
normalizeFramework,
|
|
12163
12299
|
parseLessonFields,
|
|
12164
12300
|
recordPreventionHits as recordPreventionHits2,
|
|
12165
12301
|
resolveHaivePaths as resolveHaivePaths36,
|
|
12166
12302
|
runSensors as runSensors2,
|
|
12303
|
+
buildProposeCommand,
|
|
12167
12304
|
scaffoldPostIncidentTest,
|
|
12168
12305
|
selectCommandSensors as selectCommandSensors2,
|
|
12169
12306
|
TEST_FRAMEWORKS,
|
|
@@ -12210,7 +12347,7 @@ function registerSensors(program2) {
|
|
|
12210
12347
|
const root = findProjectRoot39(opts.dir);
|
|
12211
12348
|
const paths = resolveHaivePaths36(root);
|
|
12212
12349
|
const memories = await runnableSensorMemories(paths);
|
|
12213
|
-
const diff = opts.diffFile ? await readFile20(
|
|
12350
|
+
const diff = opts.diffFile ? await readFile20(path42.resolve(root, opts.diffFile), "utf8") : await stagedDiff(root);
|
|
12214
12351
|
const targets = scannableSensorTargets(diff);
|
|
12215
12352
|
const hits = runSensors2(memories, targets);
|
|
12216
12353
|
const config = await loadConfig13(paths);
|
|
@@ -12342,7 +12479,7 @@ function registerSensors(program2) {
|
|
|
12342
12479
|
}
|
|
12343
12480
|
const root = findProjectRoot39(opts.dir);
|
|
12344
12481
|
const paths = resolveHaivePaths36(root);
|
|
12345
|
-
const loaded =
|
|
12482
|
+
const loaded = existsSync44(paths.memoriesDir) ? await loadMemoriesFromDir16(paths.memoriesDir) : [];
|
|
12346
12483
|
const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
|
|
12347
12484
|
if (!found) {
|
|
12348
12485
|
ui.error(`No memory found with id ${id}`);
|
|
@@ -12409,7 +12546,7 @@ function registerSensors(program2) {
|
|
|
12409
12546
|
return;
|
|
12410
12547
|
}
|
|
12411
12548
|
const root2 = findProjectRoot39(opts.dir);
|
|
12412
|
-
const { proposeSensor } = await import("./server-
|
|
12549
|
+
const { proposeSensor } = await import("./server-47VOVJJT.js");
|
|
12413
12550
|
const out = await proposeSensor(
|
|
12414
12551
|
{
|
|
12415
12552
|
memory_id: id,
|
|
@@ -12453,7 +12590,7 @@ function registerSensors(program2) {
|
|
|
12453
12590
|
}
|
|
12454
12591
|
const root = findProjectRoot39(opts.dir);
|
|
12455
12592
|
const paths = resolveHaivePaths36(root);
|
|
12456
|
-
const loaded =
|
|
12593
|
+
const loaded = existsSync44(paths.memoriesDir) ? await loadMemoriesFromDir16(paths.memoriesDir) : [];
|
|
12457
12594
|
const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
|
|
12458
12595
|
if (!found) {
|
|
12459
12596
|
ui.error(`No memory found with id ${id}`);
|
|
@@ -12497,56 +12634,78 @@ function registerSensors(program2) {
|
|
|
12497
12634
|
ui.info(
|
|
12498
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}`)
|
|
12499
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
|
+
}
|
|
12500
12642
|
});
|
|
12501
12643
|
sensors.command("scaffold").description(
|
|
12502
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"
|
|
12503
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) => {
|
|
12504
12646
|
const root = findProjectRoot39(opts.dir);
|
|
12505
12647
|
const paths = resolveHaivePaths36(root);
|
|
12506
|
-
const loaded =
|
|
12648
|
+
const loaded = existsSync44(paths.memoriesDir) ? await loadMemoriesFromDir16(paths.memoriesDir) : [];
|
|
12507
12649
|
const found = loaded.find(({ memory: memory2 }) => memory2.frontmatter.id === id);
|
|
12508
12650
|
if (!found) {
|
|
12509
12651
|
ui.error(`No memory found with id ${id}`);
|
|
12510
12652
|
process.exitCode = 1;
|
|
12511
12653
|
return;
|
|
12512
12654
|
}
|
|
12513
|
-
const
|
|
12514
|
-
|
|
12655
|
+
const fm = found.memory.frontmatter;
|
|
12656
|
+
const forced = opts.framework ? normalizeFramework(opts.framework) : null;
|
|
12657
|
+
if (opts.framework && !forced) {
|
|
12515
12658
|
ui.error(`Unknown --framework "${opts.framework}". Use one of: ${TEST_FRAMEWORKS.join(", ")}.`);
|
|
12516
12659
|
process.exitCode = 1;
|
|
12517
12660
|
return;
|
|
12518
12661
|
}
|
|
12662
|
+
const allGroups = await detectTestFrameworksForAnchors(root, fm.anchor.paths ?? []);
|
|
12663
|
+
const groups = opts.out ? allGroups.slice(0, 1) : allGroups;
|
|
12519
12664
|
const fields = parseLessonFields(found.memory.body);
|
|
12520
|
-
const
|
|
12521
|
-
|
|
12522
|
-
|
|
12523
|
-
|
|
12524
|
-
|
|
12525
|
-
|
|
12526
|
-
|
|
12527
|
-
|
|
12528
|
-
|
|
12529
|
-
}
|
|
12530
|
-
{ framework, outPath: opts.out }
|
|
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 })
|
|
12531
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
|
+
}
|
|
12532
12683
|
if (opts.stdout) {
|
|
12533
|
-
|
|
12534
|
-
|
|
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}`);
|
|
12535
12689
|
return;
|
|
12536
12690
|
}
|
|
12537
|
-
|
|
12538
|
-
|
|
12539
|
-
|
|
12540
|
-
|
|
12541
|
-
|
|
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.`);
|
|
12542
12704
|
}
|
|
12543
|
-
await mkdir17(path41.dirname(abs), { recursive: true });
|
|
12544
|
-
await writeFile27(abs, scaffold.content, "utf8");
|
|
12545
|
-
ui.success(`Wrote ${framework} post-incident test \u2192 ${scaffold.relPath}`);
|
|
12546
12705
|
ui.info(" 1. Fill in the assertion (RED on the incident, GREEN once fixed).");
|
|
12547
|
-
ui.info(` 2. Run it: ${
|
|
12706
|
+
ui.info(` 2. Run ${scaffolds.length > 1 ? "them" : "it"}: ${scaffolds.map((s) => s.runCommand).join(" && ")}`);
|
|
12548
12707
|
ui.info(" 3. Arm it as a deterministic gate:");
|
|
12549
|
-
console.log(` ${
|
|
12708
|
+
console.log(` ${proposeCmd}`);
|
|
12550
12709
|
});
|
|
12551
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) => {
|
|
12552
12711
|
const format = opts.format ?? "grep";
|
|
@@ -12558,13 +12717,13 @@ function registerSensors(program2) {
|
|
|
12558
12717
|
const root = findProjectRoot39(opts.dir);
|
|
12559
12718
|
const paths = resolveHaivePaths36(root);
|
|
12560
12719
|
const rows = await sensorRows(paths);
|
|
12561
|
-
const outDir =
|
|
12720
|
+
const outDir = path42.resolve(root, opts.outDir ?? ".ai/generated");
|
|
12562
12721
|
await mkdir17(outDir, { recursive: true });
|
|
12563
|
-
const outPath =
|
|
12722
|
+
const outPath = path42.join(outDir, format === "grep" ? "haive-sensors-grep.sh" : "haive-sensors-eslint.json");
|
|
12564
12723
|
const content = format === "grep" ? renderGrepScript(rows) : JSON.stringify({ sensors: rows }, null, 2) + "\n";
|
|
12565
12724
|
await writeFile27(outPath, content, "utf8");
|
|
12566
12725
|
if (format === "grep") await chmod2(outPath, 493);
|
|
12567
|
-
ui.success(`Exported ${rows.length} sensor(s): ${
|
|
12726
|
+
ui.success(`Exported ${rows.length} sensor(s): ${path42.relative(root, outPath)}`);
|
|
12568
12727
|
});
|
|
12569
12728
|
}
|
|
12570
12729
|
function deriveProposedMessage(body, pattern, absent) {
|
|
@@ -12597,8 +12756,8 @@ async function sensorRows(paths) {
|
|
|
12597
12756
|
});
|
|
12598
12757
|
}
|
|
12599
12758
|
async function runnableSensorMemories(paths, regexOnly = true) {
|
|
12600
|
-
if (!
|
|
12601
|
-
const loaded = await
|
|
12759
|
+
if (!existsSync44(paths.memoriesDir)) return [];
|
|
12760
|
+
const loaded = await loadMemoriesFromDir16(paths.memoriesDir);
|
|
12602
12761
|
return loaded.map(({ memory: memory2 }) => memory2).filter((memory2) => {
|
|
12603
12762
|
const sensor = memory2.frontmatter.sensor;
|
|
12604
12763
|
if (!sensor) return false;
|
|
@@ -12637,42 +12796,17 @@ function renderGrepScript(rows) {
|
|
|
12637
12796
|
function shellQuote(value) {
|
|
12638
12797
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
12639
12798
|
}
|
|
12640
|
-
function normalizeFramework(input) {
|
|
12641
|
-
const v = input.trim().toLowerCase();
|
|
12642
|
-
if (v === "vitest") return "vitest";
|
|
12643
|
-
if (v === "jest") return "jest";
|
|
12644
|
-
if (v === "pytest" || v === "py" || v === "python") return "pytest";
|
|
12645
|
-
if (v === "go" || v === "gotest" || v === "go-test") return "gotest";
|
|
12646
|
-
return null;
|
|
12647
|
-
}
|
|
12648
|
-
async function detectTestFramework(root) {
|
|
12649
|
-
try {
|
|
12650
|
-
const pkgPath = path41.join(root, "package.json");
|
|
12651
|
-
if (existsSync43(pkgPath)) {
|
|
12652
|
-
const pkg = JSON.parse(await readFile20(pkgPath, "utf8"));
|
|
12653
|
-
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
12654
|
-
if (deps.vitest) return "vitest";
|
|
12655
|
-
if (deps.jest || deps["ts-jest"]) return "jest";
|
|
12656
|
-
}
|
|
12657
|
-
} catch {
|
|
12658
|
-
}
|
|
12659
|
-
if (existsSync43(path41.join(root, "go.mod"))) return "gotest";
|
|
12660
|
-
for (const signal of ["pyproject.toml", "setup.py", "pytest.ini", "requirements.txt", "tox.ini"]) {
|
|
12661
|
-
if (existsSync43(path41.join(root, signal))) return "pytest";
|
|
12662
|
-
}
|
|
12663
|
-
return "vitest";
|
|
12664
|
-
}
|
|
12665
12799
|
|
|
12666
12800
|
// src/commands/ingest.ts
|
|
12667
|
-
import { existsSync as
|
|
12801
|
+
import { existsSync as existsSync45 } from "fs";
|
|
12668
12802
|
import { mkdir as mkdir18, readFile as readFile21, writeFile as writeFile28 } from "fs/promises";
|
|
12669
|
-
import
|
|
12803
|
+
import path43 from "path";
|
|
12670
12804
|
import "commander";
|
|
12671
12805
|
import {
|
|
12672
12806
|
draftsFromFindings,
|
|
12673
12807
|
filterNewDrafts,
|
|
12674
12808
|
findProjectRoot as findProjectRoot40,
|
|
12675
|
-
loadMemoriesFromDir as
|
|
12809
|
+
loadMemoriesFromDir as loadMemoriesFromDir17,
|
|
12676
12810
|
memoryFilePath as memoryFilePath7,
|
|
12677
12811
|
parseFindings,
|
|
12678
12812
|
resolveHaivePaths as resolveHaivePaths37,
|
|
@@ -12702,7 +12836,7 @@ function registerIngest(program2) {
|
|
|
12702
12836
|
}
|
|
12703
12837
|
const root = findProjectRoot40(opts.dir);
|
|
12704
12838
|
const paths = resolveHaivePaths37(root);
|
|
12705
|
-
if (!
|
|
12839
|
+
if (!existsSync45(paths.haiveDir)) {
|
|
12706
12840
|
ui.error(`No .ai/ found at ${root}. Run \`hivelore init\` first.`);
|
|
12707
12841
|
process.exitCode = 1;
|
|
12708
12842
|
return;
|
|
@@ -12723,8 +12857,8 @@ function registerIngest(program2) {
|
|
|
12723
12857
|
process.exitCode = 1;
|
|
12724
12858
|
return;
|
|
12725
12859
|
}
|
|
12726
|
-
const reportPath =
|
|
12727
|
-
if (!
|
|
12860
|
+
const reportPath = path43.resolve(root, file);
|
|
12861
|
+
if (!existsSync45(reportPath)) {
|
|
12728
12862
|
ui.error(`Report file not found: ${reportPath}`);
|
|
12729
12863
|
process.exitCode = 1;
|
|
12730
12864
|
return;
|
|
@@ -12756,7 +12890,7 @@ function registerIngest(program2) {
|
|
|
12756
12890
|
process.exitCode = 1;
|
|
12757
12891
|
return;
|
|
12758
12892
|
}
|
|
12759
|
-
const existing =
|
|
12893
|
+
const existing = existsSync45(paths.memoriesDir) ? await loadMemoriesFromDir17(paths.memoriesDir) : [];
|
|
12760
12894
|
const existingTopics = new Set(
|
|
12761
12895
|
existing.map(({ memory: memory2 }) => memory2.frontmatter.topic).filter((t) => Boolean(t))
|
|
12762
12896
|
);
|
|
@@ -12818,13 +12952,13 @@ function registerIngest(program2) {
|
|
|
12818
12952
|
await writeDraft(paths, draft);
|
|
12819
12953
|
created++;
|
|
12820
12954
|
}
|
|
12821
|
-
ui.success(`Created ${created} proposed memory(ies) under ${
|
|
12955
|
+
ui.success(`Created ${created} proposed memory(ies) under ${path43.relative(root, paths.memoriesDir)}/`);
|
|
12822
12956
|
ui.info("Review with `hivelore memory pending`; promote sensors with `hivelore sensors promote <id> --yes`.");
|
|
12823
12957
|
});
|
|
12824
12958
|
}
|
|
12825
12959
|
async function writeDraft(paths, draft) {
|
|
12826
12960
|
const file = memoryFilePath7(paths, draft.frontmatter.scope, draft.frontmatter.id, draft.frontmatter.module);
|
|
12827
|
-
await mkdir18(
|
|
12961
|
+
await mkdir18(path43.dirname(file), { recursive: true });
|
|
12828
12962
|
await writeFile28(file, serializeMemory16({ frontmatter: draft.frontmatter, body: draft.body }), "utf8");
|
|
12829
12963
|
return file;
|
|
12830
12964
|
}
|
|
@@ -12866,13 +13000,13 @@ async function fetchSonarIssues(opts) {
|
|
|
12866
13000
|
}
|
|
12867
13001
|
|
|
12868
13002
|
// src/commands/dashboard.ts
|
|
12869
|
-
import { existsSync as
|
|
13003
|
+
import { existsSync as existsSync46 } from "fs";
|
|
12870
13004
|
import "commander";
|
|
12871
13005
|
import {
|
|
12872
13006
|
buildDashboard,
|
|
12873
13007
|
findProjectRoot as findProjectRoot41,
|
|
12874
13008
|
loadConfig as loadConfig14,
|
|
12875
|
-
loadMemoriesFromDir as
|
|
13009
|
+
loadMemoriesFromDir as loadMemoriesFromDir18,
|
|
12876
13010
|
loadPreventionEvents as loadPreventionEvents4,
|
|
12877
13011
|
loadUsageIndex as loadUsageIndex16,
|
|
12878
13012
|
resolveHaivePaths as resolveHaivePaths38
|
|
@@ -12883,12 +13017,12 @@ function registerDashboard(program2) {
|
|
|
12883
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) => {
|
|
12884
13018
|
const root = findProjectRoot41(opts.dir);
|
|
12885
13019
|
const paths = resolveHaivePaths38(root);
|
|
12886
|
-
if (!
|
|
13020
|
+
if (!existsSync46(paths.haiveDir)) {
|
|
12887
13021
|
ui.error(`No .ai/ found at ${root}. Run \`hivelore init\` first.`);
|
|
12888
13022
|
process.exitCode = 1;
|
|
12889
13023
|
return;
|
|
12890
13024
|
}
|
|
12891
|
-
const memories =
|
|
13025
|
+
const memories = existsSync46(paths.memoriesDir) ? await loadMemoriesFromDir18(paths.memoriesDir) : [];
|
|
12892
13026
|
const usage = await loadUsageIndex16(paths);
|
|
12893
13027
|
const preventionEvents = await loadPreventionEvents4(paths);
|
|
12894
13028
|
const config = await loadConfig14(paths);
|
|
@@ -13014,8 +13148,8 @@ function warnNum(n) {
|
|
|
13014
13148
|
// src/commands/dev-link.ts
|
|
13015
13149
|
import { execFile as execFile8 } from "child_process";
|
|
13016
13150
|
import { cp, readFile as readFile22 } from "fs/promises";
|
|
13017
|
-
import { existsSync as
|
|
13018
|
-
import
|
|
13151
|
+
import { existsSync as existsSync47 } from "fs";
|
|
13152
|
+
import path44 from "path";
|
|
13019
13153
|
import { promisify as promisify8 } from "util";
|
|
13020
13154
|
import "commander";
|
|
13021
13155
|
import { findProjectRoot as findProjectRoot42 } from "@hivelore/core";
|
|
@@ -13024,7 +13158,7 @@ function registerDevLink(program2) {
|
|
|
13024
13158
|
const dev = program2.commands.find((c) => c.name() === "dev") ?? program2.command("dev").description("Developer utilities for working on Hivelore itself.");
|
|
13025
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) => {
|
|
13026
13160
|
const root = findProjectRoot42(opts.dir);
|
|
13027
|
-
if (!
|
|
13161
|
+
if (!existsSync47(path44.join(root, "packages", "cli", "dist", "index.js"))) {
|
|
13028
13162
|
ui.error(`Not the Hivelore monorepo (no packages/cli/dist) at ${root}. Run \`pnpm -r build\` first, or pass --dir.`);
|
|
13029
13163
|
process.exitCode = 1;
|
|
13030
13164
|
return;
|
|
@@ -13033,9 +13167,9 @@ function registerDevLink(program2) {
|
|
|
13033
13167
|
try {
|
|
13034
13168
|
globalModules = (await exec6("npm", ["root", "-g"])).stdout.trim();
|
|
13035
13169
|
} catch {
|
|
13036
|
-
globalModules =
|
|
13170
|
+
globalModules = path44.join(path44.dirname(path44.dirname(process.execPath)), "lib", "node_modules");
|
|
13037
13171
|
}
|
|
13038
|
-
const scopeDirs = ["@hivelore", "@hiveai"].map((scope) =>
|
|
13172
|
+
const scopeDirs = ["@hivelore", "@hiveai"].map((scope) => path44.join(globalModules, scope)).filter((dir) => existsSync47(dir));
|
|
13039
13173
|
if (scopeDirs.length === 0) {
|
|
13040
13174
|
ui.error(`No global @hivelore (or legacy @hiveai) install under ${globalModules}. Install once with \`npm i -g @hivelore/cli\`, then re-run.`);
|
|
13041
13175
|
process.exitCode = 1;
|
|
@@ -13043,24 +13177,24 @@ function registerDevLink(program2) {
|
|
|
13043
13177
|
}
|
|
13044
13178
|
const linked = [];
|
|
13045
13179
|
const copyDist = async (fromPkg, toDistDir) => {
|
|
13046
|
-
const from =
|
|
13047
|
-
if (!
|
|
13180
|
+
const from = path44.join(root, "packages", fromPkg, "dist");
|
|
13181
|
+
if (!existsSync47(from) || !existsSync47(path44.dirname(toDistDir))) return;
|
|
13048
13182
|
await cp(from, toDistDir, { recursive: true });
|
|
13049
|
-
linked.push(
|
|
13183
|
+
linked.push(path44.relative(globalModules, toDistDir));
|
|
13050
13184
|
};
|
|
13051
13185
|
for (const globalHive of scopeDirs) {
|
|
13052
|
-
const nestedScope =
|
|
13186
|
+
const nestedScope = path44.basename(globalHive);
|
|
13053
13187
|
for (const pkg of ["cli", "mcp"]) {
|
|
13054
|
-
await copyDist(pkg,
|
|
13188
|
+
await copyDist(pkg, path44.join(globalHive, pkg, "dist"));
|
|
13055
13189
|
for (const nested of ["core", "embeddings"]) {
|
|
13056
|
-
await copyDist(nested,
|
|
13190
|
+
await copyDist(nested, path44.join(globalHive, pkg, "node_modules", nestedScope, nested, "dist"));
|
|
13057
13191
|
}
|
|
13058
13192
|
}
|
|
13059
|
-
await copyDist("core",
|
|
13193
|
+
await copyDist("core", path44.join(globalHive, "core", "dist"));
|
|
13060
13194
|
}
|
|
13061
13195
|
let version = "unknown";
|
|
13062
13196
|
try {
|
|
13063
|
-
version = JSON.parse(await readFile22(
|
|
13197
|
+
version = JSON.parse(await readFile22(path44.join(root, "package.json"), "utf8")).version ?? "unknown";
|
|
13064
13198
|
} catch {
|
|
13065
13199
|
}
|
|
13066
13200
|
if (opts.json) {
|
|
@@ -13071,7 +13205,7 @@ function registerDevLink(program2) {
|
|
|
13071
13205
|
ui.warn("Nothing linked \u2014 no matching dist targets were found in the global install.");
|
|
13072
13206
|
return;
|
|
13073
13207
|
}
|
|
13074
|
-
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(", ")}`);
|
|
13075
13209
|
for (const t of linked) console.log(` ${ui.dim("\u2192")} ${t}`);
|
|
13076
13210
|
console.log(ui.dim("The global binary now runs your local build (git hooks + MCP included)."));
|
|
13077
13211
|
});
|
|
@@ -13079,8 +13213,8 @@ function registerDevLink(program2) {
|
|
|
13079
13213
|
|
|
13080
13214
|
// src/commands/coverage.ts
|
|
13081
13215
|
import { readFile as readFile23 } from "fs/promises";
|
|
13082
|
-
import { existsSync as
|
|
13083
|
-
import
|
|
13216
|
+
import { existsSync as existsSync48 } from "fs";
|
|
13217
|
+
import path45 from "path";
|
|
13084
13218
|
import "commander";
|
|
13085
13219
|
import {
|
|
13086
13220
|
findCoverageGaps,
|
|
@@ -13090,7 +13224,7 @@ import {
|
|
|
13090
13224
|
tallyHotFiles
|
|
13091
13225
|
} from "@hivelore/core";
|
|
13092
13226
|
async function readAgentHotFiles(root, cacheFile, sinceMs) {
|
|
13093
|
-
if (!
|
|
13227
|
+
if (!existsSync48(cacheFile)) return [];
|
|
13094
13228
|
const raw = await readFile23(cacheFile, "utf8").catch(() => "");
|
|
13095
13229
|
const files = [];
|
|
13096
13230
|
for (const line of raw.split("\n")) {
|
|
@@ -13104,7 +13238,7 @@ async function readAgentHotFiles(root, cacheFile, sinceMs) {
|
|
|
13104
13238
|
}
|
|
13105
13239
|
for (const f of obs.files ?? []) {
|
|
13106
13240
|
if (typeof f !== "string" || !f) continue;
|
|
13107
|
-
const rel =
|
|
13241
|
+
const rel = path45.isAbsolute(f) ? path45.relative(root, f) : f;
|
|
13108
13242
|
if (rel.startsWith("..")) continue;
|
|
13109
13243
|
files.push(rel);
|
|
13110
13244
|
}
|
|
@@ -13148,7 +13282,7 @@ function registerCoverage(program2) {
|
|
|
13148
13282
|
}) : null;
|
|
13149
13283
|
const gitHotFiles = (radar?.hotFiles ?? []).filter((h) => !isNoisePath(h.path)).map((h) => ({ path: h.path, changes: h.changes, source: "git" }));
|
|
13150
13284
|
const sinceMs = Date.now() - days * 864e5;
|
|
13151
|
-
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)) : [];
|
|
13152
13286
|
const hotFiles = mergeHotFiles(gitHotFiles, agentHotFiles);
|
|
13153
13287
|
const memories = await loadMemoriesFromDir(paths.memoriesDir);
|
|
13154
13288
|
const gaps = findCoverageGaps(hotFiles, memories, { minChanges, limit });
|
|
@@ -13186,8 +13320,8 @@ function registerCoverage(program2) {
|
|
|
13186
13320
|
|
|
13187
13321
|
// src/commands/merge-driver.ts
|
|
13188
13322
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
13189
|
-
import { readFileSync, writeFileSync, existsSync as
|
|
13190
|
-
import
|
|
13323
|
+
import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync49 } from "fs";
|
|
13324
|
+
import path46 from "path";
|
|
13191
13325
|
import "commander";
|
|
13192
13326
|
import { findProjectRoot as findProjectRoot44, mergeMemoryVersions } from "@hivelore/core";
|
|
13193
13327
|
var GITATTRIBUTES_MARK = "# Hivelore merge driver";
|
|
@@ -13200,8 +13334,8 @@ function registerMergeDriver(program2) {
|
|
|
13200
13334
|
const cmd = program2.command("merge-driver").description("Deterministic git merge driver for Hivelore memory files (kills .ai/ conflict markers)");
|
|
13201
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) => {
|
|
13202
13336
|
try {
|
|
13203
|
-
const oursContent =
|
|
13204
|
-
const theirsContent =
|
|
13337
|
+
const oursContent = readFileSync2(ours, "utf8");
|
|
13338
|
+
const theirsContent = readFileSync2(theirs, "utf8");
|
|
13205
13339
|
const result = mergeMemoryVersions(oursContent, theirsContent);
|
|
13206
13340
|
if (result.content !== oursContent) writeFileSync(ours, result.content, "utf8");
|
|
13207
13341
|
process.exit(0);
|
|
@@ -13219,8 +13353,8 @@ function registerMergeDriver(program2) {
|
|
|
13219
13353
|
process.exitCode = 1;
|
|
13220
13354
|
return;
|
|
13221
13355
|
}
|
|
13222
|
-
const gaPath =
|
|
13223
|
-
let content =
|
|
13356
|
+
const gaPath = path46.join(root, ".gitattributes");
|
|
13357
|
+
let content = existsSync49(gaPath) ? readFileSync2(gaPath, "utf8") : "";
|
|
13224
13358
|
if (!content.includes(GITATTRIBUTES_MARK)) {
|
|
13225
13359
|
if (content.length > 0 && !content.endsWith("\n")) content += "\n";
|
|
13226
13360
|
content += GITATTRIBUTES_BLOCK + "\n";
|
|
@@ -13234,8 +13368,8 @@ function registerMergeDriver(program2) {
|
|
|
13234
13368
|
}
|
|
13235
13369
|
|
|
13236
13370
|
// src/commands/bridges.ts
|
|
13237
|
-
import { existsSync as
|
|
13238
|
-
import
|
|
13371
|
+
import { existsSync as existsSync50 } from "fs";
|
|
13372
|
+
import path47 from "path";
|
|
13239
13373
|
import "commander";
|
|
13240
13374
|
import {
|
|
13241
13375
|
findProjectRoot as findProjectRoot45,
|
|
@@ -13256,7 +13390,7 @@ function registerBridges(program2) {
|
|
|
13256
13390
|
const root = findProjectRoot45(opts.dir);
|
|
13257
13391
|
const paths = resolveHaivePaths40(root);
|
|
13258
13392
|
const dryRun = opts.dryRun === true;
|
|
13259
|
-
if (!
|
|
13393
|
+
if (!existsSync50(paths.memoriesDir)) {
|
|
13260
13394
|
ui.warn(`No .ai/memories at ${root}. Run \`hivelore init\` first.`);
|
|
13261
13395
|
process.exitCode = 1;
|
|
13262
13396
|
return;
|
|
@@ -13275,7 +13409,7 @@ function registerBridges(program2) {
|
|
|
13275
13409
|
targets = BRIDGE_TARGETS4;
|
|
13276
13410
|
} else {
|
|
13277
13411
|
targets = BRIDGE_TARGETS4.filter(
|
|
13278
|
-
(t) =>
|
|
13412
|
+
(t) => existsSync50(path47.join(root, BRIDGE_TARGET_PATH3[t]))
|
|
13279
13413
|
);
|
|
13280
13414
|
if (targets.length === 0) {
|
|
13281
13415
|
ui.info(
|
|
@@ -13323,7 +13457,7 @@ function registerBridges(program2) {
|
|
|
13323
13457
|
|
|
13324
13458
|
// src/index.ts
|
|
13325
13459
|
var program = new Command48();
|
|
13326
|
-
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);
|
|
13327
13461
|
registerInit(program);
|
|
13328
13462
|
registerResolveProject(program);
|
|
13329
13463
|
registerEnforce(program);
|