@hivelore/cli 0.47.0 → 0.51.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-HBS7PNNY.js → chunk-UK4CWVS7.js} +32 -9
- package/dist/chunk-UK4CWVS7.js.map +1 -0
- package/dist/index.js +222 -127
- package/dist/index.js.map +1 -1
- package/dist/{server-LNSXYVVW.js → server-Z4M6ERWN.js} +2 -2
- package/package.json +5 -7
- package/dist/chunk-HBS7PNNY.js.map +0 -1
- /package/dist/{server-LNSXYVVW.js.map → server-Z4M6ERWN.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
readPresumedCorrectTargets,
|
|
14
14
|
runAstSensorOnContent,
|
|
15
15
|
runHaiveMcpStdio
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-UK4CWVS7.js";
|
|
17
17
|
import {
|
|
18
18
|
registerMemoryPending
|
|
19
19
|
} from "./chunk-OYJKHD22.js";
|
|
@@ -588,7 +588,7 @@ async function applyAutopilotRepairs(root, paths, options = {}) {
|
|
|
588
588
|
if (changed) {
|
|
589
589
|
repairs.push({
|
|
590
590
|
code: "autopilot-config",
|
|
591
|
-
message: "Enabled autopilot defaults in .ai/
|
|
591
|
+
message: "Enabled autopilot defaults in .ai/hivelore.config.json."
|
|
592
592
|
});
|
|
593
593
|
}
|
|
594
594
|
}
|
|
@@ -1483,7 +1483,7 @@ function isCodeMapStale(root, generatedAt, files) {
|
|
|
1483
1483
|
|
|
1484
1484
|
// src/commands/init.ts
|
|
1485
1485
|
import { execFile as execFile2 } from "child_process";
|
|
1486
|
-
import { mkdir as mkdir6, readFile as readFile6, readdir as readdir2, writeFile as writeFile7 } from "fs/promises";
|
|
1486
|
+
import { mkdir as mkdir6, readFile as readFile6, readdir as readdir2, rm, writeFile as writeFile7 } from "fs/promises";
|
|
1487
1487
|
import { existsSync as existsSync12 } from "fs";
|
|
1488
1488
|
import path13 from "path";
|
|
1489
1489
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
@@ -1491,6 +1491,10 @@ import { promisify as promisify2 } from "util";
|
|
|
1491
1491
|
import "commander";
|
|
1492
1492
|
import {
|
|
1493
1493
|
AUTOPILOT_DEFAULTS as AUTOPILOT_DEFAULTS2,
|
|
1494
|
+
CONFIG_FILE,
|
|
1495
|
+
LEGACY_CONFIG_FILE,
|
|
1496
|
+
loadConfig as loadConfig3,
|
|
1497
|
+
resolveConfigPath,
|
|
1494
1498
|
BRIDGE_TARGETS as BRIDGE_TARGETS2,
|
|
1495
1499
|
buildCodeMap as buildCodeMap3,
|
|
1496
1500
|
buildFrontmatter as buildFrontmatter2,
|
|
@@ -3758,7 +3762,7 @@ ${SEED_FOOTER(stack)}` });
|
|
|
3758
3762
|
|
|
3759
3763
|
// src/commands/init.ts
|
|
3760
3764
|
var execFileAsync = promisify2(execFile2);
|
|
3761
|
-
var HAIVE_GITHUB_ACTION_REF = `v${"0.
|
|
3765
|
+
var HAIVE_GITHUB_ACTION_REF = `v${"0.51.0"}`;
|
|
3762
3766
|
var PROJECT_CONTEXT_TEMPLATE = `# Project context
|
|
3763
3767
|
|
|
3764
3768
|
> Generated by \`hivelore init\`. Run \`hivelore init --bootstrap\` to auto-fill from your codebase,
|
|
@@ -3804,7 +3808,7 @@ This repository uses **Hivelore**. Running \`hivelore init\` means the team expe
|
|
|
3804
3808
|
|
|
3805
3809
|
Tell the developer to enable the **hivelore** server (e.g. \`hivelore mcp --stdio\` in client config) and restart the client. Do not silently ignore Hivelore.
|
|
3806
3810
|
`;
|
|
3807
|
-
var CI_WORKFLOW = `name:
|
|
3811
|
+
var CI_WORKFLOW = `name: hivelore-sync
|
|
3808
3812
|
|
|
3809
3813
|
on:
|
|
3810
3814
|
push:
|
|
@@ -3926,7 +3930,7 @@ jobs:
|
|
|
3926
3930
|
run: hivelore eval --trend || true
|
|
3927
3931
|
|
|
3928
3932
|
# On push to main: push shared memories to the hub (if hubPath is configured)
|
|
3929
|
-
# Uncomment and configure hubPath in .ai/
|
|
3933
|
+
# Uncomment and configure hubPath in .ai/hivelore.config.json to enable.
|
|
3930
3934
|
# hub-push:
|
|
3931
3935
|
# if: github.event_name == 'push'
|
|
3932
3936
|
# needs: sync-on-merge
|
|
@@ -3944,7 +3948,7 @@ jobs:
|
|
|
3944
3948
|
# run: npm install -g @hivelore/cli
|
|
3945
3949
|
# - name: push shared memories to hub
|
|
3946
3950
|
# run: hivelore hub push --commit
|
|
3947
|
-
# # Requires hubPath in .ai/
|
|
3951
|
+
# # Requires hubPath in .ai/hivelore.config.json pointing to a cloned hub repo.
|
|
3948
3952
|
`;
|
|
3949
3953
|
function registerInit(program2) {
|
|
3950
3954
|
program2.command("init").description(
|
|
@@ -3954,7 +3958,7 @@ function registerInit(program2) {
|
|
|
3954
3958
|
`which agent bridges to generate: 'auto' (default \u2014 clients detected on this machine/repo, plus AGENTS.md) | 'all' | comma-list.
|
|
3955
3959
|
Available: ${BRIDGE_TARGETS2.join(", ")}. Each carries top memories + block sensors.`,
|
|
3956
3960
|
"auto"
|
|
3957
|
-
).option("--with-ci", "write a GitHub Actions workflow (.github/workflows/
|
|
3961
|
+
).option("--with-ci", "write a GitHub Actions workflow (.github/workflows/hivelore-sync.yml) \u2014 included automatically in autopilot mode").option(
|
|
3958
3962
|
"--manual",
|
|
3959
3963
|
"opt out of autopilot: memories require manual approval, no auto-session recap, no auto-context"
|
|
3960
3964
|
).option(
|
|
@@ -4031,14 +4035,16 @@ function registerInit(program2) {
|
|
|
4031
4035
|
ui.success(`Created ${path13.relative(root, paths.projectContext)}`);
|
|
4032
4036
|
}
|
|
4033
4037
|
}
|
|
4034
|
-
const
|
|
4035
|
-
|
|
4036
|
-
);
|
|
4038
|
+
const resolvedConfig = resolveConfigPath(paths);
|
|
4039
|
+
const configExists = existsSync12(resolvedConfig);
|
|
4037
4040
|
if (!configExists) {
|
|
4038
4041
|
await saveConfig2(paths, autopilot ? AUTOPILOT_DEFAULTS2 : { autopilot: false });
|
|
4039
4042
|
ui.success(
|
|
4040
|
-
`Created .ai
|
|
4043
|
+
`Created .ai/${CONFIG_FILE} (mode: ${autopilot ? "autopilot" : "standard"})`
|
|
4041
4044
|
);
|
|
4045
|
+
} else if (!resolvedConfig.endsWith(CONFIG_FILE)) {
|
|
4046
|
+
await saveConfig2(paths, await loadConfig3(paths));
|
|
4047
|
+
ui.info(`Migrated .ai/${LEGACY_CONFIG_FILE} \u2192 .ai/${CONFIG_FILE}.`);
|
|
4042
4048
|
}
|
|
4043
4049
|
const stacksToSeed = await resolveStacksToSeed(root, wantStack);
|
|
4044
4050
|
if (stacksToSeed.length > 0) {
|
|
@@ -4102,13 +4108,19 @@ function registerInit(program2) {
|
|
|
4102
4108
|
}
|
|
4103
4109
|
const wantCi = opts.withCi || autopilot;
|
|
4104
4110
|
if (wantCi) {
|
|
4105
|
-
const
|
|
4111
|
+
const workflowsDir = path13.join(root, ".github", "workflows");
|
|
4112
|
+
const ciPath = path13.join(workflowsDir, "hivelore-sync.yml");
|
|
4113
|
+
const legacyCiPath = path13.join(workflowsDir, "haive-sync.yml");
|
|
4106
4114
|
if (existsSync12(ciPath)) {
|
|
4107
4115
|
ui.info("CI workflow already exists \u2014 skipped");
|
|
4108
4116
|
} else {
|
|
4109
|
-
await mkdir6(
|
|
4117
|
+
await mkdir6(workflowsDir, { recursive: true });
|
|
4110
4118
|
await writeFile7(ciPath, CI_WORKFLOW, "utf8");
|
|
4111
4119
|
ui.success(`Created ${path13.relative(root, ciPath)}`);
|
|
4120
|
+
if (existsSync12(legacyCiPath)) {
|
|
4121
|
+
await rm(legacyCiPath, { force: true });
|
|
4122
|
+
ui.info(`Migrated ${path13.relative(root, legacyCiPath)} \u2192 ${path13.relative(root, ciPath)}.`);
|
|
4123
|
+
}
|
|
4112
4124
|
}
|
|
4113
4125
|
}
|
|
4114
4126
|
if (autopilot) {
|
|
@@ -4170,7 +4182,11 @@ function registerInit(program2) {
|
|
|
4170
4182
|
"!.ai/.cache/.gitignore",
|
|
4171
4183
|
".ai/.runtime/*",
|
|
4172
4184
|
"!.ai/.runtime/.gitignore",
|
|
4173
|
-
"!.ai/.runtime/README.md"
|
|
4185
|
+
"!.ai/.runtime/README.md",
|
|
4186
|
+
// Machine-local tool-usage telemetry — like .cache/.runtime, it changes on every invocation
|
|
4187
|
+
// and is never team truth (eval baselines exclude local usage counters). Committing it made the
|
|
4188
|
+
// working tree perpetually dirty and forced a stash before every release.
|
|
4189
|
+
".ai/.usage/"
|
|
4174
4190
|
]);
|
|
4175
4191
|
ui.success(`Hivelore initialized at ${root}${autopilot ? " (autopilot mode)" : ""}`);
|
|
4176
4192
|
if (json) {
|
|
@@ -4729,7 +4745,7 @@ import {
|
|
|
4729
4745
|
isDecaying,
|
|
4730
4746
|
isStackPackSeed,
|
|
4731
4747
|
loadCodeMap as loadCodeMap5,
|
|
4732
|
-
loadConfig as
|
|
4748
|
+
loadConfig as loadConfig4,
|
|
4733
4749
|
loadMemoriesFromDir as loadMemoriesFromDir7,
|
|
4734
4750
|
loadSensorLedger,
|
|
4735
4751
|
loadUsageIndex as loadUsageIndex3,
|
|
@@ -4767,7 +4783,7 @@ function registerSync(program2) {
|
|
|
4767
4783
|
const log = (msg) => {
|
|
4768
4784
|
if (!opts.quiet) console.log(msg);
|
|
4769
4785
|
};
|
|
4770
|
-
const config = await
|
|
4786
|
+
const config = await loadConfig4(paths);
|
|
4771
4787
|
const autoApproveDelayHours = config.autoApproveDelayHours ?? null;
|
|
4772
4788
|
const autoPromoteMinReads = config.autoPromoteMinReads ?? DEFAULT_AUTO_PROMOTE_RULE.minReads;
|
|
4773
4789
|
const autoRepair = config.autoRepair ?? {};
|
|
@@ -5433,7 +5449,7 @@ import {
|
|
|
5433
5449
|
suggestSensorSeed,
|
|
5434
5450
|
findProjectRoot as findProjectRoot10,
|
|
5435
5451
|
inferModulesFromPaths as inferModulesFromPaths2,
|
|
5436
|
-
loadConfig as
|
|
5452
|
+
loadConfig as loadConfig5,
|
|
5437
5453
|
loadMemoriesFromDir as loadMemoriesFromDir8,
|
|
5438
5454
|
memoryFilePath as memoryFilePath3,
|
|
5439
5455
|
resolveHaivePaths as resolveHaivePaths9,
|
|
@@ -5473,7 +5489,7 @@ function registerMemoryAdd(memory2) {
|
|
|
5473
5489
|
process.exitCode = 1;
|
|
5474
5490
|
return;
|
|
5475
5491
|
}
|
|
5476
|
-
const config = await
|
|
5492
|
+
const config = await loadConfig5(paths);
|
|
5477
5493
|
const userTags = parseCsv2(opts.tags);
|
|
5478
5494
|
const anchorPaths = parseCsv2(opts.paths ?? opts.files);
|
|
5479
5495
|
const activation = opts.type === "skill" && (opts.activationKeyword || opts.activationGlob || opts.activationAlways) ? {
|
|
@@ -6125,7 +6141,7 @@ import path22 from "path";
|
|
|
6125
6141
|
import "commander";
|
|
6126
6142
|
import {
|
|
6127
6143
|
findProjectRoot as findProjectRoot16,
|
|
6128
|
-
loadConfig as
|
|
6144
|
+
loadConfig as loadConfig6,
|
|
6129
6145
|
resolveHaivePaths as resolveHaivePaths15
|
|
6130
6146
|
} from "@hivelore/core";
|
|
6131
6147
|
async function readDependencyMap(root) {
|
|
@@ -6206,7 +6222,7 @@ function registerMemorySeed(memory2) {
|
|
|
6206
6222
|
ui.success(`Seeded ${total} starter memor${total === 1 ? "y" : "ies"}: ${seededStacks.join(", ")}`);
|
|
6207
6223
|
ui.info("Kept at background priority. Anchor them to a real file (or replace them) to make them high-signal:");
|
|
6208
6224
|
ui.info(" hivelore memory update <id> --paths <key-file> # anchor a seed to a file");
|
|
6209
|
-
const config = await
|
|
6225
|
+
const config = await loadConfig6(paths);
|
|
6210
6226
|
if (config.autopilot || config.autoRepair?.corpus === true) {
|
|
6211
6227
|
const repairs = await applyAutopilotRepairs(root, paths, {
|
|
6212
6228
|
applyConfig: false,
|
|
@@ -6675,7 +6691,7 @@ import {
|
|
|
6675
6691
|
} from "@hivelore/core";
|
|
6676
6692
|
function registerMemoryVerify(memory2) {
|
|
6677
6693
|
memory2.command("verify").description(
|
|
6678
|
-
"Check that memory anchor paths still exist in the current codebase.\n\n A memory is 'stale' when its anchored file or symbol was moved, deleted, or renamed.\n Stale memories are shown with a warning in get_briefing and should be updated or deleted.\n\n hivelore sync runs this automatically. Use this command for on-demand checks or in CI.\n\n CI recommendation: add 'hivelore memory verify' to your
|
|
6694
|
+
"Check that memory anchor paths still exist in the current codebase.\n\n A memory is 'stale' when its anchored file or symbol was moved, deleted, or renamed.\n Stale memories are shown with a warning in get_briefing and should be updated or deleted.\n\n hivelore sync runs this automatically. Use this command for on-demand checks or in CI.\n\n CI recommendation: add 'hivelore memory verify' to your hivelore-sync.yml PR check job\n to catch stale memories before they reach main.\n\n Examples:\n hivelore memory verify # check all, report only\n hivelore memory verify --update # mark stale/fresh on disk\n hivelore memory verify --id 2026-04-28-gotcha-x # check one memory\n"
|
|
6679
6695
|
).option("--id <id>", "verify a single memory by id").option("--all", "verify every memory (default if --id is omitted)").option("--update", "write status=stale or status=validated back to disk").option("--json", "emit machine-readable JSON (for CI / agents)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
6680
6696
|
const root = findProjectRoot24(opts.dir);
|
|
6681
6697
|
const paths = resolveHaivePaths23(root);
|
|
@@ -6962,7 +6978,7 @@ function registerMemoryDigest(program2) {
|
|
|
6962
6978
|
}
|
|
6963
6979
|
|
|
6964
6980
|
// src/commands/session-end.ts
|
|
6965
|
-
import { writeFile as writeFile17, mkdir as mkdir11, readFile as readFile13, rm } from "fs/promises";
|
|
6981
|
+
import { writeFile as writeFile17, mkdir as mkdir11, readFile as readFile13, rm as rm2 } from "fs/promises";
|
|
6966
6982
|
import { existsSync as existsSync32 } from "fs";
|
|
6967
6983
|
import { spawn as spawn2 } from "child_process";
|
|
6968
6984
|
import path29 from "path";
|
|
@@ -6971,7 +6987,7 @@ import {
|
|
|
6971
6987
|
distillFailureObservations,
|
|
6972
6988
|
buildFrontmatter as buildFrontmatter5,
|
|
6973
6989
|
findProjectRoot as findProjectRoot27,
|
|
6974
|
-
loadConfig as
|
|
6990
|
+
loadConfig as loadConfig7,
|
|
6975
6991
|
loadMemoriesFromDir as loadMemoriesFromDir10,
|
|
6976
6992
|
loadPreventionEvents,
|
|
6977
6993
|
readUsageEvents,
|
|
@@ -7324,10 +7340,10 @@ ${note}` : note;
|
|
|
7324
7340
|
const cleanupObservations = async () => {
|
|
7325
7341
|
if (!opts.auto) return;
|
|
7326
7342
|
const obsFile = path29.join(paths.haiveDir, ".cache", "observations.jsonl");
|
|
7327
|
-
if (existsSync32(obsFile)) await
|
|
7343
|
+
if (existsSync32(obsFile)) await rm2(obsFile).catch(() => {
|
|
7328
7344
|
});
|
|
7329
7345
|
};
|
|
7330
|
-
const config = await
|
|
7346
|
+
const config = await loadConfig7(paths);
|
|
7331
7347
|
if (opts.auto && config.autoSessionRecap === false) {
|
|
7332
7348
|
if (config.sessionHandoff) {
|
|
7333
7349
|
const diffStat = await runGit(root, ["diff", "--stat", "HEAD"]).catch(() => "");
|
|
@@ -7954,7 +7970,7 @@ import {
|
|
|
7954
7970
|
computeGatePrecision,
|
|
7955
7971
|
computeEvalTrend,
|
|
7956
7972
|
findProjectRoot as findProjectRoot31,
|
|
7957
|
-
loadConfig as
|
|
7973
|
+
loadConfig as loadConfig8,
|
|
7958
7974
|
loadEvalHistory,
|
|
7959
7975
|
loadPreventionEvents as loadPreventionEvents3,
|
|
7960
7976
|
loadUsageIndex as loadUsageIndex13,
|
|
@@ -8061,7 +8077,7 @@ function registerEval(program2) {
|
|
|
8061
8077
|
const [usage, preventionEvents, config] = await Promise.all([
|
|
8062
8078
|
loadUsageIndex13(paths),
|
|
8063
8079
|
loadPreventionEvents3(paths),
|
|
8064
|
-
|
|
8080
|
+
loadConfig8(paths)
|
|
8065
8081
|
]);
|
|
8066
8082
|
const gatePrecision = computeGatePrecision(
|
|
8067
8083
|
preventionEvents,
|
|
@@ -8389,7 +8405,7 @@ import {
|
|
|
8389
8405
|
aggregateUsage as aggregateUsage2,
|
|
8390
8406
|
buildFrontmatter as buildFrontmatter6,
|
|
8391
8407
|
findProjectRoot as findProjectRoot32,
|
|
8392
|
-
loadConfig as
|
|
8408
|
+
loadConfig as loadConfig9,
|
|
8393
8409
|
loadMemoriesFromDir as loadMemoriesFromDir12,
|
|
8394
8410
|
memoryFilePath as memoryFilePath6,
|
|
8395
8411
|
parseSince as parseSince2,
|
|
@@ -8461,7 +8477,7 @@ function registerMemorySuggest(memory2) {
|
|
|
8461
8477
|
inferred_type: inferType(v.tools, query)
|
|
8462
8478
|
})).sort((a, b) => b.count - a.count);
|
|
8463
8479
|
if (opts.autoSave) {
|
|
8464
|
-
const config = await
|
|
8480
|
+
const config = await loadConfig9(paths);
|
|
8465
8481
|
const topN = Math.max(1, parseInt(opts.topN ?? "3", 10));
|
|
8466
8482
|
const scope = opts.scope === "personal" || opts.scope === "team" ? opts.scope : config.defaultScope ?? "personal";
|
|
8467
8483
|
const status = config.defaultStatus === "validated" ? "validated" : "draft";
|
|
@@ -8680,7 +8696,7 @@ import {
|
|
|
8680
8696
|
findProjectRoot as findProjectRoot34,
|
|
8681
8697
|
getUsage as getUsage8,
|
|
8682
8698
|
retirementSignal as retirementSignal2,
|
|
8683
|
-
loadConfig as
|
|
8699
|
+
loadConfig as loadConfig10,
|
|
8684
8700
|
loadMemoriesFromDir as loadMemoriesFromDir13,
|
|
8685
8701
|
loadUsageIndex as loadUsageIndex14,
|
|
8686
8702
|
resolveHaivePaths as resolveHaivePaths32,
|
|
@@ -8698,7 +8714,7 @@ function registerMemoryArchive(memory2) {
|
|
|
8698
8714
|
process.exitCode = 1;
|
|
8699
8715
|
return;
|
|
8700
8716
|
}
|
|
8701
|
-
const config = await
|
|
8717
|
+
const config = await loadConfig10(paths);
|
|
8702
8718
|
const defaultWindow = config.enforcement?.decayAfterDays ? `${config.enforcement.decayAfterDays}d` : "180d";
|
|
8703
8719
|
const minDays = parseDays(opts.since ?? defaultWindow);
|
|
8704
8720
|
if (minDays === null) {
|
|
@@ -8812,7 +8828,7 @@ import {
|
|
|
8812
8828
|
getUsage as getUsage9,
|
|
8813
8829
|
isStackPackSeed as isStackPackSeed2,
|
|
8814
8830
|
loadCodeMap as loadCodeMap7,
|
|
8815
|
-
loadConfig as
|
|
8831
|
+
loadConfig as loadConfig11,
|
|
8816
8832
|
loadMemoriesFromDirDetailed,
|
|
8817
8833
|
loadSensorLedger as loadSensorLedger2,
|
|
8818
8834
|
loadUsageIndex as loadUsageIndex15,
|
|
@@ -8902,12 +8918,12 @@ var MS_PER_DAY2 = 24 * 60 * 60 * 1e3;
|
|
|
8902
8918
|
function registerDoctor(program2) {
|
|
8903
8919
|
program2.command("doctor").description(
|
|
8904
8920
|
"Analyze the local Hivelore setup and emit actionable recommendations.\n\n Inspects: project-context status, memory health (stale/anchorless/decay/pending),\n code-map freshness, usage log signals (low-hit briefings, repeated empty searches).\n\n Read-only by default. Pass --fix to apply safe autopilot repairs."
|
|
8905
|
-
).option("--json", "emit JSON instead of human-readable output", false).option("--fix", "include suggested fix commands in human output", false).option("--dry-run", "with --fix, show delegated repairs without applying them", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
8921
|
+
).option("--json", "emit JSON instead of human-readable output", false).option("--fix", "include suggested fix commands in human output", false).option("--dry-run", "with --fix, show delegated repairs without applying them", false).option("--all", "show informational findings too (hidden by default to keep doctor actionable)", false).option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
8906
8922
|
const root = findProjectRoot35(opts.dir);
|
|
8907
8923
|
const paths = resolveHaivePaths33(root);
|
|
8908
8924
|
const findings = [];
|
|
8909
8925
|
const repairs = [];
|
|
8910
|
-
const config = await
|
|
8926
|
+
const config = await loadConfig11(paths);
|
|
8911
8927
|
if (!existsSync40(paths.haiveDir)) {
|
|
8912
8928
|
if (opts.json) {
|
|
8913
8929
|
console.log(JSON.stringify({
|
|
@@ -9142,7 +9158,7 @@ function registerDoctor(program2) {
|
|
|
9142
9158
|
}
|
|
9143
9159
|
const astSensorCount = sensorMemories.filter((m) => m.memory.frontmatter.sensor?.kind === "ast").length;
|
|
9144
9160
|
if (astSensorCount > 0) {
|
|
9145
|
-
const { astEngineAvailable: astEngineAvailable2 } = await import("./server-
|
|
9161
|
+
const { astEngineAvailable: astEngineAvailable2 } = await import("./server-Z4M6ERWN.js");
|
|
9146
9162
|
if (!await astEngineAvailable2()) {
|
|
9147
9163
|
findings.push({
|
|
9148
9164
|
severity: "warn",
|
|
@@ -9310,11 +9326,11 @@ function registerDoctor(program2) {
|
|
|
9310
9326
|
severity: "info",
|
|
9311
9327
|
code: "no-autopilot",
|
|
9312
9328
|
message: "Autopilot is OFF \u2014 session recaps are not auto-saved on shutdown.",
|
|
9313
|
-
fix: "Edit .ai/
|
|
9329
|
+
fix: "Edit .ai/hivelore.config.json: set autoSessionEnd: true (or re-run `hivelore init` without --manual)."
|
|
9314
9330
|
});
|
|
9315
9331
|
}
|
|
9316
|
-
findings.push(...await collectInstallFindings(root, "0.
|
|
9317
|
-
findings.push(...await collectMcpRuntimeFindings(paths, "0.
|
|
9332
|
+
findings.push(...await collectInstallFindings(root, "0.51.0"));
|
|
9333
|
+
findings.push(...await collectMcpRuntimeFindings(paths, "0.51.0"));
|
|
9318
9334
|
findings.push(...await collectToolchainFindings(root));
|
|
9319
9335
|
try {
|
|
9320
9336
|
const legacyRaw = execSync("haive-mcp --version", {
|
|
@@ -9322,7 +9338,7 @@ function registerDoctor(program2) {
|
|
|
9322
9338
|
timeout: 3e3,
|
|
9323
9339
|
stdio: ["ignore", "pipe", "ignore"]
|
|
9324
9340
|
}).trim();
|
|
9325
|
-
const cliVersion = "0.
|
|
9341
|
+
const cliVersion = "0.51.0";
|
|
9326
9342
|
if (legacyRaw && legacyRaw !== cliVersion) {
|
|
9327
9343
|
findings.push({
|
|
9328
9344
|
severity: "warn",
|
|
@@ -9449,12 +9465,16 @@ function emit(findings, opts, repairs = []) {
|
|
|
9449
9465
|
"Next actions"
|
|
9450
9466
|
];
|
|
9451
9467
|
const severityOrder = ["error", "warn", "info"];
|
|
9468
|
+
const isVisible = (f) => f.severity !== "info" || opts.all === true || f.alwaysShow === true;
|
|
9469
|
+
let hiddenInfo = 0;
|
|
9452
9470
|
for (const section2 of sectionOrder) {
|
|
9453
9471
|
const sectionFindings = classified.filter((f) => f.section === section2);
|
|
9454
|
-
|
|
9472
|
+
const visible = sectionFindings.filter(isVisible);
|
|
9473
|
+
hiddenInfo += sectionFindings.length - visible.length;
|
|
9474
|
+
if (visible.length === 0) continue;
|
|
9455
9475
|
console.log(ui.bold(section2));
|
|
9456
9476
|
for (const sev of severityOrder) {
|
|
9457
|
-
for (const f of
|
|
9477
|
+
for (const f of visible.filter((x) => x.severity === sev)) {
|
|
9458
9478
|
const icon = sev === "error" ? ui.red("\u2717") : sev === "warn" ? ui.yellow("\u26A0") : ui.dim("\u2139");
|
|
9459
9479
|
console.log(`${icon} ${ui.bold(f.code)} ${f.message}`);
|
|
9460
9480
|
if (opts.fix && f.fix) {
|
|
@@ -9466,6 +9486,9 @@ function emit(findings, opts, repairs = []) {
|
|
|
9466
9486
|
}
|
|
9467
9487
|
console.log();
|
|
9468
9488
|
}
|
|
9489
|
+
if (hiddenInfo > 0) {
|
|
9490
|
+
ui.info(`${hiddenInfo} informational finding(s) hidden \u2014 run \`hivelore doctor --all\` to show them.`);
|
|
9491
|
+
}
|
|
9469
9492
|
if (repairs.length > 0) {
|
|
9470
9493
|
console.log(ui.bold("Autopilot repairs applied"));
|
|
9471
9494
|
for (const repair of repairs) console.log(` ${ui.dim("\u2713")} ${repair.message}`);
|
|
@@ -9572,6 +9595,7 @@ async function collectHarnessCoverageFindings(codeMap, memories) {
|
|
|
9572
9595
|
findings.push({
|
|
9573
9596
|
severity: "info",
|
|
9574
9597
|
code: "harness-coverage",
|
|
9598
|
+
alwaysShow: true,
|
|
9575
9599
|
coverage_percent: pct2,
|
|
9576
9600
|
message: `${covered}/${total} code-map files have validated memory anchors (${pct2}%). ` + coverageDesc + uncoveredHint,
|
|
9577
9601
|
fix: pct2 < 50 && total > 10 ? `hivelore memory save --type gotcha|convention|architecture --paths <key-file> --scope team` : void 0,
|
|
@@ -9599,6 +9623,7 @@ function collectBehaviourCoverageFindings(codeMap, memories) {
|
|
|
9599
9623
|
return [{
|
|
9600
9624
|
severity: "info",
|
|
9601
9625
|
code: "behaviour-coverage",
|
|
9626
|
+
alwaysShow: true,
|
|
9602
9627
|
section: "Protection",
|
|
9603
9628
|
coverage_percent: Math.round(cov.areasWithOracle.length / cov.mainAreas.length * 100),
|
|
9604
9629
|
message: `Behaviour harness: ${renderBehaviourCoverageLine2(cov)}.` + uncoveredHint,
|
|
@@ -9951,7 +9976,7 @@ import "commander";
|
|
|
9951
9976
|
import {
|
|
9952
9977
|
antiPatternGateParams,
|
|
9953
9978
|
findProjectRoot as findProjectRoot36,
|
|
9954
|
-
loadConfig as
|
|
9979
|
+
loadConfig as loadConfig12,
|
|
9955
9980
|
resolveHaivePaths as resolveHaivePaths34
|
|
9956
9981
|
} from "@hivelore/core";
|
|
9957
9982
|
function registerPrecommit(program2) {
|
|
@@ -9967,7 +9992,7 @@ function registerPrecommit(program2) {
|
|
|
9967
9992
|
const root = findProjectRoot36(opts.dir);
|
|
9968
9993
|
const paths = resolveHaivePaths34(root);
|
|
9969
9994
|
const ctx = { paths };
|
|
9970
|
-
const config = await
|
|
9995
|
+
const config = await loadConfig12(paths);
|
|
9971
9996
|
const gate = config.enforcement?.antiPatternGate ?? "anchored";
|
|
9972
9997
|
const gateParams = antiPatternGateParams(gate);
|
|
9973
9998
|
const blockOn = opts.blockOn ?? gateParams.block_on;
|
|
@@ -10116,7 +10141,7 @@ function registerResolveProject(program2) {
|
|
|
10116
10141
|
// src/commands/enforce.ts
|
|
10117
10142
|
import { execFile as execFile5, execFileSync as execFileSync2, spawn as spawn4 } from "child_process";
|
|
10118
10143
|
import { existsSync as existsSync42, statSync as statSync4 } from "fs";
|
|
10119
|
-
import { chmod, mkdir as mkdir16, readFile as readFile18, readdir as readdir4, rm as
|
|
10144
|
+
import { chmod, mkdir as mkdir16, readFile as readFile18, readdir as readdir4, rm as rm3, writeFile as writeFile25 } from "fs/promises";
|
|
10120
10145
|
import path40 from "path";
|
|
10121
10146
|
import { promisify as promisify5 } from "util";
|
|
10122
10147
|
import "commander";
|
|
@@ -10137,7 +10162,7 @@ import {
|
|
|
10137
10162
|
hasRecentBriefingMarker,
|
|
10138
10163
|
isFreshIsoDate,
|
|
10139
10164
|
isRetiredMemory as isRetiredMemory2,
|
|
10140
|
-
loadConfig as
|
|
10165
|
+
loadConfig as loadConfig13,
|
|
10141
10166
|
detectAgentContext,
|
|
10142
10167
|
loadMemoriesFromDir as loadMemoriesFromDir15,
|
|
10143
10168
|
loadSensorLedger as loadSensorLedger3,
|
|
@@ -10435,7 +10460,7 @@ function registerEnforce(program2) {
|
|
|
10435
10460
|
const root = findProjectRoot37(opts.dir);
|
|
10436
10461
|
const paths = resolveHaivePaths35(root);
|
|
10437
10462
|
await mkdir16(paths.haiveDir, { recursive: true });
|
|
10438
|
-
const current = await
|
|
10463
|
+
const current = await loadConfig13(paths);
|
|
10439
10464
|
await saveConfig3(paths, {
|
|
10440
10465
|
...current,
|
|
10441
10466
|
enforcement: {
|
|
@@ -10452,7 +10477,7 @@ function registerEnforce(program2) {
|
|
|
10452
10477
|
policyPacks: ["architecture", "gotchas", "security", "domain", "release"]
|
|
10453
10478
|
}
|
|
10454
10479
|
});
|
|
10455
|
-
ui.success("Hivelore strict enforcement enabled in .ai/
|
|
10480
|
+
ui.success("Hivelore strict enforcement enabled in .ai/hivelore.config.json");
|
|
10456
10481
|
if (opts.git !== false) await installGitEnforcement(root);
|
|
10457
10482
|
if (opts.ci !== false) await installCiEnforcement(root);
|
|
10458
10483
|
if (opts.claude !== false) {
|
|
@@ -10607,7 +10632,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
10607
10632
|
const paths = resolveHaivePaths35(root);
|
|
10608
10633
|
if (!existsSync42(paths.haiveDir)) return;
|
|
10609
10634
|
if (!isWriteLikeTool(payload)) return;
|
|
10610
|
-
const config = await
|
|
10635
|
+
const config = await loadConfig13(paths);
|
|
10611
10636
|
if (config.enforcement?.requireBriefingFirst === false) return;
|
|
10612
10637
|
const gate = config.enforcement?.preEditGate ?? "advise";
|
|
10613
10638
|
const targetFiles = extractToolPaths(payload, root);
|
|
@@ -10621,7 +10646,7 @@ ${briefing.project_context.content.slice(0, 1800)}`);
|
|
|
10621
10646
|
const contextText = buildPreEditContext(payload.tool_name ?? "write tool", targetFiles, missing, hasMarker);
|
|
10622
10647
|
if (gate === "block") {
|
|
10623
10648
|
console.error(
|
|
10624
|
-
contextText + '\n\nThe relevant context is now recorded \u2014 re-issue the same edit to proceed (no `hivelore briefing` command needed). To make this advisory instead of blocking, set `{ "enforcement": { "preEditGate": "advise" } }` in .ai/
|
|
10649
|
+
contextText + '\n\nThe relevant context is now recorded \u2014 re-issue the same edit to proceed (no `hivelore briefing` command needed). To make this advisory instead of blocking, set `{ "enforcement": { "preEditGate": "advise" } }` in .ai/hivelore.config.json.'
|
|
10625
10650
|
);
|
|
10626
10651
|
process.exit(2);
|
|
10627
10652
|
}
|
|
@@ -10687,7 +10712,7 @@ async function buildFinishReport(dir) {
|
|
|
10687
10712
|
const root = findProjectRoot37(dir);
|
|
10688
10713
|
const paths = resolveHaivePaths35(root);
|
|
10689
10714
|
const initialized = existsSync42(paths.haiveDir);
|
|
10690
|
-
const config = initialized ? await
|
|
10715
|
+
const config = initialized ? await loadConfig13(paths) : {};
|
|
10691
10716
|
const mode = config.enforcement?.mode ?? "strict";
|
|
10692
10717
|
const findings = [];
|
|
10693
10718
|
if (!initialized) {
|
|
@@ -10708,7 +10733,7 @@ async function buildFinishReport(dir) {
|
|
|
10708
10733
|
}
|
|
10709
10734
|
findings.push(...await checkFailureCapture(paths, config));
|
|
10710
10735
|
findings.push(...await checkPostIncidentScaffolds(paths));
|
|
10711
|
-
findings.push(...await checkBootstrapComplete(paths, config, true));
|
|
10736
|
+
findings.push(...await checkBootstrapComplete(paths, config, true, "pre-push"));
|
|
10712
10737
|
const status = await getGitSyncStatus(root);
|
|
10713
10738
|
if (!status.available) {
|
|
10714
10739
|
findings.push({
|
|
@@ -10966,7 +10991,7 @@ async function runWithEnforcement(command, args, opts) {
|
|
|
10966
10991
|
HAIVE_ENFORCEMENT: "strict",
|
|
10967
10992
|
HAIVE_AGENT: "1",
|
|
10968
10993
|
// wrapped process is an agent — process gates bind it (detectAgentContext)
|
|
10969
|
-
|
|
10994
|
+
HIVELORE_TOOL_PROFILE: process.env.HIVELORE_TOOL_PROFILE ?? process.env.HAIVE_TOOL_PROFILE ?? "enforcement"
|
|
10970
10995
|
}
|
|
10971
10996
|
});
|
|
10972
10997
|
await new Promise((resolve, reject) => {
|
|
@@ -11054,7 +11079,7 @@ function looksLikeProductionCode(file) {
|
|
|
11054
11079
|
if (/(^|\/)(test|tests|__tests__|__mocks__|e2e|fixtures)(\/|$)/.test(f)) return false;
|
|
11055
11080
|
return PRODUCTION_CODE_EXT.test(f);
|
|
11056
11081
|
}
|
|
11057
|
-
async function checkBootstrapComplete(paths, config, productionCodeChanged) {
|
|
11082
|
+
async function checkBootstrapComplete(paths, config, productionCodeChanged, stage) {
|
|
11058
11083
|
const gate = config.enforcement?.bootstrapGate ?? "block";
|
|
11059
11084
|
if (gate === "off") return [];
|
|
11060
11085
|
let projectContextRaw = "";
|
|
@@ -11080,7 +11105,8 @@ async function checkBootstrapComplete(paths, config, productionCodeChanged) {
|
|
|
11080
11105
|
}];
|
|
11081
11106
|
}
|
|
11082
11107
|
const hasCodeAreas = assessment.metrics.mainAreas > 0;
|
|
11083
|
-
const
|
|
11108
|
+
const enforcedStage = stage === "pre-push" || stage === "ci";
|
|
11109
|
+
const blocking = gate === "block" && hasCodeAreas && productionCodeChanged && enforcedStage;
|
|
11084
11110
|
const severity = blocking ? "error" : hasCodeAreas ? "warn" : "info";
|
|
11085
11111
|
return [{
|
|
11086
11112
|
severity,
|
|
@@ -11095,7 +11121,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
11095
11121
|
const root = findProjectRoot37(dir);
|
|
11096
11122
|
const paths = resolveHaivePaths35(root);
|
|
11097
11123
|
const initialized = existsSync42(paths.haiveDir);
|
|
11098
|
-
const config = initialized ? await
|
|
11124
|
+
const config = initialized ? await loadConfig13(paths) : {};
|
|
11099
11125
|
if (initialized) {
|
|
11100
11126
|
await applyLightweightRepairs(root, paths);
|
|
11101
11127
|
if (stage === "pre-commit") await stageResyncedArtifacts(root, paths);
|
|
@@ -11128,7 +11154,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
11128
11154
|
findings: [{ severity: "info", code: "enforcement-off", message: "Hivelore enforcement is disabled." }]
|
|
11129
11155
|
});
|
|
11130
11156
|
}
|
|
11131
|
-
findings.push(...await inspectIntegrationVersions(root, "0.
|
|
11157
|
+
findings.push(...await inspectIntegrationVersions(root, "0.51.0"));
|
|
11132
11158
|
if (config.enforcement?.requireBriefingFirst !== false && stage !== "ci") {
|
|
11133
11159
|
const hasBriefing = await hasRecentBriefingMarker(paths, sessionId);
|
|
11134
11160
|
findings.push(hasBriefing ? { severity: "ok", code: "briefing-loaded", message: "A recent Hivelore briefing marker exists." } : {
|
|
@@ -11176,7 +11202,7 @@ async function buildEnforcementReport(dir, stage, sessionId) {
|
|
|
11176
11202
|
}
|
|
11177
11203
|
{
|
|
11178
11204
|
const changed = await getChangedFiles(root, stage).catch(() => []);
|
|
11179
|
-
findings.push(...await checkBootstrapComplete(paths, config, changed.some(looksLikeProductionCode)));
|
|
11205
|
+
findings.push(...await checkBootstrapComplete(paths, config, changed.some(looksLikeProductionCode), stage));
|
|
11180
11206
|
}
|
|
11181
11207
|
const agentContext = detectAgentContext();
|
|
11182
11208
|
const relaxForHuman = stage !== "ci" && !agentContext.agent && (config.enforcement?.humanCommits ?? "relaxed") === "relaxed";
|
|
@@ -11339,7 +11365,7 @@ async function verifyDecisionCoverage(paths, stage, sessionId) {
|
|
|
11339
11365
|
}];
|
|
11340
11366
|
}
|
|
11341
11367
|
if (stage === "pre-commit" || stage === "pre-push") {
|
|
11342
|
-
const cfg = await
|
|
11368
|
+
const cfg = await loadConfig13(paths).catch(() => ({}));
|
|
11343
11369
|
if (cfg.enforcement?.autoBrief !== false) {
|
|
11344
11370
|
await writeBriefingMarker2(paths, {
|
|
11345
11371
|
sessionId,
|
|
@@ -11412,9 +11438,8 @@ async function runPrecommitPolicy(paths, gate, stage, config) {
|
|
|
11412
11438
|
semantic: true
|
|
11413
11439
|
}, { paths });
|
|
11414
11440
|
const sensorFindings = await runSensorGate(paths, snapshot.diff, stage);
|
|
11415
|
-
const
|
|
11416
|
-
|
|
11417
|
-
);
|
|
11441
|
+
const showReview = config.enforcement?.reviewMatches === true || gate === "review";
|
|
11442
|
+
const reviewWarnings = showReview ? result.warnings.filter((w) => w.level === "review" && !w.reasons.includes("sensor")) : [];
|
|
11418
11443
|
const REVIEW_SEEN_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
11419
11444
|
const reviewSeenFile = path40.join(paths.runtimeDir, "enforcement", "review-seen.json");
|
|
11420
11445
|
let reviewSeen = {};
|
|
@@ -11635,7 +11660,7 @@ async function runSensorGate(paths, diff, stage) {
|
|
|
11635
11660
|
}
|
|
11636
11661
|
}
|
|
11637
11662
|
}
|
|
11638
|
-
const config = await
|
|
11663
|
+
const config = await loadConfig13(paths).catch(() => ({}));
|
|
11639
11664
|
if (config?.enforcement?.runCommandSensors === true) {
|
|
11640
11665
|
const changedPaths = targets.map((t) => t.path).filter(Boolean);
|
|
11641
11666
|
const specs = selectCommandSensors(scannable, changedPaths).filter((sp) => !seen.has(sp.memory_id));
|
|
@@ -11720,12 +11745,13 @@ command: ${run.command} (exit ${run.exit_code}, ${run.duration_ms}ms)${outputBlo
|
|
|
11720
11745
|
}
|
|
11721
11746
|
return findings;
|
|
11722
11747
|
} catch (err) {
|
|
11748
|
+
const inCi = stage === "ci";
|
|
11723
11749
|
return [{
|
|
11724
|
-
severity: "warn",
|
|
11750
|
+
severity: inCi ? "error" : "warn",
|
|
11725
11751
|
code: "sensor-gate-errored",
|
|
11726
|
-
message:
|
|
11727
|
-
fix: "Run `hivelore sensors check` to reproduce, and `hivelore doctor` for setup drift.
|
|
11728
|
-
impact:
|
|
11752
|
+
message: `\u26D4 The sensor gate itself errored, so NO sensors were evaluated on this diff \u2014 the deterministic protection was OFF for this run${inCi ? " (failing CI: a passing gate that evaluated nothing is not trustworthy)" : ""}: ` + `${err instanceof Error ? err.message : String(err)}`.slice(0, 500),
|
|
11753
|
+
fix: "Run `hivelore sensors check` to reproduce (set HIVELORE_DEBUG=1 for the stack), and `hivelore doctor` for setup drift. Protection is OFF until this is fixed.",
|
|
11754
|
+
impact: inCi ? 60 : 25
|
|
11729
11755
|
}];
|
|
11730
11756
|
}
|
|
11731
11757
|
}
|
|
@@ -11762,7 +11788,7 @@ async function cleanupRuntimeDir(runtimeDir) {
|
|
|
11762
11788
|
removed += await cleanupEnforcementDir(path40.join(runtimeDir, entry.name));
|
|
11763
11789
|
continue;
|
|
11764
11790
|
}
|
|
11765
|
-
await
|
|
11791
|
+
await rm3(path40.join(runtimeDir, entry.name), { recursive: true, force: true });
|
|
11766
11792
|
removed++;
|
|
11767
11793
|
}
|
|
11768
11794
|
await writeFile25(path40.join(runtimeDir, ".gitignore"), "*\n!.gitignore\n!README.md\n", "utf8");
|
|
@@ -11781,7 +11807,7 @@ async function cleanupCacheDir(cacheDir) {
|
|
|
11781
11807
|
const entries = await readdir4(cacheDir, { withFileTypes: true }).catch(() => []);
|
|
11782
11808
|
for (const entry of entries) {
|
|
11783
11809
|
if (entry.name === ".gitignore") continue;
|
|
11784
|
-
await
|
|
11810
|
+
await rm3(path40.join(cacheDir, entry.name), { recursive: true, force: true });
|
|
11785
11811
|
removed++;
|
|
11786
11812
|
}
|
|
11787
11813
|
await writeFile25(path40.join(cacheDir, ".gitignore"), "*\n!.gitignore\n", "utf8");
|
|
@@ -11792,7 +11818,7 @@ async function cleanupEnforcementDir(enforcementDir) {
|
|
|
11792
11818
|
const entries = await readdir4(enforcementDir, { withFileTypes: true }).catch(() => []);
|
|
11793
11819
|
for (const entry of entries) {
|
|
11794
11820
|
if (entry.name === "briefings") continue;
|
|
11795
|
-
await
|
|
11821
|
+
await rm3(path40.join(enforcementDir, entry.name), { recursive: true, force: true });
|
|
11796
11822
|
removed++;
|
|
11797
11823
|
}
|
|
11798
11824
|
return removed;
|
|
@@ -11909,8 +11935,8 @@ async function getPolicyDiffSnapshot(root, stage) {
|
|
|
11909
11935
|
return { diff: "", paths: [], source: "none" };
|
|
11910
11936
|
}
|
|
11911
11937
|
async function resolveCiDiffRange(root) {
|
|
11912
|
-
const explicitBase = cleanGitSha(process.env.HAIVE_BASE_SHA ?? process.env.HAIVE_BASE_REF);
|
|
11913
|
-
const explicitHead = cleanGitSha(process.env.HAIVE_HEAD_SHA ?? process.env.GITHUB_SHA) ?? "HEAD";
|
|
11938
|
+
const explicitBase = cleanGitSha(process.env.HIVELORE_BASE_SHA ?? process.env.HIVELORE_BASE_REF ?? process.env.HAIVE_BASE_SHA ?? process.env.HAIVE_BASE_REF);
|
|
11939
|
+
const explicitHead = cleanGitSha(process.env.HIVELORE_HEAD_SHA ?? process.env.HAIVE_HEAD_SHA ?? process.env.GITHUB_SHA) ?? "HEAD";
|
|
11914
11940
|
if (explicitBase && await gitCommitExists(root, explicitBase)) {
|
|
11915
11941
|
return `${explicitBase}...${explicitHead}`;
|
|
11916
11942
|
}
|
|
@@ -12258,7 +12284,6 @@ async function installGitEnforcement(root) {
|
|
|
12258
12284
|
await mkdir16(hooksDir, { recursive: true });
|
|
12259
12285
|
const resolveCli = `_hivelore() {
|
|
12260
12286
|
if command -v hivelore >/dev/null 2>&1; then hivelore "$@"
|
|
12261
|
-
elif command -v haive >/dev/null 2>&1; then haive "$@"
|
|
12262
12287
|
else return 0
|
|
12263
12288
|
fi
|
|
12264
12289
|
}`;
|
|
@@ -12325,20 +12350,30 @@ ${hook.body}`, "utf8");
|
|
|
12325
12350
|
ui.success("Installed git hooks: pre-commit, pre-push, commit-msg (blocking) + post-merge, post-rewrite (sync)");
|
|
12326
12351
|
}
|
|
12327
12352
|
async function installCiEnforcement(root) {
|
|
12328
|
-
const
|
|
12329
|
-
|
|
12353
|
+
const workflowsDir = path40.join(root, ".github", "workflows");
|
|
12354
|
+
const workflowPath = path40.join(workflowsDir, "hivelore-enforcement.yml");
|
|
12355
|
+
const legacyPath = path40.join(workflowsDir, "haive-enforcement.yml");
|
|
12356
|
+
await mkdir16(workflowsDir, { recursive: true });
|
|
12330
12357
|
const workflow = renderCiEnforcementWorkflow();
|
|
12331
|
-
|
|
12332
|
-
|
|
12333
|
-
const
|
|
12334
|
-
const
|
|
12335
|
-
const
|
|
12336
|
-
const endAt = existing.indexOf(
|
|
12358
|
+
const source = existsSync42(workflowPath) ? workflowPath : existsSync42(legacyPath) ? legacyPath : null;
|
|
12359
|
+
if (source) {
|
|
12360
|
+
const existing = await readFile18(source, "utf8");
|
|
12361
|
+
const startAt = Math.max(existing.indexOf("# hivelore:enforcement-workflow:start"), existing.indexOf("# haive:enforcement-workflow:start"));
|
|
12362
|
+
const endMarker = existing.includes("# hivelore:enforcement-workflow:end") ? "# hivelore:enforcement-workflow:end" : "# haive:enforcement-workflow:end";
|
|
12363
|
+
const endAt = existing.indexOf(endMarker);
|
|
12337
12364
|
if (startAt >= 0 && endAt > startAt) {
|
|
12338
|
-
await writeFile25(workflowPath, existing.slice(0, startAt) + workflow + existing.slice(endAt +
|
|
12365
|
+
await writeFile25(workflowPath, existing.slice(0, startAt) + workflow + existing.slice(endAt + endMarker.length), "utf8");
|
|
12339
12366
|
ui.success(`Updated ${path40.relative(root, workflowPath)} managed block`);
|
|
12340
|
-
} else {
|
|
12367
|
+
} else if (source === workflowPath) {
|
|
12341
12368
|
ui.info("GitHub Actions enforcement workflow already exists without Hivelore markers \u2014 preserved");
|
|
12369
|
+
return;
|
|
12370
|
+
} else {
|
|
12371
|
+
await writeFile25(workflowPath, workflow, "utf8");
|
|
12372
|
+
ui.success(`Created ${path40.relative(root, workflowPath)}`);
|
|
12373
|
+
}
|
|
12374
|
+
if (source === legacyPath) {
|
|
12375
|
+
await rm3(legacyPath, { force: true });
|
|
12376
|
+
ui.info(`Migrated ${path40.relative(root, legacyPath)} \u2192 ${path40.relative(root, workflowPath)}.`);
|
|
12342
12377
|
}
|
|
12343
12378
|
return;
|
|
12344
12379
|
}
|
|
@@ -12346,8 +12381,8 @@ async function installCiEnforcement(root) {
|
|
|
12346
12381
|
ui.success(`Created ${path40.relative(root, workflowPath)}`);
|
|
12347
12382
|
}
|
|
12348
12383
|
function renderCiEnforcementWorkflow() {
|
|
12349
|
-
return `#
|
|
12350
|
-
name:
|
|
12384
|
+
return `# hivelore:enforcement-workflow:start
|
|
12385
|
+
name: hivelore-enforcement
|
|
12351
12386
|
|
|
12352
12387
|
on:
|
|
12353
12388
|
pull_request:
|
|
@@ -12355,7 +12390,7 @@ on:
|
|
|
12355
12390
|
branches: [main, master]
|
|
12356
12391
|
|
|
12357
12392
|
jobs:
|
|
12358
|
-
|
|
12393
|
+
hivelore-enforcement:
|
|
12359
12394
|
runs-on: ubuntu-latest
|
|
12360
12395
|
permissions:
|
|
12361
12396
|
contents: read
|
|
@@ -12372,11 +12407,11 @@ jobs:
|
|
|
12372
12407
|
- name: Enforce Hivelore policy
|
|
12373
12408
|
id: gate
|
|
12374
12409
|
env:
|
|
12375
|
-
|
|
12376
|
-
|
|
12410
|
+
HIVELORE_BASE_SHA: \${{ github.event.pull_request.base.sha || github.event.before }}
|
|
12411
|
+
HIVELORE_HEAD_SHA: \${{ github.event.pull_request.head.sha || github.sha }}
|
|
12377
12412
|
run: |
|
|
12378
12413
|
set +e
|
|
12379
|
-
hivelore enforce ci --json > "$RUNNER_TEMP/
|
|
12414
|
+
hivelore enforce ci --json > "$RUNNER_TEMP/hivelore-gate.json"
|
|
12380
12415
|
echo "exit_code=$?" >> "$GITHUB_OUTPUT"
|
|
12381
12416
|
exit 0
|
|
12382
12417
|
- name: Upsert prevention receipt
|
|
@@ -12387,7 +12422,7 @@ jobs:
|
|
|
12387
12422
|
run: |
|
|
12388
12423
|
if [ -z "\${GH_TOKEN:-}" ] || ! command -v gh >/dev/null 2>&1; then exit 0; fi
|
|
12389
12424
|
receipt="$(hivelore stats receipt --since 7d --json 2>/dev/null)" || exit 0
|
|
12390
|
-
gate="$(cat "$RUNNER_TEMP/
|
|
12425
|
+
gate="$(cat "$RUNNER_TEMP/hivelore-gate.json" 2>/dev/null)" || gate='{"findings":[]}'
|
|
12391
12426
|
body="$(jq -nr --arg marker '<!-- haive:prevention-receipt -->' --argjson receipt "$receipt" --argjson gate "$gate" '
|
|
12392
12427
|
$marker + "
|
|
12393
12428
|
## Hivelore prevention receipt
|
|
@@ -12416,7 +12451,7 @@ Weekly total: **" + ($receipt.total|tostring) + "** refused; previous window: **
|
|
|
12416
12451
|
- name: Fail when enforcement blocked
|
|
12417
12452
|
if: steps.gate.outputs.exit_code != '0'
|
|
12418
12453
|
run: exit \${{ steps.gate.outputs.exit_code }}
|
|
12419
|
-
#
|
|
12454
|
+
# hivelore:enforcement-workflow:end
|
|
12420
12455
|
`;
|
|
12421
12456
|
}
|
|
12422
12457
|
var CONTENT_CATCH_CODES = /* @__PURE__ */ new Set(["sensor-block", "precommit-policy-block"]);
|
|
@@ -12627,9 +12662,43 @@ import { readFile as readFile19, writeFile as writeFile26 } from "fs/promises";
|
|
|
12627
12662
|
import path41 from "path";
|
|
12628
12663
|
import "commander";
|
|
12629
12664
|
import { findProjectRoot as findProjectRoot38 } from "@hivelore/core";
|
|
12630
|
-
import { execFile as execFile6 } from "child_process";
|
|
12665
|
+
import { execFile as execFile6, spawn as spawn5 } from "child_process";
|
|
12631
12666
|
import { promisify as promisify6 } from "util";
|
|
12632
12667
|
var exec4 = promisify6(execFile6);
|
|
12668
|
+
async function createAndPushTag(root, push) {
|
|
12669
|
+
const version = await readCurrentVersion(root);
|
|
12670
|
+
for (const rel of VERSION_FILES2.slice(1)) {
|
|
12671
|
+
const file = path41.join(root, rel);
|
|
12672
|
+
if (!existsSync43(file)) continue;
|
|
12673
|
+
const v = JSON.parse(await readFile19(file, "utf8")).version;
|
|
12674
|
+
if (v !== version) {
|
|
12675
|
+
ui.error(`${rel} is at ${v}, root at ${version} \u2014 lockstep broken; run \`hivelore release bump\` first.`);
|
|
12676
|
+
process.exitCode = 1;
|
|
12677
|
+
return null;
|
|
12678
|
+
}
|
|
12679
|
+
}
|
|
12680
|
+
const dirty = (await exec4("git", ["status", "--porcelain"], { cwd: root })).stdout.trim();
|
|
12681
|
+
if (dirty.length > 0) {
|
|
12682
|
+
ui.error("Working tree is not clean \u2014 commit the bump before tagging.");
|
|
12683
|
+
process.exitCode = 1;
|
|
12684
|
+
return null;
|
|
12685
|
+
}
|
|
12686
|
+
const tag = `v${version}`;
|
|
12687
|
+
const existing = (await exec4("git", ["tag", "--list", tag], { cwd: root })).stdout.trim();
|
|
12688
|
+
if (existing) {
|
|
12689
|
+
ui.error(`Tag ${tag} already exists \u2014 bump the version first.`);
|
|
12690
|
+
process.exitCode = 1;
|
|
12691
|
+
return null;
|
|
12692
|
+
}
|
|
12693
|
+
await exec4("git", ["tag", tag], { cwd: root });
|
|
12694
|
+
ui.success(`Created ${tag} at HEAD.`);
|
|
12695
|
+
if (push) {
|
|
12696
|
+
await exec4("git", ["push"], { cwd: root });
|
|
12697
|
+
await exec4("git", ["push", "origin", tag], { cwd: root });
|
|
12698
|
+
ui.success(`Pushed branch and ${tag}.`);
|
|
12699
|
+
}
|
|
12700
|
+
return tag;
|
|
12701
|
+
}
|
|
12633
12702
|
var VERSION_FILES2 = [
|
|
12634
12703
|
"package.json",
|
|
12635
12704
|
"packages/core/package.json",
|
|
@@ -12695,38 +12764,45 @@ ${heading}
|
|
|
12695
12764
|
});
|
|
12696
12765
|
release.command("tag").description("Create vX.Y.Z at HEAD (from the lockstep version), push the branch and that one tag.").option("--no-push", "create the tag locally without pushing").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
12697
12766
|
const root = findProjectRoot38(opts.dir);
|
|
12698
|
-
const
|
|
12699
|
-
|
|
12700
|
-
|
|
12701
|
-
|
|
12702
|
-
|
|
12703
|
-
|
|
12704
|
-
|
|
12767
|
+
const tag = await createAndPushTag(root, opts.push !== false);
|
|
12768
|
+
if (tag && opts.push !== false) {
|
|
12769
|
+
ui.info("Next: `hivelore enforce finish --wait` (polls CI), then publish via `pnpm run publish:all` (human step).");
|
|
12770
|
+
}
|
|
12771
|
+
});
|
|
12772
|
+
release.command("ship").description("One-shot release close-out: git pull --rebase \u2192 tag + push \u2192 poll CI (enforce finish --wait). Run it after committing the bump.").option("--no-push", "tag locally without pushing (skips the CI wait)").option("-d, --dir <dir>", "project root").action(async (opts) => {
|
|
12773
|
+
const root = findProjectRoot38(opts.dir);
|
|
12774
|
+
const push = opts.push !== false;
|
|
12775
|
+
if (push) {
|
|
12776
|
+
try {
|
|
12777
|
+
await exec4("git", ["pull", "--rebase"], { cwd: root });
|
|
12778
|
+
ui.success("Rebased on origin.");
|
|
12779
|
+
} catch (err) {
|
|
12780
|
+
ui.error(`git pull --rebase failed \u2014 resolve it, then re-run \`hivelore release ship\`:
|
|
12781
|
+
${err instanceof Error ? err.message : String(err)}`);
|
|
12705
12782
|
process.exitCode = 1;
|
|
12706
12783
|
return;
|
|
12707
12784
|
}
|
|
12708
12785
|
}
|
|
12709
|
-
const
|
|
12710
|
-
if (
|
|
12711
|
-
|
|
12712
|
-
|
|
12786
|
+
const tag = await createAndPushTag(root, push);
|
|
12787
|
+
if (!tag) return;
|
|
12788
|
+
if (!push) {
|
|
12789
|
+
ui.info(`Created ${tag} locally (no push). Run \`hivelore release ship\` without --no-push to publish + poll CI.`);
|
|
12713
12790
|
return;
|
|
12714
12791
|
}
|
|
12715
|
-
|
|
12716
|
-
const
|
|
12717
|
-
|
|
12718
|
-
|
|
12719
|
-
|
|
12792
|
+
ui.info("Polling CI (enforce finish --wait)\u2026");
|
|
12793
|
+
const code = await new Promise((resolve) => {
|
|
12794
|
+
const child = spawn5(process.execPath, [process.argv[1], "enforce", "finish", "--wait", "--dir", root], {
|
|
12795
|
+
stdio: "inherit"
|
|
12796
|
+
});
|
|
12797
|
+
child.on("close", (c) => resolve(c ?? 1));
|
|
12798
|
+
child.on("error", () => resolve(1));
|
|
12799
|
+
});
|
|
12800
|
+
if (code !== 0) {
|
|
12801
|
+
ui.error("Shipped the tag, but the finish gate/CI did not pass \u2014 see the output above.");
|
|
12802
|
+
process.exitCode = code;
|
|
12720
12803
|
return;
|
|
12721
12804
|
}
|
|
12722
|
-
|
|
12723
|
-
ui.success(`Created ${tag} at HEAD.`);
|
|
12724
|
-
if (opts.push !== false) {
|
|
12725
|
-
await exec4("git", ["push"], { cwd: root });
|
|
12726
|
-
await exec4("git", ["push", "origin", tag], { cwd: root });
|
|
12727
|
-
ui.success(`Pushed branch and ${tag}.`);
|
|
12728
|
-
ui.info("Next: `hivelore enforce finish --wait` (polls CI), then publish via `pnpm run publish:all` (human step).");
|
|
12729
|
-
}
|
|
12805
|
+
ui.success(`Shipped ${tag} \u2014 CI green. npm publication stays a human step (\`pnpm run publish:all\`).`);
|
|
12730
12806
|
});
|
|
12731
12807
|
}
|
|
12732
12808
|
|
|
@@ -12757,7 +12833,7 @@ import {
|
|
|
12757
12833
|
findProjectRoot as findProjectRoot39,
|
|
12758
12834
|
isRetiredMemory as isRetiredMemory3,
|
|
12759
12835
|
judgeProposedSensor,
|
|
12760
|
-
loadConfig as
|
|
12836
|
+
loadConfig as loadConfig14,
|
|
12761
12837
|
loadSensorLedger as loadSensorLedger4,
|
|
12762
12838
|
loadMemoriesFromDir as loadMemoriesFromDir16,
|
|
12763
12839
|
normalizeFramework,
|
|
@@ -12769,6 +12845,7 @@ import {
|
|
|
12769
12845
|
buildProposeCommand,
|
|
12770
12846
|
scaffoldPostIncidentTest,
|
|
12771
12847
|
incidentHintsFromDiff,
|
|
12848
|
+
normalizeScaffoldStyle,
|
|
12772
12849
|
selectCommandSensors as selectCommandSensors2,
|
|
12773
12850
|
TEST_FRAMEWORKS,
|
|
12774
12851
|
sensorPatternBrittleness as sensorPatternBrittleness2,
|
|
@@ -12880,7 +12957,7 @@ function registerSensors(program2) {
|
|
|
12880
12957
|
}
|
|
12881
12958
|
}
|
|
12882
12959
|
}
|
|
12883
|
-
const config = await
|
|
12960
|
+
const config = await loadConfig14(paths);
|
|
12884
12961
|
const runCommands = opts.commands || config.enforcement?.runCommandSensors === true;
|
|
12885
12962
|
const changedPaths = targets.map((t) => t.path).filter(Boolean);
|
|
12886
12963
|
const allSensorMemories = await runnableSensorMemories(paths, false);
|
|
@@ -13100,7 +13177,7 @@ function registerSensors(program2) {
|
|
|
13100
13177
|
}
|
|
13101
13178
|
}
|
|
13102
13179
|
const root2 = findProjectRoot39(opts.dir);
|
|
13103
|
-
const { proposeSensor } = await import("./server-
|
|
13180
|
+
const { proposeSensor } = await import("./server-Z4M6ERWN.js");
|
|
13104
13181
|
const out = await proposeSensor(
|
|
13105
13182
|
{
|
|
13106
13183
|
memory_id: id,
|
|
@@ -13227,6 +13304,12 @@ function registerSensors(program2) {
|
|
|
13227
13304
|
).argument("<memory-id>", "lesson id to scaffold a test from").option("--framework <fw>", `test framework: ${TEST_FRAMEWORKS.join(" | ")} (auto-detected when omitted)`).option("--out <path>", "override the generated test file path (project-relative)").option("--stdout", "print the test to stdout instead of writing a file", false).option("--force", "overwrite an existing file at the target path", false).option(
|
|
13228
13305
|
"--red-ref <ref>",
|
|
13229
13306
|
"pre-fix incident commit/ref: the scaffold names the symbols the fix (<ref>..HEAD) touched and pre-fills the example around them, so the assertion is a targeted edit, not a blank page"
|
|
13307
|
+
).option(
|
|
13308
|
+
"--style <style>",
|
|
13309
|
+
"test shape: example (default) | property (state the invariant once, checked over many inputs via fast-check/Hypothesis) | differential (assert the subject agrees with a --reference impl)"
|
|
13310
|
+
).option(
|
|
13311
|
+
"--reference <import>",
|
|
13312
|
+
"differential style only: import specifier of the reference implementation to compare against (e.g. ../legacy/refund or a package name)"
|
|
13230
13313
|
).option("-d, --dir <dir>", "project root").action(async (id, opts) => {
|
|
13231
13314
|
const root = findProjectRoot39(opts.dir);
|
|
13232
13315
|
const paths = resolveHaivePaths36(root);
|
|
@@ -13244,6 +13327,17 @@ function registerSensors(program2) {
|
|
|
13244
13327
|
process.exitCode = 1;
|
|
13245
13328
|
return;
|
|
13246
13329
|
}
|
|
13330
|
+
const style = opts.style ? normalizeScaffoldStyle(opts.style) : "example";
|
|
13331
|
+
if (opts.style && !style) {
|
|
13332
|
+
ui.error(`Unknown --style "${opts.style}". Use one of: example, property, differential.`);
|
|
13333
|
+
process.exitCode = 1;
|
|
13334
|
+
return;
|
|
13335
|
+
}
|
|
13336
|
+
if (style === "differential" && !opts.reference) {
|
|
13337
|
+
ui.error("--style differential requires --reference <import> (the reference implementation to compare against).");
|
|
13338
|
+
process.exitCode = 1;
|
|
13339
|
+
return;
|
|
13340
|
+
}
|
|
13247
13341
|
const allGroups = await detectTestFrameworksForAnchors(root, fm.anchor.paths ?? []);
|
|
13248
13342
|
const groups = opts.out ? allGroups.slice(0, 1) : allGroups;
|
|
13249
13343
|
const fields = parseLessonFields(found.memory.body);
|
|
@@ -13270,14 +13364,15 @@ function registerSensors(program2) {
|
|
|
13270
13364
|
paths: fm.anchor.paths,
|
|
13271
13365
|
incidentHints
|
|
13272
13366
|
};
|
|
13367
|
+
const styleOpts = { style: style ?? "example", reference: opts.reference };
|
|
13273
13368
|
let scaffolds = groups.map(
|
|
13274
|
-
(g) => scaffoldPostIncidentTest(lesson, { framework: forced ?? g.framework, outPath: opts.out, baseDir: g.baseDir })
|
|
13369
|
+
(g) => scaffoldPostIncidentTest(lesson, { framework: forced ?? g.framework, outPath: opts.out, baseDir: g.baseDir, ...styleOpts })
|
|
13275
13370
|
);
|
|
13276
13371
|
let proposeCmd = scaffolds[0].proposeCommand;
|
|
13277
13372
|
if (scaffolds.length > 1) {
|
|
13278
13373
|
proposeCmd = buildProposeCommand(lesson, scaffolds.map((s) => s.runCommand).join(" && "));
|
|
13279
13374
|
scaffolds = groups.map(
|
|
13280
|
-
(g) => scaffoldPostIncidentTest(lesson, { framework: forced ?? g.framework, baseDir: g.baseDir, proposeCommandOverride: proposeCmd })
|
|
13375
|
+
(g) => scaffoldPostIncidentTest(lesson, { framework: forced ?? g.framework, baseDir: g.baseDir, proposeCommandOverride: proposeCmd, ...styleOpts })
|
|
13281
13376
|
);
|
|
13282
13377
|
}
|
|
13283
13378
|
if (opts.stdout) {
|
|
@@ -13697,7 +13792,7 @@ import "commander";
|
|
|
13697
13792
|
import {
|
|
13698
13793
|
buildDashboard,
|
|
13699
13794
|
findProjectRoot as findProjectRoot41,
|
|
13700
|
-
loadConfig as
|
|
13795
|
+
loadConfig as loadConfig15,
|
|
13701
13796
|
loadMemoriesFromDir as loadMemoriesFromDir18,
|
|
13702
13797
|
loadPreventionEvents as loadPreventionEvents4,
|
|
13703
13798
|
loadUsageIndex as loadUsageIndex16,
|
|
@@ -13717,7 +13812,7 @@ function registerDashboard(program2) {
|
|
|
13717
13812
|
const memories = existsSync46(paths.memoriesDir) ? await loadMemoriesFromDir18(paths.memoriesDir) : [];
|
|
13718
13813
|
const usage = await loadUsageIndex16(paths);
|
|
13719
13814
|
const preventionEvents = await loadPreventionEvents4(paths);
|
|
13720
|
-
const config = await
|
|
13815
|
+
const config = await loadConfig15(paths);
|
|
13721
13816
|
const top = Math.max(1, Number.parseInt(opts.top ?? "10", 10) || 10);
|
|
13722
13817
|
const dormantDays = opts.dormantDays ? Number.parseInt(opts.dormantDays, 10) : void 0;
|
|
13723
13818
|
const report = buildDashboard(memories, usage, {
|
|
@@ -13848,7 +13943,7 @@ import { findProjectRoot as findProjectRoot42 } from "@hivelore/core";
|
|
|
13848
13943
|
var exec6 = promisify8(execFile8);
|
|
13849
13944
|
function registerDevLink(program2) {
|
|
13850
13945
|
const dev = program2.commands.find((c) => c.name() === "dev") ?? program2.command("dev").description("Developer utilities for working on Hivelore itself.");
|
|
13851
|
-
dev.command("link").description("Hot-swap this repo's built dist into the global @hivelore
|
|
13946
|
+
dev.command("link").description("Hot-swap this repo's built dist into the global @hivelore 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) => {
|
|
13852
13947
|
const root = findProjectRoot42(opts.dir);
|
|
13853
13948
|
if (!existsSync47(path44.join(root, "packages", "cli", "dist", "index.js"))) {
|
|
13854
13949
|
ui.error(`Not the Hivelore monorepo (no packages/cli/dist) at ${root}. Run \`pnpm -r build\` first, or pass --dir.`);
|
|
@@ -13861,9 +13956,9 @@ function registerDevLink(program2) {
|
|
|
13861
13956
|
} catch {
|
|
13862
13957
|
globalModules = path44.join(path44.dirname(path44.dirname(process.execPath)), "lib", "node_modules");
|
|
13863
13958
|
}
|
|
13864
|
-
const scopeDirs = ["@hivelore"
|
|
13959
|
+
const scopeDirs = ["@hivelore"].map((scope) => path44.join(globalModules, scope)).filter((dir) => existsSync47(dir));
|
|
13865
13960
|
if (scopeDirs.length === 0) {
|
|
13866
|
-
ui.error(`No global @hivelore
|
|
13961
|
+
ui.error(`No global @hivelore install under ${globalModules}. Install once with \`npm i -g @hivelore/cli\`, then re-run.`);
|
|
13867
13962
|
process.exitCode = 1;
|
|
13868
13963
|
return;
|
|
13869
13964
|
}
|
|
@@ -14149,7 +14244,7 @@ function registerBridges(program2) {
|
|
|
14149
14244
|
|
|
14150
14245
|
// src/index.ts
|
|
14151
14246
|
var program = new Command48();
|
|
14152
|
-
program.name("hivelore").description("Hivelore - the deterministic policy gate for agent-written code (rules live as repo-native team memory)").version("0.
|
|
14247
|
+
program.name("hivelore").description("Hivelore - the deterministic policy gate for agent-written code (rules live as repo-native team memory)").version("0.51.0").option("--advanced", "show maintenance and experimental commands in help").showSuggestionAfterError(true);
|
|
14153
14248
|
registerInit(program);
|
|
14154
14249
|
registerResolveProject(program);
|
|
14155
14250
|
registerEnforce(program);
|