@bensandee/tooling 0.93.0 → 0.94.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -20
- package/dist/bin.mjs +624 -274
- package/package.json +9 -9
- package/tooling.schema.json +3 -3
package/dist/bin.mjs
CHANGED
|
@@ -175,7 +175,7 @@ function loadEnvFileForBin(rawArgs, bin, io = processIo) {
|
|
|
175
175
|
//#region src/env-file-preload.ts
|
|
176
176
|
const envFileLoad = loadEnvFileForBin(process.argv.slice(2), {
|
|
177
177
|
name: "bst",
|
|
178
|
-
version: "0.
|
|
178
|
+
version: "0.94.0"
|
|
179
179
|
});
|
|
180
180
|
//#endregion
|
|
181
181
|
//#region ../cli-kit/src/logger.ts
|
|
@@ -448,7 +448,8 @@ function resolveRepoUrlFrom(readOrigin, readPackageUrl) {
|
|
|
448
448
|
const originUrl = normalizeGitUrl(readOrigin() ?? "");
|
|
449
449
|
if (originUrl !== null && parseGitUrl(originUrl) !== null) return originUrl;
|
|
450
450
|
const pkgUrl = readPackageUrl();
|
|
451
|
-
|
|
451
|
+
if (pkgUrl === null || parseGitUrl(pkgUrl) === null) return null;
|
|
452
|
+
return normalizeGitUrl(pkgUrl);
|
|
452
453
|
}
|
|
453
454
|
function resolveForgejoCoordinates(env, repoUrl) {
|
|
454
455
|
const serverUrl = env["FORGEJO_SERVER_URL"];
|
|
@@ -554,10 +555,11 @@ function resolveIssueTarget(refTarget, repo) {
|
|
|
554
555
|
throw new RepoTargetMismatchError(`${repoTarget.serverUrl}/${repoTarget.repository}`, `${refTarget.serverUrl}/${refTarget.repository}`);
|
|
555
556
|
}
|
|
556
557
|
function normalizeGitUrl(raw) {
|
|
557
|
-
const
|
|
558
|
+
const value = raw.startsWith("git+") ? raw.slice(4) : raw;
|
|
559
|
+
const sshMatch = value.match(/^git@([^:]+):(.+?)(?:\.git)?$/);
|
|
558
560
|
if (sshMatch) return `https://${sshMatch[1]}/${sshMatch[2]}`;
|
|
559
561
|
try {
|
|
560
|
-
const url = new URL(
|
|
562
|
+
const url = new URL(value);
|
|
561
563
|
url.pathname = url.pathname.replace(/\.git$/, "");
|
|
562
564
|
url.username = "";
|
|
563
565
|
url.password = "";
|
|
@@ -838,8 +840,8 @@ const TolerantDockerCheckConfigSchema = z.object({
|
|
|
838
840
|
...DockerCheckConfigSchema.shape,
|
|
839
841
|
healthChecks: z.array(tolerantBlock(DeclarativeHealthCheckSchema)).optional()
|
|
840
842
|
});
|
|
841
|
-
const DEFAULT_BRANCH_NAME_PATTERN = /^(?!.*\/\/)(?!.*\.\.)(?!.*\/$)(?!.*\.lock$)[A-Za-z0-9][A-Za-z0-9._/-]*$/;
|
|
842
|
-
const DefaultBranchNameSchema = z.string().regex(DEFAULT_BRANCH_NAME_PATTERN, "must be a plain branch name: ASCII letters, digits, '.', '_', '-' and '/', starting with a letter or digit, with no '//', '..', trailing '/', or '.lock' suffix");
|
|
843
|
+
const DEFAULT_BRANCH_NAME_PATTERN = /^(?!refs\/)(?!.*\/\/)(?!.*\.\.)(?!.*\/$)(?!.*\.lock$)[A-Za-z0-9][A-Za-z0-9._/-]*$/;
|
|
844
|
+
const DefaultBranchNameSchema = z.string().regex(DEFAULT_BRANCH_NAME_PATTERN, "must be a plain branch name: ASCII letters, digits, '.', '_', '-' and '/', starting with a letter or digit, with no leading 'refs/', no '//', '..', trailing '/', or '.lock' suffix");
|
|
843
845
|
const AgentEnabledSliceSchema = z.looseObject({ agent: z.looseObject({ enabled: z.boolean().optional() }).optional() });
|
|
844
846
|
function agentEnabledIn(raw) {
|
|
845
847
|
try {
|
|
@@ -1041,6 +1043,12 @@ function detectProjectType(targetDir) {
|
|
|
1041
1043
|
if (pkg.exports || pkg.main || pkg.types || pkg.typings) return "library";
|
|
1042
1044
|
return "default";
|
|
1043
1045
|
}
|
|
1046
|
+
function resolveProjectType(packageType, structure, builder) {
|
|
1047
|
+
if (structure === "monorepo") return "default";
|
|
1048
|
+
if (packageType === "react") return "react";
|
|
1049
|
+
if (builder === "none") return "node";
|
|
1050
|
+
return packageType;
|
|
1051
|
+
}
|
|
1044
1052
|
const WEB_UI_DEPS = /* @__PURE__ */ new Set([
|
|
1045
1053
|
"react",
|
|
1046
1054
|
"react-dom",
|
|
@@ -1078,19 +1086,21 @@ function computeDefaults(targetDir) {
|
|
|
1078
1086
|
const detected = detectProject(targetDir);
|
|
1079
1087
|
const isMonorepo = detected.hasPnpmWorkspace;
|
|
1080
1088
|
const hasPrettier = detected.legacyConfigs.some((l) => l.tool === "prettier");
|
|
1089
|
+
const structure = isMonorepo ? "monorepo" : "single";
|
|
1090
|
+
const builder = detectBuilder(targetDir, readPackageJson(targetDir));
|
|
1081
1091
|
return {
|
|
1082
|
-
structure
|
|
1092
|
+
structure,
|
|
1083
1093
|
useEslintPlugin: true,
|
|
1084
1094
|
formatter: hasPrettier ? "prettier" : "oxfmt",
|
|
1085
1095
|
stack: detectStack(targetDir),
|
|
1086
|
-
builder
|
|
1096
|
+
builder,
|
|
1087
1097
|
setupVitest: !isMonorepo && !detected.hasVitestConfig,
|
|
1088
1098
|
ci: detectCiPlatform(targetDir),
|
|
1089
1099
|
setupRenovate: true,
|
|
1090
1100
|
releaseStrategy: "none",
|
|
1091
1101
|
publishNpm: false,
|
|
1092
1102
|
publishDocker: false,
|
|
1093
|
-
projectType:
|
|
1103
|
+
projectType: resolveProjectType(detectProjectType(targetDir), structure, builder),
|
|
1094
1104
|
detectPackageTypes: true
|
|
1095
1105
|
};
|
|
1096
1106
|
}
|
|
@@ -1159,7 +1169,7 @@ async function runInitPrompts(targetDir, saved, options) {
|
|
|
1159
1169
|
let ci = saved?.ci ?? defaults.ci;
|
|
1160
1170
|
const setupRenovate = saved?.setupRenovate ?? defaults.setupRenovate;
|
|
1161
1171
|
let releaseStrategy = saved?.releaseStrategy ?? defaults.releaseStrategy;
|
|
1162
|
-
|
|
1172
|
+
let projectType = saved?.projectType ?? defaults.projectType;
|
|
1163
1173
|
const detectPackageTypes = saved?.detectPackageTypes ?? defaults.detectPackageTypes;
|
|
1164
1174
|
const skipPrompts = (options?.skipStackPrompt ?? false) || Boolean(process.env["CI"]);
|
|
1165
1175
|
if (isFirstInit && defaults.stack === "none" && !skipPrompts) {
|
|
@@ -1216,6 +1226,7 @@ async function runInitPrompts(targetDir, saved, options) {
|
|
|
1216
1226
|
builder = builderAnswer;
|
|
1217
1227
|
}
|
|
1218
1228
|
}
|
|
1229
|
+
if (saved?.projectType === void 0) projectType = resolveProjectType(detectProjectType(targetDir), structure, builder);
|
|
1219
1230
|
if (detected.legacyConfigs.some((l) => l.tool === "prettier") && isFirstInit) {
|
|
1220
1231
|
const formatterAnswer = await clack.select({
|
|
1221
1232
|
message: "Existing Prettier config found. Keep Prettier or migrate to oxfmt?",
|
|
@@ -1715,7 +1726,7 @@ const AgentEgressConfigSchema = z.strictObject({ browseHosts: z.array(z.string()
|
|
|
1715
1726
|
const AgentConfigSchema = z.strictObject({
|
|
1716
1727
|
enabled: z.boolean().optional().meta({ description: "The repo's explicit consent to the agent (default false). Only `true`, read from the repo's **default branch**, lets the agent work the repo: claim units, run sweeps, recover orphans, merge, act on a chat proposal or start a new spec. A missing key, `false`, a missing `.tooling.json` or a read that fails all mean not enabled, and the answer is re-read within about a minute. Every other `agent.*` key below has no effect without this one; `repo:sync` warns when the block sets one of them and this key is not `true`. Where this is `true`, `repo:sync` also installs `@bensandee/agent-cli`. `bst setup:agent` sets it to true and runs the sync. Provisioning the agent's labels is not consent, and is never gated by this key." }),
|
|
1717
1728
|
autonomy: z.boolean().optional().meta({ description: "Opt into interior autonomy (default false). When a phase's latest rationale reports readiness \"ready\" with an empty openQuestions[] and the review phase is clean, the agent applies the existing plan -> implement -> review -> park-at-mergeable advance itself instead of parking for a human. Never applies to the design/spec gate or the final merge from the mergeable terminal -- those always wait on a human. Also arms the trunk-red detector: when this repo's default branch combined status settles red, the agent files a tracking issue naming the failing checks and labels it agent/spec, starting the fix through the ordinary spec/plan/implement/review pipeline. It also closes that issue, with a comment, if the default branch recovers on its own before a human acts on it. Also arms the world-changed retrigger sweep: a pull request labelled agent-flags/autopilot that is parked at a -stuck or -failed terminal is re-queued when the fact its park recorded stops being true (the default branch went green, or moved past a conflicted base)." }),
|
|
1718
|
-
preRunRebase: z.boolean().optional().meta({ description: "Merge the current default branch into the work branch before the implement (execute)
|
|
1729
|
+
preRunRebase: z.boolean().optional().meta({ description: "Merge the current default branch into the work branch before the container starts (default true) -- for an implement (execute) run, and for a spec or plan rerun on an existing branch. A clean merge or an already-up-to-date branch runs the container as is; a conflicting merge is aborted, recorded on the run's status comment, and the run proceeds on the un-synced branch -- a conflict, timeout or failed merge never fails the run, for any phase. Only the execute phase keeps the two-strike rule: two consecutive conflicting attempts hand the unit to agent/repair before any container runs, while a docs run just tries again on its next slice. Set false to skip the attempt entirely for this repo, in every phase: no fetch, no merge, no abort, and the branch is worked exactly as cloned." }),
|
|
1719
1730
|
backlog: z.boolean().optional().meta({ description: "Whether the agent may self-trigger backlog runs consuming this repo's .agent/findings/ ledger (default: on wherever the ledger exists, which only happens in a repo whose own review loop wrote it). A backlog run opens a pull request that fixes or refutes the pending findings; false disarms it, leaving the ledger to accumulate for a human. A run starts only once at least three findings are pending or one is older than seven days." }),
|
|
1720
1731
|
lintSweep: LintSweepConfigSchema.optional().meta({ description: "The lint-warning sweep: a periodic agent job that measures this repo's oxlint warnings on its default branch in a container and, past minWarnings, opens a pull request carrying a generated plan for an agent/implement run to work off." }),
|
|
1721
1732
|
priority: z.record(z.string(), z.enum([
|
|
@@ -1783,7 +1794,7 @@ const ToolingConfigSchema = z.strictObject({
|
|
|
1783
1794
|
docs: z.union([z.literal(false), DocsConfigSchema]).optional().meta({ description: "Plan/spec lifecycle gate (`bst docs:check`). Overrides only -- dirs are discovered by convention. Set to false to disable. Violations are advisory until a pair is named in `enforced`." }),
|
|
1784
1795
|
deploy: DeployConfigSchema.optional().meta({ description: "Generate a deploy-dispatch step in the release workflow that triggers a workflow_dispatch event in another repo after a successful release. The resolved release token must have rights on `dispatchRepo`." }),
|
|
1785
1796
|
pnpmWorkspace: z.literal(false).optional().meta({ description: "Set to false to opt out of pnpm-workspace.yaml management (skips both generation and the repo:sync --check validation)." }),
|
|
1786
|
-
defaultBranch: DefaultBranchNameSchema.optional().meta({ description: "Repository default branch name
|
|
1797
|
+
defaultBranch: DefaultBranchNameSchema.optional().meta({ description: "Repository default branch name. Drives the generated CI `on.push` trigger and every default-branch gate in `ci.yml` and `release.yml`, the release path (`release:changesets`, the version PR, `release:trigger`) and `checks:run`'s coverage gate. Generated output uses 'main' when unset; `checks:run` still falls back to `origin/HEAD`. Must be a plain branch name: ASCII letters, digits, '.', '_', '-' and '/', with no leading 'refs/'." }),
|
|
1787
1798
|
unmanagedDevDependencies: UnmanagedDevDependenciesSchema.optional().meta({ description: "devDependency names repo:sync must never add or re-pin. Escape hatch for holding a tooling-managed dep (oxlint, vitest, knip, ...) at a deliberately off-canonical version; without it, repo:sync raises any managed devDep whose version has drifted below the canonical pin." }),
|
|
1788
1799
|
stack: StackSchema.optional().meta({ description: "Whether the repo has TS/JS source. Detected from source evidence; TypeScript is never inferred from absence. Setting this key pins its value, even one that equals detection, and repo:sync keeps it until the key is deleted." }),
|
|
1789
1800
|
builder: z.enum([
|
|
@@ -1903,7 +1914,10 @@ function pruneCharsDefaults(value) {
|
|
|
1903
1914
|
return kept;
|
|
1904
1915
|
}
|
|
1905
1916
|
function saveToolingConfig(ctx, config) {
|
|
1906
|
-
const defaults =
|
|
1917
|
+
const defaults = {
|
|
1918
|
+
...computeDefaults(config.targetDir),
|
|
1919
|
+
projectType: resolveProjectType(detectProjectType(config.targetDir), config.structure, config.builder)
|
|
1920
|
+
};
|
|
1907
1921
|
const isMonorepo = config.structure === "monorepo";
|
|
1908
1922
|
const existing = ctx.exists(CONFIG_FILE) ? ctx.read(CONFIG_FILE) : void 0;
|
|
1909
1923
|
const existingJson = parseExistingConfig(existing);
|
|
@@ -1937,21 +1951,23 @@ function saveToolingConfig(ctx, config) {
|
|
|
1937
1951
|
};
|
|
1938
1952
|
}
|
|
1939
1953
|
function mergeWithSavedConfig(detected, saved) {
|
|
1954
|
+
const structure = saved.structure ?? detected.structure;
|
|
1955
|
+
const builder = saved.builder ?? detected.builder;
|
|
1940
1956
|
return {
|
|
1941
1957
|
name: detected.name,
|
|
1942
1958
|
targetDir: detected.targetDir,
|
|
1943
|
-
structure
|
|
1959
|
+
structure,
|
|
1944
1960
|
useEslintPlugin: saved.useEslintPlugin ?? detected.useEslintPlugin,
|
|
1945
1961
|
formatter: saved.formatter ?? detected.formatter,
|
|
1946
1962
|
stack: saved.stack ?? detected.stack,
|
|
1947
|
-
builder
|
|
1963
|
+
builder,
|
|
1948
1964
|
setupVitest: saved.setupVitest ?? detected.setupVitest,
|
|
1949
1965
|
ci: saved.ci ?? detected.ci,
|
|
1950
1966
|
setupRenovate: saved.setupRenovate ?? detected.setupRenovate,
|
|
1951
1967
|
releaseStrategy: saved.releaseStrategy ?? detected.releaseStrategy,
|
|
1952
1968
|
publishNpm: saved.publishNpm ?? detected.publishNpm,
|
|
1953
1969
|
publishDocker: saved.publishDocker ?? detected.publishDocker,
|
|
1954
|
-
projectType: saved.projectType ?? detected.
|
|
1970
|
+
projectType: saved.projectType ?? resolveProjectType(detectProjectType(detected.targetDir), structure, builder),
|
|
1955
1971
|
detectPackageTypes: saved.detectPackageTypes ?? detected.detectPackageTypes
|
|
1956
1972
|
};
|
|
1957
1973
|
}
|
|
@@ -2088,10 +2104,7 @@ async function throwForgejoFailure(res, operation, notFoundMessage) {
|
|
|
2088
2104
|
if (res.status === 410) throw new FatalError(`${message}\nForgejo returned 410 Gone -- issues are likely disabled on this repository; retrying will not help.`);
|
|
2089
2105
|
throw new TransientError(message);
|
|
2090
2106
|
}
|
|
2091
|
-
z.object({
|
|
2092
|
-
secrets: z.array(z.object({ name: z.string() })),
|
|
2093
|
-
total_count: z.number()
|
|
2094
|
-
});
|
|
2107
|
+
z.array(z.object({ name: z.string() }));
|
|
2095
2108
|
const AccessTokenResponseSchema = z.object({ sha1: z.string() });
|
|
2096
2109
|
const NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
2097
2110
|
const FORBIDDEN_PREFIX_PATTERN = /^(FORGEJO_|GITEA_|GITHUB_|[0-9])/i;
|
|
@@ -2903,8 +2916,11 @@ function tracedRunners(logger) {
|
|
|
2903
2916
|
if (result.signal !== null && result.signal !== void 0) throw new UnexpectedError(`Command killed by signal ${result.signal}: ${commandLine(file, args)}`);
|
|
2904
2917
|
if (result.status !== 0) throw new UnexpectedError(`Command failed with exit code ${String(result.status)}: ${commandLine(file, args)}`);
|
|
2905
2918
|
}
|
|
2906
|
-
} finally {
|
|
2907
2919
|
logger.trace(`execInherit [ok] ${file} (${sinceMs(startedAt)})`);
|
|
2920
|
+
} catch (error) {
|
|
2921
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2922
|
+
logger.trace(`execInherit [x] ${file} (${reason}, ${sinceMs(startedAt)})`);
|
|
2923
|
+
throw error;
|
|
2908
2924
|
}
|
|
2909
2925
|
},
|
|
2910
2926
|
async fetch(url, init, retry) {
|
|
@@ -4485,8 +4501,8 @@ function buildWorkflowYaml(options) {
|
|
|
4485
4501
|
}
|
|
4486
4502
|
//#endregion
|
|
4487
4503
|
//#region src/generators/mise-pin.ts
|
|
4488
|
-
const MISE_VERSION = "2026.10.
|
|
4489
|
-
const MISE_LINUX_X64_SHA256 = "
|
|
4504
|
+
const MISE_VERSION = "2026.10.15";
|
|
4505
|
+
const MISE_LINUX_X64_SHA256 = "e3b186d4c70627f27037be615becaf4acfb30e41fb6a1819486717dc94e4e2a9";
|
|
4490
4506
|
const SETUP_MISE_ACTION_URL = "https://code.orangebikelabs.com/bensandee/common/actions/setup-mise";
|
|
4491
4507
|
const SETUP_MISE_ACTION_TAG = "setup-mise@latest";
|
|
4492
4508
|
const SETUP_MISE_ACTION_SHA = "ebe622ef0d24c7efec4a5f6093ddb18500900e24";
|
|
@@ -4716,7 +4732,7 @@ function detectReleaseAssetsConventionDirs(ctx) {
|
|
|
4716
4732
|
if (ctx.config.structure === "monorepo") {
|
|
4717
4733
|
const packages = getMonorepoPackages(ctx.targetDir);
|
|
4718
4734
|
for (const pkg of packages) {
|
|
4719
|
-
const dirName =
|
|
4735
|
+
const dirName = path.basename(pkg.dir);
|
|
4720
4736
|
const pkgRaw = ctx.read(`packages/${dirName}/package.json`);
|
|
4721
4737
|
if (!pkgRaw) continue;
|
|
4722
4738
|
if (parsePackageJson(pkgRaw)?.scripts?.["build:release-assets"]) dirs.push(`packages/${dirName}`);
|
|
@@ -4944,6 +4960,11 @@ function readUnmanagedDevDepNames(ctx) {
|
|
|
4944
4960
|
const slice = parseToolingConfigSlice(ctx.read(".tooling.json"), UnmanagedSliceSchema, { block: "unmanagedDevDependencies" });
|
|
4945
4961
|
return new Set(slice?.unmanagedDevDependencies ?? []);
|
|
4946
4962
|
}
|
|
4963
|
+
const COMMIT_AND_TAG_VERSION_PACKAGE = "commit-and-tag-version";
|
|
4964
|
+
function wantsCommitAndTagVersion(ctx) {
|
|
4965
|
+
if (ctx.config.releaseStrategy !== "simple") return false;
|
|
4966
|
+
return !readUnmanagedDevDepNames(ctx).has(COMMIT_AND_TAG_VERSION_PACKAGE);
|
|
4967
|
+
}
|
|
4947
4968
|
//#endregion
|
|
4948
4969
|
//#region src/generators/package-json.ts
|
|
4949
4970
|
const NODE_ENGINES_FLOOR = "24.13.0";
|
|
@@ -5050,18 +5071,18 @@ function perPackageDevDeps(config) {
|
|
|
5050
5071
|
deps["typescript"] = {
|
|
5051
5072
|
"@changesets/cli": "3.0.3",
|
|
5052
5073
|
"@release-it/bumper": "8.0.1",
|
|
5053
|
-
"@types/node": "24.19.
|
|
5074
|
+
"@types/node": "24.19.2",
|
|
5054
5075
|
"@vitest/coverage-v8": "5.0.3",
|
|
5055
|
-
"commit-and-tag-version": "13.
|
|
5076
|
+
"commit-and-tag-version": "13.3.0",
|
|
5056
5077
|
"knip": "6.41.0",
|
|
5057
|
-
"lefthook": "2.2.
|
|
5058
|
-
"modern-web-guidance": "0.0.
|
|
5059
|
-
"oxfmt": "0.
|
|
5078
|
+
"lefthook": "2.2.2",
|
|
5079
|
+
"modern-web-guidance": "0.0.194",
|
|
5080
|
+
"oxfmt": "0.73.0",
|
|
5060
5081
|
"oxlint": "1.87.0",
|
|
5061
|
-
"oxlint-tsgolint": "7.0.
|
|
5062
|
-
"prettier": "3.9.
|
|
5082
|
+
"oxlint-tsgolint": "7.0.2004",
|
|
5083
|
+
"prettier": "3.9.10",
|
|
5063
5084
|
"release-it": "21.1.1",
|
|
5064
|
-
"tsdown": "0.23.
|
|
5085
|
+
"tsdown": "0.23.1",
|
|
5065
5086
|
"typescript": "6.0.3",
|
|
5066
5087
|
"vitest": "5.0.3",
|
|
5067
5088
|
"pnpm": "11.28.5"
|
|
@@ -5069,18 +5090,18 @@ function perPackageDevDeps(config) {
|
|
|
5069
5090
|
deps["@types/node"] = {
|
|
5070
5091
|
"@changesets/cli": "3.0.3",
|
|
5071
5092
|
"@release-it/bumper": "8.0.1",
|
|
5072
|
-
"@types/node": "24.19.
|
|
5093
|
+
"@types/node": "24.19.2",
|
|
5073
5094
|
"@vitest/coverage-v8": "5.0.3",
|
|
5074
|
-
"commit-and-tag-version": "13.
|
|
5095
|
+
"commit-and-tag-version": "13.3.0",
|
|
5075
5096
|
"knip": "6.41.0",
|
|
5076
|
-
"lefthook": "2.2.
|
|
5077
|
-
"modern-web-guidance": "0.0.
|
|
5078
|
-
"oxfmt": "0.
|
|
5097
|
+
"lefthook": "2.2.2",
|
|
5098
|
+
"modern-web-guidance": "0.0.194",
|
|
5099
|
+
"oxfmt": "0.73.0",
|
|
5079
5100
|
"oxlint": "1.87.0",
|
|
5080
|
-
"oxlint-tsgolint": "7.0.
|
|
5081
|
-
"prettier": "3.9.
|
|
5101
|
+
"oxlint-tsgolint": "7.0.2004",
|
|
5102
|
+
"prettier": "3.9.10",
|
|
5082
5103
|
"release-it": "21.1.1",
|
|
5083
|
-
"tsdown": "0.23.
|
|
5104
|
+
"tsdown": "0.23.1",
|
|
5084
5105
|
"typescript": "6.0.3",
|
|
5085
5106
|
"vitest": "5.0.3",
|
|
5086
5107
|
"pnpm": "11.28.5"
|
|
@@ -5089,18 +5110,18 @@ function perPackageDevDeps(config) {
|
|
|
5089
5110
|
if (config.builder === "tsdown") deps["tsdown"] = {
|
|
5090
5111
|
"@changesets/cli": "3.0.3",
|
|
5091
5112
|
"@release-it/bumper": "8.0.1",
|
|
5092
|
-
"@types/node": "24.19.
|
|
5113
|
+
"@types/node": "24.19.2",
|
|
5093
5114
|
"@vitest/coverage-v8": "5.0.3",
|
|
5094
|
-
"commit-and-tag-version": "13.
|
|
5115
|
+
"commit-and-tag-version": "13.3.0",
|
|
5095
5116
|
"knip": "6.41.0",
|
|
5096
|
-
"lefthook": "2.2.
|
|
5097
|
-
"modern-web-guidance": "0.0.
|
|
5098
|
-
"oxfmt": "0.
|
|
5117
|
+
"lefthook": "2.2.2",
|
|
5118
|
+
"modern-web-guidance": "0.0.194",
|
|
5119
|
+
"oxfmt": "0.73.0",
|
|
5099
5120
|
"oxlint": "1.87.0",
|
|
5100
|
-
"oxlint-tsgolint": "7.0.
|
|
5101
|
-
"prettier": "3.9.
|
|
5121
|
+
"oxlint-tsgolint": "7.0.2004",
|
|
5122
|
+
"prettier": "3.9.10",
|
|
5102
5123
|
"release-it": "21.1.1",
|
|
5103
|
-
"tsdown": "0.23.
|
|
5124
|
+
"tsdown": "0.23.1",
|
|
5104
5125
|
"typescript": "6.0.3",
|
|
5105
5126
|
"vitest": "5.0.3",
|
|
5106
5127
|
"pnpm": "11.28.5"
|
|
@@ -5112,18 +5133,18 @@ function vitestDevDeps() {
|
|
|
5112
5133
|
vitest: {
|
|
5113
5134
|
"@changesets/cli": "3.0.3",
|
|
5114
5135
|
"@release-it/bumper": "8.0.1",
|
|
5115
|
-
"@types/node": "24.19.
|
|
5136
|
+
"@types/node": "24.19.2",
|
|
5116
5137
|
"@vitest/coverage-v8": "5.0.3",
|
|
5117
|
-
"commit-and-tag-version": "13.
|
|
5138
|
+
"commit-and-tag-version": "13.3.0",
|
|
5118
5139
|
"knip": "6.41.0",
|
|
5119
|
-
"lefthook": "2.2.
|
|
5120
|
-
"modern-web-guidance": "0.0.
|
|
5121
|
-
"oxfmt": "0.
|
|
5140
|
+
"lefthook": "2.2.2",
|
|
5141
|
+
"modern-web-guidance": "0.0.194",
|
|
5142
|
+
"oxfmt": "0.73.0",
|
|
5122
5143
|
"oxlint": "1.87.0",
|
|
5123
|
-
"oxlint-tsgolint": "7.0.
|
|
5124
|
-
"prettier": "3.9.
|
|
5144
|
+
"oxlint-tsgolint": "7.0.2004",
|
|
5145
|
+
"prettier": "3.9.10",
|
|
5125
5146
|
"release-it": "21.1.1",
|
|
5126
|
-
"tsdown": "0.23.
|
|
5147
|
+
"tsdown": "0.23.1",
|
|
5127
5148
|
"typescript": "6.0.3",
|
|
5128
5149
|
"vitest": "5.0.3",
|
|
5129
5150
|
"pnpm": "11.28.5"
|
|
@@ -5131,18 +5152,18 @@ function vitestDevDeps() {
|
|
|
5131
5152
|
"@vitest/coverage-v8": {
|
|
5132
5153
|
"@changesets/cli": "3.0.3",
|
|
5133
5154
|
"@release-it/bumper": "8.0.1",
|
|
5134
|
-
"@types/node": "24.19.
|
|
5155
|
+
"@types/node": "24.19.2",
|
|
5135
5156
|
"@vitest/coverage-v8": "5.0.3",
|
|
5136
|
-
"commit-and-tag-version": "13.
|
|
5157
|
+
"commit-and-tag-version": "13.3.0",
|
|
5137
5158
|
"knip": "6.41.0",
|
|
5138
|
-
"lefthook": "2.2.
|
|
5139
|
-
"modern-web-guidance": "0.0.
|
|
5140
|
-
"oxfmt": "0.
|
|
5159
|
+
"lefthook": "2.2.2",
|
|
5160
|
+
"modern-web-guidance": "0.0.194",
|
|
5161
|
+
"oxfmt": "0.73.0",
|
|
5141
5162
|
"oxlint": "1.87.0",
|
|
5142
|
-
"oxlint-tsgolint": "7.0.
|
|
5143
|
-
"prettier": "3.9.
|
|
5163
|
+
"oxlint-tsgolint": "7.0.2004",
|
|
5164
|
+
"prettier": "3.9.10",
|
|
5144
5165
|
"release-it": "21.1.1",
|
|
5145
|
-
"tsdown": "0.23.
|
|
5166
|
+
"tsdown": "0.23.1",
|
|
5146
5167
|
"typescript": "6.0.3",
|
|
5147
5168
|
"vitest": "5.0.3",
|
|
5148
5169
|
"pnpm": "11.28.5"
|
|
@@ -5153,18 +5174,18 @@ function rootDevDeps(config) {
|
|
|
5153
5174
|
const deps = { lefthook: {
|
|
5154
5175
|
"@changesets/cli": "3.0.3",
|
|
5155
5176
|
"@release-it/bumper": "8.0.1",
|
|
5156
|
-
"@types/node": "24.19.
|
|
5177
|
+
"@types/node": "24.19.2",
|
|
5157
5178
|
"@vitest/coverage-v8": "5.0.3",
|
|
5158
|
-
"commit-and-tag-version": "13.
|
|
5179
|
+
"commit-and-tag-version": "13.3.0",
|
|
5159
5180
|
"knip": "6.41.0",
|
|
5160
|
-
"lefthook": "2.2.
|
|
5161
|
-
"modern-web-guidance": "0.0.
|
|
5162
|
-
"oxfmt": "0.
|
|
5181
|
+
"lefthook": "2.2.2",
|
|
5182
|
+
"modern-web-guidance": "0.0.194",
|
|
5183
|
+
"oxfmt": "0.73.0",
|
|
5163
5184
|
"oxlint": "1.87.0",
|
|
5164
|
-
"oxlint-tsgolint": "7.0.
|
|
5165
|
-
"prettier": "3.9.
|
|
5185
|
+
"oxlint-tsgolint": "7.0.2004",
|
|
5186
|
+
"prettier": "3.9.10",
|
|
5166
5187
|
"release-it": "21.1.1",
|
|
5167
|
-
"tsdown": "0.23.
|
|
5188
|
+
"tsdown": "0.23.1",
|
|
5168
5189
|
"typescript": "6.0.3",
|
|
5169
5190
|
"vitest": "5.0.3",
|
|
5170
5191
|
"pnpm": "11.28.5"
|
|
@@ -5173,18 +5194,18 @@ function rootDevDeps(config) {
|
|
|
5173
5194
|
deps["knip"] = {
|
|
5174
5195
|
"@changesets/cli": "3.0.3",
|
|
5175
5196
|
"@release-it/bumper": "8.0.1",
|
|
5176
|
-
"@types/node": "24.19.
|
|
5197
|
+
"@types/node": "24.19.2",
|
|
5177
5198
|
"@vitest/coverage-v8": "5.0.3",
|
|
5178
|
-
"commit-and-tag-version": "13.
|
|
5199
|
+
"commit-and-tag-version": "13.3.0",
|
|
5179
5200
|
"knip": "6.41.0",
|
|
5180
|
-
"lefthook": "2.2.
|
|
5181
|
-
"modern-web-guidance": "0.0.
|
|
5182
|
-
"oxfmt": "0.
|
|
5201
|
+
"lefthook": "2.2.2",
|
|
5202
|
+
"modern-web-guidance": "0.0.194",
|
|
5203
|
+
"oxfmt": "0.73.0",
|
|
5183
5204
|
"oxlint": "1.87.0",
|
|
5184
|
-
"oxlint-tsgolint": "7.0.
|
|
5185
|
-
"prettier": "3.9.
|
|
5205
|
+
"oxlint-tsgolint": "7.0.2004",
|
|
5206
|
+
"prettier": "3.9.10",
|
|
5186
5207
|
"release-it": "21.1.1",
|
|
5187
|
-
"tsdown": "0.23.
|
|
5208
|
+
"tsdown": "0.23.1",
|
|
5188
5209
|
"typescript": "6.0.3",
|
|
5189
5210
|
"vitest": "5.0.3",
|
|
5190
5211
|
"pnpm": "11.28.5"
|
|
@@ -5192,18 +5213,18 @@ function rootDevDeps(config) {
|
|
|
5192
5213
|
deps["oxlint"] = {
|
|
5193
5214
|
"@changesets/cli": "3.0.3",
|
|
5194
5215
|
"@release-it/bumper": "8.0.1",
|
|
5195
|
-
"@types/node": "24.19.
|
|
5216
|
+
"@types/node": "24.19.2",
|
|
5196
5217
|
"@vitest/coverage-v8": "5.0.3",
|
|
5197
|
-
"commit-and-tag-version": "13.
|
|
5218
|
+
"commit-and-tag-version": "13.3.0",
|
|
5198
5219
|
"knip": "6.41.0",
|
|
5199
|
-
"lefthook": "2.2.
|
|
5200
|
-
"modern-web-guidance": "0.0.
|
|
5201
|
-
"oxfmt": "0.
|
|
5220
|
+
"lefthook": "2.2.2",
|
|
5221
|
+
"modern-web-guidance": "0.0.194",
|
|
5222
|
+
"oxfmt": "0.73.0",
|
|
5202
5223
|
"oxlint": "1.87.0",
|
|
5203
|
-
"oxlint-tsgolint": "7.0.
|
|
5204
|
-
"prettier": "3.9.
|
|
5224
|
+
"oxlint-tsgolint": "7.0.2004",
|
|
5225
|
+
"prettier": "3.9.10",
|
|
5205
5226
|
"release-it": "21.1.1",
|
|
5206
|
-
"tsdown": "0.23.
|
|
5227
|
+
"tsdown": "0.23.1",
|
|
5207
5228
|
"typescript": "6.0.3",
|
|
5208
5229
|
"vitest": "5.0.3",
|
|
5209
5230
|
"pnpm": "11.28.5"
|
|
@@ -5213,82 +5234,82 @@ function rootDevDeps(config) {
|
|
|
5213
5234
|
if (config.stack === "typescript") deps["oxlint-tsgolint"] = {
|
|
5214
5235
|
"@changesets/cli": "3.0.3",
|
|
5215
5236
|
"@release-it/bumper": "8.0.1",
|
|
5216
|
-
"@types/node": "24.19.
|
|
5237
|
+
"@types/node": "24.19.2",
|
|
5217
5238
|
"@vitest/coverage-v8": "5.0.3",
|
|
5218
|
-
"commit-and-tag-version": "13.
|
|
5239
|
+
"commit-and-tag-version": "13.3.0",
|
|
5219
5240
|
"knip": "6.41.0",
|
|
5220
|
-
"lefthook": "2.2.
|
|
5221
|
-
"modern-web-guidance": "0.0.
|
|
5222
|
-
"oxfmt": "0.
|
|
5241
|
+
"lefthook": "2.2.2",
|
|
5242
|
+
"modern-web-guidance": "0.0.194",
|
|
5243
|
+
"oxfmt": "0.73.0",
|
|
5223
5244
|
"oxlint": "1.87.0",
|
|
5224
|
-
"oxlint-tsgolint": "7.0.
|
|
5225
|
-
"prettier": "3.9.
|
|
5245
|
+
"oxlint-tsgolint": "7.0.2004",
|
|
5246
|
+
"prettier": "3.9.10",
|
|
5226
5247
|
"release-it": "21.1.1",
|
|
5227
|
-
"tsdown": "0.23.
|
|
5248
|
+
"tsdown": "0.23.1",
|
|
5228
5249
|
"typescript": "6.0.3",
|
|
5229
5250
|
"vitest": "5.0.3",
|
|
5230
5251
|
"pnpm": "11.28.5"
|
|
5231
5252
|
}["oxlint-tsgolint"];
|
|
5232
5253
|
return deps;
|
|
5233
5254
|
}
|
|
5234
|
-
function addReleaseDeps(deps,
|
|
5235
|
-
switch (config.releaseStrategy) {
|
|
5255
|
+
function addReleaseDeps(deps, ctx) {
|
|
5256
|
+
switch (ctx.config.releaseStrategy) {
|
|
5236
5257
|
case "release-it":
|
|
5237
5258
|
deps["release-it"] = {
|
|
5238
5259
|
"@changesets/cli": "3.0.3",
|
|
5239
5260
|
"@release-it/bumper": "8.0.1",
|
|
5240
|
-
"@types/node": "24.19.
|
|
5261
|
+
"@types/node": "24.19.2",
|
|
5241
5262
|
"@vitest/coverage-v8": "5.0.3",
|
|
5242
|
-
"commit-and-tag-version": "13.
|
|
5263
|
+
"commit-and-tag-version": "13.3.0",
|
|
5243
5264
|
"knip": "6.41.0",
|
|
5244
|
-
"lefthook": "2.2.
|
|
5245
|
-
"modern-web-guidance": "0.0.
|
|
5246
|
-
"oxfmt": "0.
|
|
5265
|
+
"lefthook": "2.2.2",
|
|
5266
|
+
"modern-web-guidance": "0.0.194",
|
|
5267
|
+
"oxfmt": "0.73.0",
|
|
5247
5268
|
"oxlint": "1.87.0",
|
|
5248
|
-
"oxlint-tsgolint": "7.0.
|
|
5249
|
-
"prettier": "3.9.
|
|
5269
|
+
"oxlint-tsgolint": "7.0.2004",
|
|
5270
|
+
"prettier": "3.9.10",
|
|
5250
5271
|
"release-it": "21.1.1",
|
|
5251
|
-
"tsdown": "0.23.
|
|
5272
|
+
"tsdown": "0.23.1",
|
|
5252
5273
|
"typescript": "6.0.3",
|
|
5253
5274
|
"vitest": "5.0.3",
|
|
5254
5275
|
"pnpm": "11.28.5"
|
|
5255
5276
|
}["release-it"];
|
|
5256
|
-
if (config.structure === "monorepo") deps["@release-it/bumper"] = {
|
|
5277
|
+
if (ctx.config.structure === "monorepo") deps["@release-it/bumper"] = {
|
|
5257
5278
|
"@changesets/cli": "3.0.3",
|
|
5258
5279
|
"@release-it/bumper": "8.0.1",
|
|
5259
|
-
"@types/node": "24.19.
|
|
5280
|
+
"@types/node": "24.19.2",
|
|
5260
5281
|
"@vitest/coverage-v8": "5.0.3",
|
|
5261
|
-
"commit-and-tag-version": "13.
|
|
5282
|
+
"commit-and-tag-version": "13.3.0",
|
|
5262
5283
|
"knip": "6.41.0",
|
|
5263
|
-
"lefthook": "2.2.
|
|
5264
|
-
"modern-web-guidance": "0.0.
|
|
5265
|
-
"oxfmt": "0.
|
|
5284
|
+
"lefthook": "2.2.2",
|
|
5285
|
+
"modern-web-guidance": "0.0.194",
|
|
5286
|
+
"oxfmt": "0.73.0",
|
|
5266
5287
|
"oxlint": "1.87.0",
|
|
5267
|
-
"oxlint-tsgolint": "7.0.
|
|
5268
|
-
"prettier": "3.9.
|
|
5288
|
+
"oxlint-tsgolint": "7.0.2004",
|
|
5289
|
+
"prettier": "3.9.10",
|
|
5269
5290
|
"release-it": "21.1.1",
|
|
5270
|
-
"tsdown": "0.23.
|
|
5291
|
+
"tsdown": "0.23.1",
|
|
5271
5292
|
"typescript": "6.0.3",
|
|
5272
5293
|
"vitest": "5.0.3",
|
|
5273
5294
|
"pnpm": "11.28.5"
|
|
5274
5295
|
}["@release-it/bumper"];
|
|
5275
5296
|
break;
|
|
5276
5297
|
case "simple":
|
|
5277
|
-
deps[
|
|
5298
|
+
if (wantsCommitAndTagVersion(ctx)) deps[COMMIT_AND_TAG_VERSION_PACKAGE] = {
|
|
5278
5299
|
"@changesets/cli": "3.0.3",
|
|
5279
5300
|
"@release-it/bumper": "8.0.1",
|
|
5280
|
-
"@types/node": "24.19.
|
|
5301
|
+
"@types/node": "24.19.2",
|
|
5281
5302
|
"@vitest/coverage-v8": "5.0.3",
|
|
5282
|
-
"commit-and-tag-version": "13.
|
|
5303
|
+
"commit-and-tag-version": "13.3.0",
|
|
5283
5304
|
"knip": "6.41.0",
|
|
5284
|
-
"lefthook": "2.2.
|
|
5285
|
-
"modern-web-guidance": "0.0.
|
|
5286
|
-
"oxfmt": "0.
|
|
5305
|
+
"lefthook": "2.2.2",
|
|
5306
|
+
"modern-web-guidance": "0.0.194",
|
|
5307
|
+
"oxfmt": "0.73.0",
|
|
5287
5308
|
"oxlint": "1.87.0",
|
|
5288
|
-
"oxlint-tsgolint": "7.0.
|
|
5289
|
-
"prettier": "3.9.
|
|
5309
|
+
"oxlint-tsgolint": "7.0.2004",
|
|
5310
|
+
"prettier": "3.9.10",
|
|
5290
5311
|
"release-it": "21.1.1",
|
|
5291
|
-
"tsdown": "0.23.
|
|
5312
|
+
"tsdown": "0.23.1",
|
|
5292
5313
|
"typescript": "6.0.3",
|
|
5293
5314
|
"vitest": "5.0.3",
|
|
5294
5315
|
"pnpm": "11.28.5"
|
|
@@ -5297,18 +5318,18 @@ function addReleaseDeps(deps, config) {
|
|
|
5297
5318
|
case "changesets": deps["@changesets/cli"] = {
|
|
5298
5319
|
"@changesets/cli": "3.0.3",
|
|
5299
5320
|
"@release-it/bumper": "8.0.1",
|
|
5300
|
-
"@types/node": "24.19.
|
|
5321
|
+
"@types/node": "24.19.2",
|
|
5301
5322
|
"@vitest/coverage-v8": "5.0.3",
|
|
5302
|
-
"commit-and-tag-version": "13.
|
|
5323
|
+
"commit-and-tag-version": "13.3.0",
|
|
5303
5324
|
"knip": "6.41.0",
|
|
5304
|
-
"lefthook": "2.2.
|
|
5305
|
-
"modern-web-guidance": "0.0.
|
|
5306
|
-
"oxfmt": "0.
|
|
5325
|
+
"lefthook": "2.2.2",
|
|
5326
|
+
"modern-web-guidance": "0.0.194",
|
|
5327
|
+
"oxfmt": "0.73.0",
|
|
5307
5328
|
"oxlint": "1.87.0",
|
|
5308
|
-
"oxlint-tsgolint": "7.0.
|
|
5309
|
-
"prettier": "3.9.
|
|
5329
|
+
"oxlint-tsgolint": "7.0.2004",
|
|
5330
|
+
"prettier": "3.9.10",
|
|
5310
5331
|
"release-it": "21.1.1",
|
|
5311
|
-
"tsdown": "0.23.
|
|
5332
|
+
"tsdown": "0.23.1",
|
|
5312
5333
|
"typescript": "6.0.3",
|
|
5313
5334
|
"vitest": "5.0.3",
|
|
5314
5335
|
"pnpm": "11.28.5"
|
|
@@ -5425,18 +5446,18 @@ function applyMetadataDefaults(pkg, ctx, changes) {
|
|
|
5425
5446
|
const pinnedPnpm = {
|
|
5426
5447
|
"@changesets/cli": "3.0.3",
|
|
5427
5448
|
"@release-it/bumper": "8.0.1",
|
|
5428
|
-
"@types/node": "24.19.
|
|
5449
|
+
"@types/node": "24.19.2",
|
|
5429
5450
|
"@vitest/coverage-v8": "5.0.3",
|
|
5430
|
-
"commit-and-tag-version": "13.
|
|
5451
|
+
"commit-and-tag-version": "13.3.0",
|
|
5431
5452
|
"knip": "6.41.0",
|
|
5432
|
-
"lefthook": "2.2.
|
|
5433
|
-
"modern-web-guidance": "0.0.
|
|
5434
|
-
"oxfmt": "0.
|
|
5453
|
+
"lefthook": "2.2.2",
|
|
5454
|
+
"modern-web-guidance": "0.0.194",
|
|
5455
|
+
"oxfmt": "0.73.0",
|
|
5435
5456
|
"oxlint": "1.87.0",
|
|
5436
|
-
"oxlint-tsgolint": "7.0.
|
|
5437
|
-
"prettier": "3.9.
|
|
5457
|
+
"oxlint-tsgolint": "7.0.2004",
|
|
5458
|
+
"prettier": "3.9.10",
|
|
5438
5459
|
"release-it": "21.1.1",
|
|
5439
|
-
"tsdown": "0.23.
|
|
5460
|
+
"tsdown": "0.23.1",
|
|
5440
5461
|
"typescript": "6.0.3",
|
|
5441
5462
|
"vitest": "5.0.3",
|
|
5442
5463
|
"pnpm": "11.28.5"
|
|
@@ -5477,25 +5498,25 @@ function buildManagedDevDeps(ctx) {
|
|
|
5477
5498
|
const isMonorepo = ctx.config.structure === "monorepo";
|
|
5478
5499
|
const devDeps = { ...rootDevDeps(ctx.config) };
|
|
5479
5500
|
if (!isMonorepo) Object.assign(devDeps, perPackageDevDeps(ctx.config));
|
|
5480
|
-
if (ctx.config.stack !== "none"
|
|
5481
|
-
if (!isWorkspacePackage(ctx, "@bensandee/tooling")) devDeps["@bensandee/tooling"] = "0.
|
|
5501
|
+
if (ctx.config.stack !== "none") devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.15.0";
|
|
5502
|
+
if (!isWorkspacePackage(ctx, "@bensandee/tooling")) devDeps["@bensandee/tooling"] = "0.94.0";
|
|
5482
5503
|
if (ctx.config.useEslintPlugin && ctx.config.stack !== "none") devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.17.0";
|
|
5483
5504
|
if (wantsAgentCli(ctx)) devDeps[AGENT_CLI_PACKAGE] = "0.1.2";
|
|
5484
5505
|
if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
|
|
5485
5506
|
"@changesets/cli": "3.0.3",
|
|
5486
5507
|
"@release-it/bumper": "8.0.1",
|
|
5487
|
-
"@types/node": "24.19.
|
|
5508
|
+
"@types/node": "24.19.2",
|
|
5488
5509
|
"@vitest/coverage-v8": "5.0.3",
|
|
5489
|
-
"commit-and-tag-version": "13.
|
|
5510
|
+
"commit-and-tag-version": "13.3.0",
|
|
5490
5511
|
"knip": "6.41.0",
|
|
5491
|
-
"lefthook": "2.2.
|
|
5492
|
-
"modern-web-guidance": "0.0.
|
|
5493
|
-
"oxfmt": "0.
|
|
5512
|
+
"lefthook": "2.2.2",
|
|
5513
|
+
"modern-web-guidance": "0.0.194",
|
|
5514
|
+
"oxfmt": "0.73.0",
|
|
5494
5515
|
"oxlint": "1.87.0",
|
|
5495
|
-
"oxlint-tsgolint": "7.0.
|
|
5496
|
-
"prettier": "3.9.
|
|
5516
|
+
"oxlint-tsgolint": "7.0.2004",
|
|
5517
|
+
"prettier": "3.9.10",
|
|
5497
5518
|
"release-it": "21.1.1",
|
|
5498
|
-
"tsdown": "0.23.
|
|
5519
|
+
"tsdown": "0.23.1",
|
|
5499
5520
|
"typescript": "6.0.3",
|
|
5500
5521
|
"vitest": "5.0.3",
|
|
5501
5522
|
"pnpm": "11.28.5"
|
|
@@ -5503,23 +5524,23 @@ function buildManagedDevDeps(ctx) {
|
|
|
5503
5524
|
if (ctx.config.formatter === "prettier") devDeps["prettier"] = {
|
|
5504
5525
|
"@changesets/cli": "3.0.3",
|
|
5505
5526
|
"@release-it/bumper": "8.0.1",
|
|
5506
|
-
"@types/node": "24.19.
|
|
5527
|
+
"@types/node": "24.19.2",
|
|
5507
5528
|
"@vitest/coverage-v8": "5.0.3",
|
|
5508
|
-
"commit-and-tag-version": "13.
|
|
5529
|
+
"commit-and-tag-version": "13.3.0",
|
|
5509
5530
|
"knip": "6.41.0",
|
|
5510
|
-
"lefthook": "2.2.
|
|
5511
|
-
"modern-web-guidance": "0.0.
|
|
5512
|
-
"oxfmt": "0.
|
|
5531
|
+
"lefthook": "2.2.2",
|
|
5532
|
+
"modern-web-guidance": "0.0.194",
|
|
5533
|
+
"oxfmt": "0.73.0",
|
|
5513
5534
|
"oxlint": "1.87.0",
|
|
5514
|
-
"oxlint-tsgolint": "7.0.
|
|
5515
|
-
"prettier": "3.9.
|
|
5535
|
+
"oxlint-tsgolint": "7.0.2004",
|
|
5536
|
+
"prettier": "3.9.10",
|
|
5516
5537
|
"release-it": "21.1.1",
|
|
5517
|
-
"tsdown": "0.23.
|
|
5538
|
+
"tsdown": "0.23.1",
|
|
5518
5539
|
"typescript": "6.0.3",
|
|
5519
5540
|
"vitest": "5.0.3",
|
|
5520
5541
|
"pnpm": "11.28.5"
|
|
5521
5542
|
}["prettier"];
|
|
5522
|
-
addReleaseDeps(devDeps, ctx
|
|
5543
|
+
addReleaseDeps(devDeps, ctx);
|
|
5523
5544
|
return devDeps;
|
|
5524
5545
|
}
|
|
5525
5546
|
async function generatePackageJson(ctx) {
|
|
@@ -5528,7 +5549,7 @@ async function generatePackageJson(ctx) {
|
|
|
5528
5549
|
const allScripts = buildManagedScripts(ctx);
|
|
5529
5550
|
const unmanaged = readUnmanagedDevDepNames(ctx);
|
|
5530
5551
|
const devDeps = buildManagedDevDeps(ctx);
|
|
5531
|
-
if (existing) {
|
|
5552
|
+
if (existing !== void 0 && existing.trim() !== "") {
|
|
5532
5553
|
const pkg = parsePackageJson(existing);
|
|
5533
5554
|
if (!pkg) return {
|
|
5534
5555
|
filePath,
|
|
@@ -5574,18 +5595,18 @@ async function generatePackageJson(ctx) {
|
|
|
5574
5595
|
packageManager: `pnpm@${{
|
|
5575
5596
|
"@changesets/cli": "3.0.3",
|
|
5576
5597
|
"@release-it/bumper": "8.0.1",
|
|
5577
|
-
"@types/node": "24.19.
|
|
5598
|
+
"@types/node": "24.19.2",
|
|
5578
5599
|
"@vitest/coverage-v8": "5.0.3",
|
|
5579
|
-
"commit-and-tag-version": "13.
|
|
5600
|
+
"commit-and-tag-version": "13.3.0",
|
|
5580
5601
|
"knip": "6.41.0",
|
|
5581
|
-
"lefthook": "2.2.
|
|
5582
|
-
"modern-web-guidance": "0.0.
|
|
5583
|
-
"oxfmt": "0.
|
|
5602
|
+
"lefthook": "2.2.2",
|
|
5603
|
+
"modern-web-guidance": "0.0.194",
|
|
5604
|
+
"oxfmt": "0.73.0",
|
|
5584
5605
|
"oxlint": "1.87.0",
|
|
5585
|
-
"oxlint-tsgolint": "7.0.
|
|
5586
|
-
"prettier": "3.9.
|
|
5606
|
+
"oxlint-tsgolint": "7.0.2004",
|
|
5607
|
+
"prettier": "3.9.10",
|
|
5587
5608
|
"release-it": "21.1.1",
|
|
5588
|
-
"tsdown": "0.23.
|
|
5609
|
+
"tsdown": "0.23.1",
|
|
5589
5610
|
"typescript": "6.0.3",
|
|
5590
5611
|
"vitest": "5.0.3",
|
|
5591
5612
|
"pnpm": "11.28.5"
|
|
@@ -5651,24 +5672,49 @@ function warnIfNarrowInclude(ctx, filePath, include) {
|
|
|
5651
5672
|
if (!ctx.exists(testDir)) return;
|
|
5652
5673
|
log$1.warn(`${filePath}: "include" is ${formatInclude(include)}, so a test/ directory beside it sits outside tsc --noEmit and its types go unchecked. Consider "include": ${formatInclude(DEFAULT_INCLUDE)}.`);
|
|
5653
5674
|
}
|
|
5675
|
+
const NODE_PRESET_SUFFIX = "/node";
|
|
5676
|
+
function extendsNodePreset(extendsValue) {
|
|
5677
|
+
return extendsValue.endsWith(NODE_PRESET_SUFFIX);
|
|
5678
|
+
}
|
|
5679
|
+
const VERBATIM_MODULE_SYNTAX_KEY = "verbatimModuleSyntax";
|
|
5654
5680
|
function buildGeneratedTsconfig(ctx, extendsValue, options = {}) {
|
|
5655
5681
|
const dir = options.dir ?? ".";
|
|
5656
5682
|
const include = options.include ?? DEFAULT_INCLUDE;
|
|
5657
5683
|
const config = {
|
|
5658
5684
|
extends: extendsValue,
|
|
5685
|
+
...options.verbatimModuleSyntax ? { compilerOptions: { [VERBATIM_MODULE_SYNTAX_KEY]: true } } : {},
|
|
5659
5686
|
...ctx.exists(path.join(dir, "src")) ? { include: [...include] } : {}
|
|
5660
5687
|
};
|
|
5661
5688
|
return JSON.stringify(config, null, 2) + "\n";
|
|
5662
5689
|
}
|
|
5690
|
+
function withMergedVerbatimKey(template) {
|
|
5691
|
+
const parsed = parseTsconfig(template);
|
|
5692
|
+
parsed.compilerOptions = {
|
|
5693
|
+
...parsed.compilerOptions,
|
|
5694
|
+
[VERBATIM_MODULE_SYNTAX_KEY]: true
|
|
5695
|
+
};
|
|
5696
|
+
return JSON.stringify(parsed, null, 2) + "\n";
|
|
5697
|
+
}
|
|
5663
5698
|
async function generateTsconfig(ctx) {
|
|
5664
5699
|
const filePath = "tsconfig.json";
|
|
5665
5700
|
const existing = ctx.read(filePath);
|
|
5666
5701
|
if (ctx.config.structure === "monorepo") return [generateMonorepoRootTsconfig(ctx), ...ctx.config.detectPackageTypes ? generateMonorepoPackageTsconfigs(ctx) : []];
|
|
5667
5702
|
const extendsValue = `@bensandee/config/tsconfig/${ctx.config.projectType}`;
|
|
5668
5703
|
if (ctx.config.stack !== "typescript") {
|
|
5669
|
-
const
|
|
5670
|
-
const
|
|
5671
|
-
|
|
5704
|
+
const nodePreset = extendsNodePreset(extendsValue);
|
|
5705
|
+
const keylessCurrent = buildGeneratedTsconfig(ctx, extendsValue);
|
|
5706
|
+
const keylessLegacy = buildGeneratedTsconfig(ctx, extendsValue, { include: LEGACY_INCLUDE });
|
|
5707
|
+
const generated = /* @__PURE__ */ new Set([
|
|
5708
|
+
buildGeneratedTsconfig(ctx, extendsValue, { verbatimModuleSyntax: nodePreset }),
|
|
5709
|
+
buildGeneratedTsconfig(ctx, extendsValue, {
|
|
5710
|
+
verbatimModuleSyntax: nodePreset,
|
|
5711
|
+
include: LEGACY_INCLUDE
|
|
5712
|
+
}),
|
|
5713
|
+
keylessCurrent,
|
|
5714
|
+
keylessLegacy,
|
|
5715
|
+
...nodePreset ? [withMergedVerbatimKey(keylessCurrent), withMergedVerbatimKey(keylessLegacy)] : []
|
|
5716
|
+
]);
|
|
5717
|
+
if (existing !== void 0 && generated.has(existing)) return [reportStaleConfig(filePath, `stack is ${ctx.config.stack}`)];
|
|
5672
5718
|
return [{
|
|
5673
5719
|
filePath,
|
|
5674
5720
|
action: "skipped",
|
|
@@ -5676,7 +5722,7 @@ async function generateTsconfig(ctx) {
|
|
|
5676
5722
|
}];
|
|
5677
5723
|
}
|
|
5678
5724
|
if (!existing) {
|
|
5679
|
-
ctx.write(filePath, buildGeneratedTsconfig(ctx, extendsValue));
|
|
5725
|
+
ctx.write(filePath, buildGeneratedTsconfig(ctx, extendsValue, { verbatimModuleSyntax: extendsNodePreset(extendsValue) }));
|
|
5680
5726
|
return [{
|
|
5681
5727
|
filePath,
|
|
5682
5728
|
action: "created",
|
|
@@ -5729,6 +5775,13 @@ function mergeSingleTsconfig(ctx, filePath, extendsValue) {
|
|
|
5729
5775
|
parsed.extends = extendsValue;
|
|
5730
5776
|
changes.push(`added extends: ${extendsValue}`);
|
|
5731
5777
|
}
|
|
5778
|
+
if (extendsNodePreset(String(parsed.extends)) && parsed.compilerOptions?.[VERBATIM_MODULE_SYNTAX_KEY] === void 0) {
|
|
5779
|
+
parsed.compilerOptions = {
|
|
5780
|
+
...parsed.compilerOptions,
|
|
5781
|
+
[VERBATIM_MODULE_SYNTAX_KEY]: true
|
|
5782
|
+
};
|
|
5783
|
+
changes.push(`added compilerOptions.${VERBATIM_MODULE_SYNTAX_KEY}: true`);
|
|
5784
|
+
}
|
|
5732
5785
|
if (!parsed.include) {
|
|
5733
5786
|
const tsconfigDir = path.dirname(filePath);
|
|
5734
5787
|
const srcDir = tsconfigDir === "." ? "src" : path.join(tsconfigDir, "src");
|
|
@@ -6614,7 +6667,7 @@ async function generateTsdown(ctx) {
|
|
|
6614
6667
|
description: "Monorepo: tsdown config belongs in individual packages"
|
|
6615
6668
|
};
|
|
6616
6669
|
if (ctx.config.builder !== "tsdown") {
|
|
6617
|
-
if (ctx.read(filePath) !== void 0 && isGeneratedTemplate(ctx, filePath,
|
|
6670
|
+
if (ctx.read(filePath) !== void 0 && isGeneratedTemplate(ctx, filePath, "import { defineConfig } from \"tsdown\";\n\nexport default defineConfig({\n entry: [\"src/index.ts\"],\n});\n")) return reportStaleConfig(filePath, `builder is ${ctx.config.builder}`);
|
|
6618
6671
|
return {
|
|
6619
6672
|
filePath,
|
|
6620
6673
|
action: "skipped",
|
|
@@ -7994,7 +8047,10 @@ function ignoreBinariesField(binaries) {
|
|
|
7994
8047
|
return binaries.length > 0 ? `\n ignoreBinaries: ${JSON.stringify(binaries)},` : "";
|
|
7995
8048
|
}
|
|
7996
8049
|
function ignorableDependencies(ctx) {
|
|
7997
|
-
|
|
8050
|
+
const dependencies = [];
|
|
8051
|
+
if (wantsAgentCli(ctx)) dependencies.push(AGENT_CLI_PACKAGE);
|
|
8052
|
+
if (wantsCommitAndTagVersion(ctx)) dependencies.push(COMMIT_AND_TAG_VERSION_PACKAGE);
|
|
8053
|
+
return dependencies;
|
|
7998
8054
|
}
|
|
7999
8055
|
function ignoreDependenciesField(dependencies) {
|
|
8000
8056
|
return dependencies.length > 0 ? `\n ignoreDependencies: ${JSON.stringify(dependencies)},` : "";
|
|
@@ -8003,7 +8059,6 @@ function buildKnipConfigSingle(ignoreBinaries, ignoreDependencies) {
|
|
|
8003
8059
|
return `import type { KnipConfig } from "knip";
|
|
8004
8060
|
|
|
8005
8061
|
export default {
|
|
8006
|
-
entry: ["src/index.ts"],
|
|
8007
8062
|
project: ["src/**/*.ts"],${ignoreBinariesField(ignoreBinaries)}${ignoreDependenciesField(ignoreDependencies)}
|
|
8008
8063
|
} satisfies KnipConfig;
|
|
8009
8064
|
`;
|
|
@@ -8018,7 +8073,6 @@ export default {${ignoreBinariesField(ignoreBinaries)}${ignoreDependenciesField(
|
|
|
8018
8073
|
project: [],
|
|
8019
8074
|
},
|
|
8020
8075
|
"packages/*": {
|
|
8021
|
-
entry: ["src/index.ts", "src/bin.ts"],
|
|
8022
8076
|
project: ["src/**/*.ts"],
|
|
8023
8077
|
},
|
|
8024
8078
|
},
|
|
@@ -8112,17 +8166,20 @@ function ensureIgnoreInJson(content, ignoreBinaries, ignoreDependencies, needsHo
|
|
|
8112
8166
|
if (!changed) return void 0;
|
|
8113
8167
|
return JSON.stringify(parsed, null, 2) + "\n";
|
|
8114
8168
|
}
|
|
8169
|
+
function arrayHasEntry(arrayContent, entry) {
|
|
8170
|
+
return arrayContent.includes(`"${entry}"`) || arrayContent.includes(`'${entry}'`);
|
|
8171
|
+
}
|
|
8115
8172
|
function missingIgnoreBinariesInTs(content, binaries) {
|
|
8116
8173
|
const match = /ignoreBinaries:\s*\[([^\]]*)\]/.exec(content);
|
|
8117
8174
|
if (!match) return binaries;
|
|
8118
8175
|
const arrayContent = match[1] ?? "";
|
|
8119
|
-
return binaries.filter((binary) => !arrayContent
|
|
8176
|
+
return binaries.filter((binary) => !arrayHasEntry(arrayContent, binary));
|
|
8120
8177
|
}
|
|
8121
8178
|
function missingIgnoreDependenciesInTs(content, dependencies) {
|
|
8122
8179
|
const match = /ignoreDependencies:\s*\[([^\]]*)\]/.exec(content);
|
|
8123
8180
|
if (!match) return dependencies;
|
|
8124
8181
|
const arrayContent = match[1] ?? "";
|
|
8125
|
-
return dependencies.filter((dependency) => !arrayContent
|
|
8182
|
+
return dependencies.filter((dependency) => !arrayHasEntry(arrayContent, dependency));
|
|
8126
8183
|
}
|
|
8127
8184
|
function insertIntoTsArray(content, field, entries) {
|
|
8128
8185
|
const quoted = entries.map((entry) => `"${entry}"`).join(", ");
|
|
@@ -8204,7 +8261,7 @@ async function generateKnip(ctx) {
|
|
|
8204
8261
|
}
|
|
8205
8262
|
const updated = ensureIgnorePattern(content, existingPath, ignoreBinaries, ignoreDependencies);
|
|
8206
8263
|
if (updated) {
|
|
8207
|
-
ctx.write(existingPath, updated);
|
|
8264
|
+
ctx.write(existingPath, ctx.format(existingPath, updated));
|
|
8208
8265
|
return {
|
|
8209
8266
|
filePath: existingPath,
|
|
8210
8267
|
action: "updated",
|
|
@@ -8336,7 +8393,7 @@ async function generatePnpmWorkspace(ctx) {
|
|
|
8336
8393
|
};
|
|
8337
8394
|
const isMonorepo = ctx.config.structure === "monorepo";
|
|
8338
8395
|
const existing = ctx.read(FILE_PATH);
|
|
8339
|
-
if (existing === void 0) {
|
|
8396
|
+
if (existing === void 0 || existing.trim() === "") {
|
|
8340
8397
|
ctx.write(FILE_PATH, isMonorepo ? FRESH_MONOREPO : FRESH_SINGLE);
|
|
8341
8398
|
return {
|
|
8342
8399
|
filePath: FILE_PATH,
|
|
@@ -8415,7 +8472,7 @@ function writeEmbedded(ctx, file) {
|
|
|
8415
8472
|
}
|
|
8416
8473
|
const NOT_BUILT = "run `pnpm build` from the repo root";
|
|
8417
8474
|
async function generateClaudeHooks(ctx, embedded = {
|
|
8418
|
-
hook: "/**\n * bst-hook -- Claude Code PreToolUse hook\n * @bensandee/claude-hooks\n * https://github.com/bensandee/tooling\n *\n * Auto-generated by @bensandee/tooling repo:sync -- do not edit manually.\n */\nimport * as fs from \"node:fs\";\nimport { existsSync, readFileSync, readdirSync, realpathSync, statSync } from \"node:fs\";\nimport * as path from \"node:path\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { execFileSync } from \"node:child_process\";\n\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/util.js\nfunction joinValues(array, separator = \"|\") {\n return array.map((val) => stringifyPrimitive(val)).join(separator);\n}\nfunction jsonStringifyReplacer(_, value) {\n if (typeof value === \"bigint\") return value.toString();\n return value;\n}\nvar Cached = class {\n constructor(getter) {\n this._getter = getter;\n this._value = void 0;\n }\n get value() {\n const getter = this._getter;\n if (getter !== void 0) {\n this._value = getter();\n this._getter = void 0;\n }\n return this._value;\n }\n};\nfunction cached(getter) {\n return new Cached(getter);\n}\nfunction nullish(input) {\n return input === null || input === void 0;\n}\nfunction cleanRegex(source) {\n const start = source.startsWith(\"^\") ? 1 : 0;\n const end = source.endsWith(\"$\") ? source.length - 1 : source.length;\n return source.slice(start, end);\n}\nfunction assignProp(target, prop, value) {\n Object.defineProperty(target, prop, {\n value,\n writable: true,\n enumerable: true,\n configurable: true\n });\n}\nconst captureStackTrace = \"captureStackTrace\" in Error ? Error.captureStackTrace : (..._args) => {};\nfunction isObject(data) {\n return typeof data === \"object\" && data !== null && !Array.isArray(data);\n}\nfunction isPlainObject(o) {\n if (isObject(o) === false) return false;\n const ctor = o.constructor;\n if (ctor === void 0) return true;\n if (typeof ctor !== \"function\") return true;\n const prot = ctor.prototype;\n if (isObject(prot) === false) return false;\n if (Object.prototype.hasOwnProperty.call(prot, \"isPrototypeOf\") === false) return false;\n return true;\n}\nfunction shallowClone(o) {\n if (isPlainObject(o)) return { ...o };\n if (Array.isArray(o)) return [...o];\n if (o instanceof Map) return new Map(o);\n if (o instanceof Set) return new Set(o);\n return o;\n}\nfunction clone(inst, def, params) {\n const cl = new inst._zod.constr(def ?? inst._zod.def);\n if (!def || params?.parent) cl._zod.parent = inst;\n return cl;\n}\nfunction normalizeParams(_params) {\n const params = _params;\n if (!params) return {};\n if (typeof params === \"string\") return { error: () => params };\n if (params?.message !== void 0) {\n if (params?.error !== void 0) throw new Error(\"Cannot specify both `message` and `error` params\");\n params.error = params.message;\n }\n delete params.message;\n if (typeof params.error === \"string\") return {\n ...params,\n error: () => params.error\n };\n return params;\n}\nfunction stringifyPrimitive(value) {\n if (typeof value === \"bigint\") return value.toString() + \"n\";\n if (typeof value === \"string\") return `\"${value}\"`;\n return `${value}`;\n}\nfunction optionalKeys(shape) {\n return Object.keys(shape).filter((k) => {\n return shape[k]._zod.optin !== void 0 && shape[k]._zod.optout === \"optional\";\n });\n}\nfunction aborted(x, startIndex = 0) {\n if (x.aborted === true) return true;\n for (let i = startIndex; i < x.issues.length; i++) if (x.issues[i]?.continue !== true) return true;\n return false;\n}\nfunction explicitlyAborted(x, startIndex = 0) {\n if (x.aborted === true) return true;\n for (let i = startIndex; i < x.issues.length; i++) if (x.issues[i]?.continue === false) return true;\n return false;\n}\nfunction prefixIssues(path, issues) {\n return issues.map((iss) => {\n var _a;\n (_a = iss).path ?? (_a.path = []);\n iss.path.unshift(path);\n return iss;\n });\n}\nfunction unwrapMessage(message) {\n return typeof message === \"string\" ? message : message?.message;\n}\nfunction attachSchema(issues, start, inst) {\n var _a;\n for (let i = start; i < issues.length; i++) (_a = issues[i]).schema ?? (_a.schema = inst);\n}\nfunction finalizeIssue(iss, ctx, config) {\n var _a;\n const traits = iss.inst?._zod?.traits;\n if (traits?.has(\"$ZodType\")) {\n if (traits.has(\"$ZodCheck\")) (_a = iss).schema ?? (_a.schema = iss.inst);\n else iss.schema = iss.inst;\n }\n const schemaError = iss.schema !== iss.inst ? iss.schema?._zod.def?.error : void 0;\n const message = iss.message ? iss.message : unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(schemaError?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config.customError?.(iss)) ?? unwrapMessage(config.localeError?.(iss)) ?? \"Invalid input\";\n const full = {};\n for (const k of Object.keys(iss)) {\n if (k === \"inst\" || k === \"schema\" || k === \"continue\" || k === \"input\" || k === \"__proto__\") continue;\n full[k] = iss[k];\n }\n full.path ?? (full.path = []);\n full.message = message;\n if (ctx?.reportInput) full.input = iss.input;\n return full;\n}\nconst highSurrogate = /[\\uD800-\\uDBFF]/;\nfunction codePointLength(str) {\n const units = str.length;\n if (!highSurrogate.test(str)) return units;\n let count = units;\n for (let i = 0; i < units - 1; i++) if ((str.charCodeAt(i) & 64512) === 55296 && (str.charCodeAt(i + 1) & 64512) === 56320) {\n count--;\n i++;\n }\n return count;\n}\nfunction getLengthableOrigin(input) {\n if (Array.isArray(input)) return \"array\";\n if (typeof input === \"string\") return \"string\";\n return \"unknown\";\n}\nfunction parsedType(data) {\n const t = typeof data;\n switch (t) {\n case \"number\": return Number.isNaN(data) ? \"nan\" : \"number\";\n case \"object\": {\n if (data === null) return \"null\";\n if (Array.isArray(data)) return \"array\";\n const obj = data;\n if (obj && Object.getPrototypeOf(obj) !== Object.prototype && \"constructor\" in obj && obj.constructor) return obj.constructor.name;\n }\n }\n return t;\n}\nfunction members(proto, table) {\n for (const key in table) {\n const desc = Object.getOwnPropertyDescriptor(table, key);\n if (desc.get) Object.defineProperty(proto, key, {\n ...desc,\n enumerable: false\n });\n else defineBound(proto, key, desc.value);\n }\n}\nfunction own(inst, key, value, enumerable = true) {\n Object.defineProperty(inst, key, {\n configurable: true,\n writable: true,\n enumerable,\n value\n });\n return value;\n}\nfunction hide(inst, key, value) {\n return own(inst, key, value, false);\n}\nfunction defineBound(proto, key, fn) {\n Object.defineProperty(proto, key, {\n configurable: true,\n get() {\n return this == null ? fn : own(this, key, fn.bind(this));\n },\n set(value) {\n own(this, key, value);\n }\n });\n}\nfunction claim(inst, sentinel) {\n const proto = Object.getPrototypeOf(inst);\n return sentinel in proto ? void 0 : proto;\n}\nlet installing;\nlet broke = false;\nconst breaker = {\n configurable: true,\n get() {\n broke = true;\n }\n};\nfunction defineLazyInternal(inst, key, compute) {\n const proto = Object.getPrototypeOf(inst._zod);\n if (key in proto && installing !== inst._zod) {\n installing = void 0;\n return;\n }\n installing = inst._zod;\n Object.defineProperty(proto, key, {\n configurable: true,\n get() {\n Object.defineProperty(this, key, breaker);\n const outer = broke;\n broke = false;\n try {\n const value = compute(this);\n if (broke) delete this[key];\n else Object.defineProperty(this, key, {\n configurable: true,\n writable: true,\n value\n });\n broke = broke || outer;\n return value;\n } catch (err) {\n delete this[key];\n broke = broke || outer;\n throw err;\n }\n },\n set(value) {\n Object.defineProperty(this, key, {\n configurable: true,\n writable: true,\n value\n });\n }\n });\n}\nfunction installLazyProp(inst, key, make, enumerable) {\n const proto = claim(inst, key);\n if (!proto) return;\n Object.defineProperty(proto, key, {\n configurable: true,\n get() {\n const desc = {\n configurable: true,\n writable: true,\n enumerable,\n value: void 0\n };\n Object.defineProperty(this, key, desc);\n desc.value = make(this);\n Object.defineProperty(this, key, desc);\n return desc.value;\n },\n set(value) {\n Object.defineProperty(this, key, {\n configurable: true,\n writable: true,\n enumerable,\n value\n });\n }\n });\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/core.js\nvar _a;\nconst _zodDesc = {\n value: void 0,\n enumerable: false\n};\nlet _E = \"captureStackTrace\" in Error ? Error : null;\nfunction newError(Definition) {\n const E = _E;\n if (E) {\n const saved = E.stackTraceLimit;\n if (typeof saved === \"number\") {\n try {\n E.stackTraceLimit = 0;\n } catch {\n _E = null;\n return new Definition();\n }\n try {\n return new Definition();\n } finally {\n E.stackTraceLimit = saved;\n }\n }\n }\n return new Definition();\n}\nfunction $constructor(name, initializer, proto, params) {\n const zodProto = {};\n function Internals(def) {\n this.def = def;\n this.constr = _;\n this.traits = /* @__PURE__ */ new Set();\n }\n Internals.prototype = zodProto;\n const protoMembers = proto;\n const initialized = protoMembers && /* @__PURE__ */ new WeakSet();\n function init(inst, def) {\n if (!inst._zod) {\n _zodDesc.value = new Internals(def);\n try {\n Object.defineProperty(inst, \"_zod\", _zodDesc);\n } finally {\n _zodDesc.value = void 0;\n }\n } else if (inst._zod.traits.has(name)) return;\n inst._zod.traits.add(name);\n initializer(inst, def);\n if (initialized) {\n const own = Object.getPrototypeOf(inst);\n const ctorProto = inst._zod.constr.prototype;\n let up = own;\n while (up && up !== ctorProto) up = Object.getPrototypeOf(up);\n const target = up ?? own;\n if (!initialized.has(target)) {\n initialized.add(target);\n members(target, protoMembers);\n }\n }\n const proto = _.prototype;\n for (const k in proto) {\n if (!Object.prototype.hasOwnProperty.call(proto, k)) continue;\n if (!(k in inst)) inst[k] = proto[k].bind(inst);\n }\n }\n const Parent = params?.Parent ?? Object;\n class Definition extends Parent {}\n Object.defineProperty(Definition, \"name\", { value: name });\n function _(def) {\n const inst = params?.Parent ? newError(Definition) : this;\n init(inst, def);\n const deferred = inst._zod.deferred;\n if (deferred) {\n for (const fn of deferred) fn();\n inst._zod.deferred = void 0;\n }\n const pp = globalThis.__zod_globalConfig?.postProcessor;\n if (pp) pp(inst);\n return inst;\n }\n Object.defineProperty(_, \"init\", { value: init });\n Object.defineProperty(_, Symbol.hasInstance, { value: (inst) => {\n if (params?.Parent && inst instanceof params.Parent) return true;\n return inst?._zod?.traits?.has(name);\n } });\n Object.defineProperty(_, \"name\", { value: name });\n return _;\n}\nvar $ZodAsyncError = class extends Error {\n constructor() {\n super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`);\n }\n};\n(_a = globalThis).__zod_globalConfig ?? (_a.__zod_globalConfig = {});\nconst globalConfig = globalThis.__zod_globalConfig;\nfunction config(newConfig) {\n if (newConfig) Object.assign(globalConfig, newConfig);\n return globalConfig;\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/errors.js\nfunction _getMessage() {\n const internals = this._zod;\n internals.message ?? (internals.message = JSON.stringify(internals.def, jsonStringifyReplacer, 2));\n return internals.message;\n}\nfunction _setMessage(value) {\n this._zod.message = value;\n}\nconst _messageDesc = {\n get: _getMessage,\n set: _setMessage,\n enumerable: true,\n configurable: true\n};\nconst _issuesDesc = {\n value: void 0,\n enumerable: false\n};\nconst _installedToString = /* @__PURE__ */ new WeakSet([Object.prototype, Error.prototype]);\nconst initializer = (inst, def) => {\n inst.name = \"$ZodError\";\n _issuesDesc.value = def;\n Object.defineProperty(inst, \"issues\", _issuesDesc);\n _issuesDesc.value = void 0;\n Object.defineProperty(inst, \"message\", _messageDesc);\n const proto = Object.getPrototypeOf(inst);\n if (!_installedToString.has(proto)) {\n _installedToString.add(proto);\n Object.defineProperty(proto, \"toString\", {\n configurable: true,\n enumerable: false,\n get() {\n const value = () => this.message;\n Object.defineProperty(this, \"toString\", {\n value,\n configurable: true,\n writable: true\n });\n return value;\n },\n set(value) {\n Object.defineProperty(this, \"toString\", {\n value,\n configurable: true,\n writable: true\n });\n }\n });\n }\n};\nconst $ZodError = $constructor(\"$ZodError\", initializer);\nconst $ZodRealError = $constructor(\"$ZodError\", initializer, void 0, { Parent: Error });\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/parse.js\nconst _parse = (_Err) => {\n const fn = (schema, value, _ctx, _params) => {\n const ctx = _ctx ? {\n ..._ctx,\n async: false\n } : { async: false };\n const result = schema._zod.run({\n value,\n issues: []\n }, ctx);\n if (result instanceof Promise) throw new $ZodAsyncError();\n if (result.issues.length) {\n const e = new ((_params?.Err) ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));\n captureStackTrace(e, _params?.callee ?? fn);\n throw e;\n }\n return result.value;\n };\n return fn;\n};\nconst parse = /* @__PURE__*/ _parse($ZodRealError);\nconst _parseAsync = (_Err) => {\n const fn = async (schema, value, _ctx, params) => {\n const ctx = _ctx ? {\n ..._ctx,\n async: true\n } : { async: true };\n let result = schema._zod.run({\n value,\n issues: []\n }, ctx);\n if (result instanceof Promise) result = await result;\n if (result.issues.length) {\n const e = new ((params?.Err) ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));\n captureStackTrace(e, params?.callee ?? fn);\n throw e;\n }\n return result.value;\n };\n return fn;\n};\nconst parseAsync = /* @__PURE__*/ _parseAsync($ZodRealError);\nconst _safeParse = (_Err) => (schema, value, _ctx) => {\n const ctx = _ctx ? {\n ..._ctx,\n async: false\n } : { async: false };\n const result = schema._zod.run({\n value,\n issues: []\n }, ctx);\n if (result instanceof Promise) throw new $ZodAsyncError();\n return result.issues.length ? failure(_Err, result.issues, ctx) : {\n success: true,\n data: result.value\n };\n};\nconst safeParse = /* @__PURE__*/ _safeParse($ZodRealError);\nfunction failure(Err, issues, ctx) {\n let error;\n return {\n success: false,\n get error() {\n if (!error) {\n error = new Err(issues.map((iss) => finalizeIssue(iss, ctx, config())));\n issues = void 0;\n ctx = void 0;\n }\n return error;\n },\n set error(e) {\n error = e;\n issues = void 0;\n ctx = void 0;\n }\n };\n}\nconst _safeParseAsync = (_Err) => async (schema, value, _ctx) => {\n const ctx = _ctx ? {\n ..._ctx,\n async: true\n } : { async: true };\n let result = schema._zod.run({\n value,\n issues: []\n }, ctx);\n if (result instanceof Promise) result = await result;\n return result.issues.length ? failure(_Err, result.issues, ctx) : {\n success: true,\n data: result.value\n };\n};\nconst safeParseAsync = /* @__PURE__*/ _safeParseAsync($ZodRealError);\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/regexes.js\nconst anyString = /^[\\s\\S]{0,}$/;\nconst number = /^-?\\d+(?:\\.\\d+)?$/;\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/checks.js\nconst $ZodCheck = /*@__PURE__*/ $constructor(\"$ZodCheck\", (inst, def) => {\n var _a;\n inst._zod ?? (inst._zod = {});\n inst._zod.def = def;\n (_a = inst._zod).onattach ?? (_a.onattach = []);\n});\nconst _whenHasLength = (payload) => {\n const val = payload.value;\n return !nullish(val) && val.length !== void 0;\n};\nconst $ZodCheckMinLength = /*@__PURE__*/ $constructor(\"$ZodCheckMinLength\", (inst, def) => {\n var _a;\n $ZodCheck.init(inst, def);\n (_a = inst._zod.def).when ?? (_a.when = _whenHasLength);\n inst._zod.check = (payload) => {\n const input = payload.value;\n const units = input.length;\n if ((typeof input === \"string\" && units >= def.minimum && units < def.minimum * 2 ? codePointLength(input) : units) >= def.minimum) return;\n const origin = getLengthableOrigin(input);\n payload.issues.push({\n origin,\n code: \"too_small\",\n minimum: def.minimum,\n inclusive: true,\n input,\n inst,\n continue: !def.abort\n });\n };\n});\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/versions.js\nconst version = {\n major: 4,\n minor: 6,\n patch: 5\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/schemas.js\nconst $ZodType = /*@__PURE__*/ $constructor(\"$ZodType\", (inst, def) => {\n var _a;\n inst ?? (inst = {});\n inst._zod.def = def;\n inst._zod.bag = inst._zod.bag || {};\n inst._zod.version = version;\n const defChecks = inst._zod.def.checks;\n const checks = inst._zod.traits.has(\"$ZodCheck\") ? [inst, ...defChecks ?? []] : defChecks?.length ? [...defChecks] : [];\n for (const ch of checks) for (const fn of ch._zod.onattach) fn(inst);\n if (checks.length === 0) {\n (_a = inst._zod).deferred ?? (_a.deferred = []);\n inst._zod.deferred?.push(() => {\n inst._zod.run = inst._zod.parse;\n });\n } else {\n const runChecks = (payload, checks, ctx) => {\n if (payload.memo) return payload;\n let isAborted = aborted(payload);\n let asyncResult;\n for (const ch of checks) {\n if (ch._zod.def.when) {\n if (explicitlyAborted(payload)) continue;\n if (!ch._zod.def.when(payload)) continue;\n } else if (isAborted) continue;\n const currLen = payload.issues.length;\n const _ = ch._zod.check(payload);\n if (_ instanceof Promise && ctx?.async === false) throw new $ZodAsyncError();\n if (asyncResult || _ instanceof Promise) asyncResult = (asyncResult ?? Promise.resolve()).then(async () => {\n await _;\n if (payload.issues.length === currLen) return;\n attachSchema(payload.issues, currLen, inst);\n if (!isAborted) isAborted = aborted(payload, currLen);\n });\n else {\n if (payload.issues.length === currLen) continue;\n attachSchema(payload.issues, currLen, inst);\n if (!isAborted) isAborted = aborted(payload, currLen);\n }\n }\n if (asyncResult) return asyncResult.then(() => {\n return payload;\n });\n return payload;\n };\n const handleCanaryResult = (canary, payload, ctx) => {\n if (aborted(canary)) {\n canary.aborted = true;\n return canary;\n }\n const checkResult = runChecks(payload, checks, ctx);\n if (checkResult instanceof Promise) {\n if (ctx.async === false) throw new $ZodAsyncError();\n return checkResult.then((checkResult) => inst._zod.parse(checkResult, ctx));\n }\n return inst._zod.parse(checkResult, ctx);\n };\n inst._zod.run = (payload, ctx) => {\n if (ctx.skipChecks) return inst._zod.parse(payload, ctx);\n if (ctx.direction === \"backward\") {\n const canary = inst._zod.parse({\n value: payload.value,\n issues: []\n }, {\n ...ctx,\n skipChecks: true\n });\n if (canary instanceof Promise) return canary.then((canary) => {\n return handleCanaryResult(canary, payload, ctx);\n });\n return handleCanaryResult(canary, payload, ctx);\n }\n const result = inst._zod.parse(payload, ctx);\n if (result instanceof Promise) {\n if (ctx.async === false) throw new $ZodAsyncError();\n return result.then((result) => runChecks(result, checks, ctx));\n }\n return runChecks(result, checks, ctx);\n };\n }\n}, {\n get \"~standard\"() {\n return hide(this, \"~standard\", standardProps(this));\n },\n set \"~standard\"(value) {\n own(this, \"~standard\", value);\n }\n});\nconst toStandardResult = (r, ctx) => r.issues.length ? { issues: r.issues.map((iss) => finalizeIssue(iss, ctx, config())) } : { value: r.value };\nasync function validateAsync(inst, value) {\n const ctx = { async: true };\n return toStandardResult(await inst._zod.run({\n value,\n issues: []\n }, ctx), ctx);\n}\nfunction standardProps(inst) {\n return {\n validate: (value) => {\n const ctx = { async: false };\n try {\n const r = inst._zod.run({\n value,\n issues: []\n }, ctx);\n if (!(r instanceof Promise)) return toStandardResult(r, ctx);\n } catch (_) {}\n return validateAsync(inst, value);\n },\n vendor: \"zod\",\n version: 1\n };\n}\nconst $ZodString = /*@__PURE__*/ $constructor(\"$ZodString\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.pattern = def.pattern ?? anyString;\n inst._zod.parse = (payload, _) => {\n if (def.coerce) try {\n payload.value = String(payload.value);\n } catch (_) {}\n if (typeof payload.value === \"string\") return payload;\n payload.issues.push({\n expected: \"string\",\n code: \"invalid_type\",\n input: payload.value,\n inst\n });\n return payload;\n };\n});\nconst $ZodUnknown = /*@__PURE__*/ $constructor(\"$ZodUnknown\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.parse = (payload) => payload;\n});\nfunction handleArrayResult(result, final, index) {\n if (result.issues.length) final.issues.push(...prefixIssues(index, result.issues));\n final.value[index] = result.value;\n}\nconst $ZodArray = /*@__PURE__*/ $constructor(\"$ZodArray\", (inst, def) => {\n $ZodType.init(inst, def);\n const memo = globalConfig.memoizer;\n memo?.attach(inst);\n inst._zod.parse = (payload, ctx) => {\n const input = payload.value;\n if (!Array.isArray(input)) {\n payload.issues.push({\n expected: \"array\",\n code: \"invalid_type\",\n input,\n inst\n });\n return payload;\n }\n payload.value = memo ? memo.alloc(inst, payload, Array(input.length), ctx) : Array(input.length);\n const proms = [];\n const abortEarly = ctx?.abortEarly;\n for (let i = 0; i < input.length; i++) {\n const item = input[i];\n const result = def.element._zod.run({\n value: item,\n issues: []\n }, ctx);\n if (result instanceof Promise) proms.push(result.then((result) => handleArrayResult(result, payload, i)));\n else {\n handleArrayResult(result, payload, i);\n if (abortEarly && result.issues.length !== 0 && aborted(result)) break;\n }\n }\n if (proms.length) return Promise.all(proms).then(() => payload);\n return payload;\n };\n});\nfunction handlePropertyResult(result, final, key, input, optin, optout) {\n const isPresent = key in input;\n const isOptionalOut = optout === \"optional\";\n if (!isPresent && isOptionalOut && optin === \"optional\") return;\n if (result.issues.length) {\n if (optin !== void 0 && isOptionalOut && !isPresent) return;\n final.issues.push(...prefixIssues(key, result.issues));\n }\n if (!isPresent && optin === void 0) {\n if (!result.issues.length) final.issues.push({\n code: \"invalid_type\",\n expected: \"nonoptional\",\n input: void 0,\n path: [key]\n });\n return;\n }\n if (result.value === void 0) {\n if (isPresent || optin === \"defaulted\" && !isOptionalOut) final.value[key] = void 0;\n } else final.value[key] = result.value;\n}\nconst NO_SYMBOL_KEYS = [];\nfunction normalizeDef(def) {\n const keys = Object.keys(def.shape);\n const ownSymbols = Object.getOwnPropertySymbols(def.shape);\n const symbolKeys = ownSymbols.length ? ownSymbols : NO_SYMBOL_KEYS;\n const allKeys = symbolKeys.length ? [...keys, ...symbolKeys] : keys;\n for (const k of allKeys) if (!def.shape?.[k]?._zod?.traits?.has(\"$ZodType\")) throw new Error(`Invalid element at key \"${String(k)}\": expected a Zod schema`);\n const okeys = optionalKeys(def.shape);\n return {\n ...def,\n allKeys,\n symbolKeys,\n keySet: new Set(keys),\n numKeys: keys.length,\n optionalKeys: new Set(okeys)\n };\n}\nfunction handleCatchall(proms, input, payload, ctx, def, inst, abortEarly) {\n const unrecognized = [];\n const keySet = def.keySet;\n const _catchall = def.catchall._zod;\n const t = _catchall.def.type;\n const optin = _catchall.optin;\n const optout = _catchall.optout;\n let seen = 0;\n for (const key in input) {\n if (abortEarly && payload.issues.length !== seen) {\n if (aborted(payload, seen)) break;\n seen = payload.issues.length;\n }\n if (keySet.has(key)) continue;\n if (key === \"__proto__\") {\n if (t === \"never\") unrecognized.push(key);\n continue;\n }\n if (t === \"never\") {\n unrecognized.push(key);\n continue;\n }\n const r = _catchall.run({\n value: input[key],\n issues: []\n }, ctx);\n if (r instanceof Promise) proms.push(r.then((r) => handlePropertyResult(r, payload, key, input, optin, optout)));\n else handlePropertyResult(r, payload, key, input, optin, optout);\n }\n if (unrecognized.length) payload.issues.push({\n code: \"unrecognized_keys\",\n keys: unrecognized,\n input,\n inst,\n continue: true\n });\n if (!proms.length) return payload;\n return Promise.all(proms).then(() => {\n return payload;\n });\n}\nconst $ZodObject = /*@__PURE__*/ $constructor(\"$ZodObject\", (inst, def) => {\n $ZodType.init(inst, def);\n const desc = Object.getOwnPropertyDescriptor(def, \"shape\");\n const sh = desc?.get ? desc.get.raw : def.shape ?? {};\n if (sh) {\n const get = () => {\n const newSh = { ...sh };\n Object.defineProperty(def, \"shape\", { value: newSh });\n get.raw = newSh;\n return newSh;\n };\n get.raw = sh;\n Object.defineProperty(def, \"shape\", { get });\n }\n const _normalized = cached(() => normalizeDef(def));\n defineLazyInternal(inst, \"propValues\", (zod) => {\n const shape = zod.def.shape;\n const propValues = {};\n for (const key in shape) {\n const field = shape[key]._zod;\n if (field.values) {\n if (!Object.prototype.hasOwnProperty.call(propValues, key)) assignProp(propValues, key, /* @__PURE__ */ new Set());\n for (const v of field.values) propValues[key].add(v);\n if (field.optin !== void 0) propValues[key].add(void 0);\n }\n }\n return propValues;\n });\n const isObject$2 = isObject;\n const catchall = def.catchall;\n let value;\n const memo = globalConfig.memoizer;\n memo?.attach(inst);\n inst._zod.parse = (payload, ctx) => {\n value ?? (value = _normalized.value);\n const input = payload.value;\n if (!isObject$2(input)) {\n payload.issues.push({\n expected: \"object\",\n code: \"invalid_type\",\n input,\n inst\n });\n return payload;\n }\n payload.value = memo ? memo.alloc(inst, payload, {}, ctx) : {};\n const proms = [];\n const shape = value.shape;\n const abortEarly = ctx?.abortEarly;\n let seen = payload.issues.length;\n for (const key of value.allKeys) {\n if (abortEarly && payload.issues.length !== seen) {\n if (aborted(payload, seen)) break;\n seen = payload.issues.length;\n }\n if (key === \"__proto__\") continue;\n const el = shape[key];\n const optin = el._zod.optin;\n const optout = el._zod.optout;\n const r = el._zod.run({\n value: input[key],\n issues: []\n }, ctx);\n if (r instanceof Promise) proms.push(r.then((r) => handlePropertyResult(r, payload, key, input, optin, optout)));\n else handlePropertyResult(r, payload, key, input, optin, optout);\n }\n if (!catchall) return proms.length ? Promise.all(proms).then(() => payload) : payload;\n return handleCatchall(proms, input, payload, ctx, _normalized.value, inst, abortEarly === true);\n };\n});\nconst $ZodRecord = /*@__PURE__*/ $constructor(\"$ZodRecord\", (inst, def) => {\n $ZodType.init(inst, def);\n const memo = globalConfig.memoizer;\n memo?.attach(inst);\n inst._zod.parse = (payload, ctx) => {\n const input = payload.value;\n if (!isPlainObject(input)) {\n payload.issues.push({\n expected: \"record\",\n code: \"invalid_type\",\n input,\n inst\n });\n return payload;\n }\n const proms = [];\n const values = def.keyType._zod.values;\n if (values && !def.partial) {\n payload.value = memo ? memo.alloc(inst, payload, {}, ctx) : {};\n const recordKeys = /* @__PURE__ */ new Set();\n for (const key of values) if (typeof key === \"string\" || typeof key === \"number\" || typeof key === \"symbol\") {\n recordKeys.add(typeof key === \"number\" ? key.toString() : key);\n if (key === \"__proto__\") continue;\n const keyResult = def.keyType._zod.run({\n value: key,\n issues: []\n }, ctx);\n if (keyResult instanceof Promise) throw new Error(\"Async schemas not supported in object keys currently\");\n if (keyResult.issues.length) {\n payload.issues.push({\n code: \"invalid_key\",\n origin: \"record\",\n issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),\n input: key,\n path: [key],\n inst\n });\n continue;\n }\n const outKey = keyResult.value;\n if (outKey === \"__proto__\") continue;\n const result = def.valueType._zod.run({\n value: input[key],\n issues: []\n }, ctx);\n if (result instanceof Promise) proms.push(result.then((result) => {\n if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));\n payload.value[outKey] = result.value;\n }));\n else {\n if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));\n payload.value[outKey] = result.value;\n }\n }\n let unrecognized;\n for (const key in input) if (!recordKeys.has(key)) {\n if (def.mode === \"loose\") {\n if (key === \"__proto__\") continue;\n payload.value[key] = input[key];\n } else {\n unrecognized = unrecognized ?? [];\n unrecognized.push(key);\n }\n }\n if (unrecognized && unrecognized.length > 0) payload.issues.push({\n code: \"unrecognized_keys\",\n input,\n inst,\n keys: unrecognized,\n continue: true\n });\n } else {\n payload.value = memo ? memo.alloc(inst, payload, {}, ctx) : {};\n let unrecognized;\n for (const key of Reflect.ownKeys(input)) {\n if (key === \"__proto__\") continue;\n if (!Object.prototype.propertyIsEnumerable.call(input, key)) continue;\n let keyResult = def.keyType._zod.run({\n value: key,\n issues: []\n }, ctx);\n if (keyResult instanceof Promise) throw new Error(\"Async schemas not supported in object keys currently\");\n if (typeof key === \"string\" && number.test(key) && keyResult.issues.length) {\n const retryResult = def.keyType._zod.run({\n value: Number(key),\n issues: []\n }, ctx);\n if (retryResult instanceof Promise) throw new Error(\"Async schemas not supported in object keys currently\");\n if (retryResult.issues.length === 0) keyResult = retryResult;\n }\n if (keyResult.issues.length) {\n if (def.mode === \"loose\") payload.value[key] = input[key];\n else if (values) {\n unrecognized = unrecognized ?? [];\n unrecognized.push(key);\n } else payload.issues.push({\n code: \"invalid_key\",\n origin: \"record\",\n issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),\n input: key,\n path: [key],\n inst\n });\n continue;\n }\n const outKey = keyResult.value;\n if (outKey === \"__proto__\") continue;\n const result = def.valueType._zod.run({\n value: input[key],\n issues: []\n }, ctx);\n if (result instanceof Promise) proms.push(result.then((result) => {\n if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));\n payload.value[outKey] = result.value;\n }));\n else {\n if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));\n payload.value[outKey] = result.value;\n }\n }\n if (unrecognized && unrecognized.length > 0) payload.issues.push({\n code: \"unrecognized_keys\",\n input,\n inst,\n keys: unrecognized,\n continue: true\n });\n }\n if (proms.length) return Promise.all(proms).then(() => payload);\n return payload;\n };\n});\nfunction handleOptionalResult(payload, result) {\n payload.value = result.issues.length ? void 0 : result.value;\n return payload;\n}\nconst $ZodOptional = /*@__PURE__*/ $constructor(\"$ZodOptional\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazyInternal(inst, \"optin\", (zod) => zod.def.innerType._zod.optin === \"defaulted\" ? \"defaulted\" : \"optional\");\n inst._zod.optout = \"optional\";\n defineLazyInternal(inst, \"values\", (zod) => {\n const values = zod.def.innerType._zod.values;\n return values ? /* @__PURE__ */ new Set([...values, void 0]) : void 0;\n });\n defineLazyInternal(inst, \"pattern\", (zod) => {\n const pattern = zod.def.innerType._zod.pattern;\n return pattern ? new RegExp(`^(${cleanRegex(pattern.source)})?$`) : void 0;\n });\n inst._zod.parse = (payload, ctx) => {\n if (payload.value === void 0) {\n if (def.innerType._zod.optin !== \"defaulted\") return payload;\n const result = def.innerType._zod.run({\n value: payload.value,\n issues: []\n }, ctx);\n if (result instanceof Promise) return result.then((result) => handleOptionalResult(payload, result));\n return handleOptionalResult(payload, result);\n }\n return def.innerType._zod.run(payload, ctx);\n };\n});\nconst $ZodDefault = /*@__PURE__*/ $constructor(\"$ZodDefault\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.optin = \"defaulted\";\n defineLazyInternal(inst, \"values\", (zod) => zod.def.innerType._zod.values);\n inst._zod.parse = (payload, ctx) => {\n if (ctx.direction === \"backward\") return def.innerType._zod.run(payload, ctx);\n if (payload.value === void 0) {\n payload.value = def.defaultValue;\n return payload;\n }\n const result = def.innerType._zod.run(payload, ctx);\n if (result instanceof Promise) return result.then((result) => handleDefaultResult(result, def));\n return handleDefaultResult(result, def);\n };\n});\nfunction handleDefaultResult(payload, def) {\n if (payload.value === void 0) payload.value = def.defaultValue;\n return payload;\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/locales/en.js\nconst error = () => {\n const Sizable = {\n string: {\n unit: \"characters\",\n verb: \"to have\"\n },\n file: {\n unit: \"bytes\",\n verb: \"to have\"\n },\n array: {\n unit: \"items\",\n verb: \"to have\"\n },\n set: {\n unit: \"items\",\n verb: \"to have\"\n },\n map: {\n unit: \"entries\",\n verb: \"to have\"\n }\n };\n function getSizing(origin) {\n return Sizable[origin] ?? null;\n }\n const FormatDictionary = {\n regex: \"input\",\n email: \"email address\",\n url: \"URL\",\n emoji: \"emoji\",\n uuid: \"UUID\",\n uuidv4: \"UUIDv4\",\n uuidv6: \"UUIDv6\",\n nanoid: \"nanoid\",\n guid: \"GUID\",\n cuid: \"cuid\",\n cuid2: \"cuid2\",\n ulid: \"ULID\",\n xid: \"XID\",\n ksuid: \"KSUID\",\n datetime: \"ISO datetime\",\n date: \"ISO date\",\n time: \"ISO time\",\n duration: \"ISO duration\",\n ipv4: \"IPv4 address\",\n ipv6: \"IPv6 address\",\n mac: \"MAC address\",\n cidrv4: \"IPv4 range\",\n cidrv6: \"IPv6 range\",\n base64: \"base64-encoded string\",\n base64url: \"base64url-encoded string\",\n json_string: \"JSON string\",\n e164: \"E.164 number\",\n currency_code: \"currency code\",\n credit_card: \"credit card number\",\n iban: \"IBAN\",\n jwt: \"JWT\",\n template_literal: \"input\"\n };\n const TypeDictionary = { nan: \"NaN\" };\n function getTypeName(type, input) {\n if (type === \"number\" && typeof input === \"number\" && !Number.isFinite(input)) return String(input);\n return TypeDictionary[type] ?? type;\n }\n return (issue) => {\n switch (issue.code) {\n case \"invalid_type\": return `Invalid input: expected ${getTypeName(issue.expected)}, received ${getTypeName(parsedType(issue.input), issue.input)}`;\n case \"invalid_value\":\n if (issue.values.length === 1) return `Invalid input: expected ${stringifyPrimitive(issue.values[0])}`;\n return `Invalid option: expected one of ${joinValues(issue.values, \"|\")}`;\n case \"too_big\": {\n const adj = issue.exact ? \"exactly \" : issue.inclusive ? \"<=\" : \"<\";\n const sizing = getSizing(issue.origin);\n if (sizing) return `Too big: expected ${issue.origin ?? \"value\"} to have ${adj}${issue.maximum.toString()} ${sizing.unit ?? \"elements\"}`;\n return `Too big: expected ${issue.origin ?? \"value\"} to be ${adj}${issue.maximum.toString()}`;\n }\n case \"too_small\": {\n const adj = issue.exact ? \"exactly \" : issue.inclusive ? \">=\" : \">\";\n const sizing = getSizing(issue.origin);\n if (sizing) return `Too small: expected ${issue.origin} to have ${adj}${issue.minimum.toString()} ${sizing.unit}`;\n return `Too small: expected ${issue.origin} to be ${adj}${issue.minimum.toString()}`;\n }\n case \"invalid_format\": {\n const _issue = issue;\n if (_issue.format === \"starts_with\") return `Invalid string: must start with \"${_issue.prefix}\"`;\n if (_issue.format === \"ends_with\") return `Invalid string: must end with \"${_issue.suffix}\"`;\n if (_issue.format === \"includes\") return `Invalid string: must include \"${_issue.includes}\"`;\n if (_issue.format === \"regex\") return `Invalid string: must match pattern ${_issue.pattern}`;\n return `Invalid ${FormatDictionary[_issue.format] ?? issue.format}`;\n }\n case \"not_multiple_of\": return `Invalid number: must be a multiple of ${issue.divisor}`;\n case \"unrecognized_keys\": return `Unrecognized key${issue.keys.length > 1 ? \"s\" : \"\"}: ${joinValues(issue.keys, \", \")}`;\n case \"invalid_key\": return `Invalid key in ${issue.origin}`;\n case \"invalid_union\":\n if (issue.options && Array.isArray(issue.options) && issue.options.length > 0) return `Invalid discriminator value. Expected ${issue.options.map((o) => `'${o}'`).join(\" | \")}`;\n if (issue.inclusive === false) return \"Invalid input: more than one option matched\";\n return \"Invalid input\";\n case \"invalid_element\": return `Invalid value in ${issue.origin}`;\n default: return `Invalid input`;\n }\n };\n};\nfunction en_default() {\n return { localeError: error() };\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/api.js\nfunction snapshotChecks(def) {\n if (def.checks) def.checks = [...def.checks];\n return def;\n}\n// @__NO_SIDE_EFFECTS__\nfunction _string(Class, params) {\n return new Class(snapshotChecks({\n type: \"string\",\n ...normalizeParams(params)\n }));\n}\n// @__NO_SIDE_EFFECTS__\nfunction _unknown(Class) {\n return new Class({ type: \"unknown\" });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _minLength(minimum, params) {\n return new $ZodCheckMinLength({\n check: \"min_length\",\n ...normalizeParams(params),\n minimum\n });\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/mini/schemas.js\nconst ZodMiniType = /*@__PURE__*/ $constructor(\"ZodMiniType\", (inst, def) => {\n if (!inst._zod) throw new Error(\"Uninitialized schema in ZodMiniType.\");\n $ZodType.init(inst, def);\n inst.def = def;\n inst.type = def.type;\n}, {\n get with() {\n return this.check;\n },\n set with(value) {\n own(this, \"with\", value);\n },\n parse(data, params) {\n return parse(this, data, params, { callee: this.parse });\n },\n parseAsync(data, params) {\n return parseAsync(this, data, params, { callee: this.parseAsync });\n },\n safeParse(data, params) {\n return safeParse(this, data, params);\n },\n safeParseAsync(data, params) {\n return safeParseAsync(this, data, params);\n },\n check(...checks) {\n const def = this.def;\n return this.clone({\n ...def,\n checks: [...def.checks ?? [], ...checks.map((ch) => typeof ch === \"function\" ? { _zod: {\n check: ch,\n def: { check: \"custom\" },\n onattach: []\n } } : ch)]\n }, { parent: true });\n },\n clone(_def, params) {\n return clone(this, _def, params);\n },\n brand() {\n return this;\n },\n register(reg, meta) {\n reg.add(this, meta);\n return this;\n },\n apply(fn, ...args) {\n return args.length === 0 ? fn(this) : fn(this, ...args);\n }\n});\nconst ZodMiniString = /*@__PURE__*/ $constructor(\"ZodMiniString\", (inst, def) => {\n $ZodString.init(inst, def);\n ZodMiniType.init(inst, def);\n});\n// @__NO_SIDE_EFFECTS__\nfunction string(params) {\n return _string(ZodMiniString, params);\n}\nconst ZodMiniUnknown = /*@__PURE__*/ $constructor(\"ZodMiniUnknown\", (inst, def) => {\n $ZodUnknown.init(inst, def);\n ZodMiniType.init(inst, def);\n});\n// @__NO_SIDE_EFFECTS__\nfunction unknown() {\n return _unknown(ZodMiniUnknown);\n}\nconst ZodMiniArray = /*@__PURE__*/ $constructor(\"ZodMiniArray\", (inst, def) => {\n $ZodArray.init(inst, def);\n ZodMiniType.init(inst, def);\n});\n// @__NO_SIDE_EFFECTS__\nfunction array(element, params) {\n return new ZodMiniArray({\n type: \"array\",\n element,\n ...normalizeParams(params)\n });\n}\nconst ZodMiniObject = /*@__PURE__*/ $constructor(\"ZodMiniObject\", (inst, def) => {\n $ZodObject.init(inst, def);\n ZodMiniType.init(inst, def);\n installLazyProp(inst, \"shape\", (self) => self._zod.def.shape, false);\n});\n// @__NO_SIDE_EFFECTS__\nfunction object(shape, params) {\n const def = {\n type: \"object\",\n shape: shape ?? {},\n ...normalizeParams(params)\n };\n return new ZodMiniObject(def);\n}\nconst ZodMiniRecord = /*@__PURE__*/ $constructor(\"ZodMiniRecord\", (inst, def) => {\n $ZodRecord.init(inst, def);\n ZodMiniType.init(inst, def);\n});\n// @__NO_SIDE_EFFECTS__\nfunction record(keyType, valueType, params) {\n if (!valueType || !valueType._zod) return new ZodMiniRecord({\n type: \"record\",\n keyType: /* @__PURE__ */ string(),\n valueType: keyType,\n ...normalizeParams(valueType)\n });\n return new ZodMiniRecord({\n type: \"record\",\n keyType,\n valueType,\n ...normalizeParams(params)\n });\n}\nconst ZodMiniOptional = /*@__PURE__*/ $constructor(\"ZodMiniOptional\", (inst, def) => {\n $ZodOptional.init(inst, def);\n ZodMiniType.init(inst, def);\n});\n// @__NO_SIDE_EFFECTS__\nfunction optional(innerType) {\n return new ZodMiniOptional({\n type: \"optional\",\n innerType\n });\n}\nconst ZodMiniDefault = /*@__PURE__*/ $constructor(\"ZodMiniDefault\", (inst, def) => {\n $ZodDefault.init(inst, def);\n ZodMiniType.init(inst, def);\n});\n// @__NO_SIDE_EFFECTS__\nfunction _default(innerType, defaultValue) {\n return new ZodMiniDefault({\n type: \"default\",\n innerType,\n get defaultValue() {\n return typeof defaultValue === \"function\" ? defaultValue() : shallowClone(defaultValue);\n }\n });\n}\n\n//#endregion\n//#region src/lib/hook-io.ts\nconfig(en_default());\nconst HookInputSchema = object({\n session_id: string(),\n transcript_path: string(),\n cwd: string(),\n permission_mode: string(),\n hook_event_name: string(),\n tool_name: string(),\n tool_input: record(string(), unknown())\n});\nasync function readHookInput() {\n const chunks = [];\n for await (const chunk of process.stdin) chunks.push(typeof chunk === \"string\" ? chunk : String(chunk));\n let parsed;\n try {\n parsed = JSON.parse(chunks.join(\"\"));\n } catch (_error) {\n return {\n ok: false,\n raw: chunks.join(\"\").slice(0, 200),\n error: \"invalid JSON\"\n };\n }\n const result = HookInputSchema.safeParse(parsed);\n if (!result.success) return {\n ok: false,\n raw: parsed,\n error: result.error.message\n };\n return {\n ok: true,\n data: result.data\n };\n}\nconst BashInputSchema = object({\n command: string(),\n description: optional(string())\n});\nconst FilePathInputSchema = object({ file_path: string() });\nconst SearchInputSchema = object({\n pattern: string(),\n path: optional(string())\n});\nfunction parseBashInput(toolInput) {\n const result = BashInputSchema.safeParse(toolInput);\n return result.success ? result.data : null;\n}\nfunction parseFilePathInput(toolInput) {\n const result = FilePathInputSchema.safeParse(toolInput);\n return result.success ? result.data : null;\n}\nfunction parseSearchInput(toolInput) {\n const result = SearchInputSchema.safeParse(toolInput);\n return result.success ? result.data : null;\n}\nfunction writeDeny(reason) {\n const decision = { hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"deny\",\n ...reason ? { permissionDecisionReason: reason } : {}\n } };\n process.stdout.write(JSON.stringify(decision));\n}\nfunction writeAdvice(advice) {\n const decision = { hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n additionalContext: advice\n } };\n process.stdout.write(JSON.stringify(decision));\n}\n\n//#endregion\n//#region src/lib/audit.ts\nconst MAX_LOG_SIZE = 1048576;\nfunction getLogPath() {\n const hookDir = path.dirname(fileURLToPath(import.meta.url));\n return path.join(hookDir, \"bst-hook.audit.local.jsonl\");\n}\nfunction rotateIfNeeded(logPath) {\n try {\n if (fs.statSync(logPath).size > MAX_LOG_SIZE) {\n const rotatedPath = `${logPath}.1`;\n fs.renameSync(logPath, rotatedPath);\n }\n } catch (_error) {}\n}\nlet rotationChecked = false;\nfunction audit(entry) {\n try {\n const logPath = getLogPath();\n if (!rotationChecked) {\n rotateIfNeeded(logPath);\n rotationChecked = true;\n }\n const record = {\n timestamp: (/* @__PURE__ */ new Date()).toISOString(),\n ...entry\n };\n fs.appendFileSync(logPath, JSON.stringify(record) + \"\\n\");\n } catch (err) {\n process.stderr.write(`[bst-hook] audit write failed: ${err instanceof Error ? err.message : String(err)}\\n`);\n }\n}\n\n//#endregion\n//#region src/lib/shell-tokenizer.ts\nfunction scanDoubleQuoted(input, start) {\n let content = \"\";\n let i = start;\n while (i < input.length && input[i] !== \"\\\"\") {\n if (input[i] === \"\\\\\" && i + 1 < input.length) {\n const next = input[i + 1];\n if (next === \"\\\"\" || next === \"\\\\\" || next === \"$\" || next === \"`\") {\n content += next;\n i += 2;\n continue;\n }\n }\n content += input[i];\n i++;\n }\n if (i >= input.length) return null;\n return {\n content,\n next: i + 1\n };\n}\nfunction tokenize(input) {\n const tokens = [];\n let current = \"\";\n let i = 0;\n let inToken = false;\n while (i < input.length) {\n const ch = input[i];\n if (ch === \"'\") {\n inToken = true;\n i++;\n while (i < input.length && input[i] !== \"'\") {\n current += input[i];\n i++;\n }\n if (i >= input.length) return {\n ok: false,\n error: \"unterminated single quote\"\n };\n i++;\n } else if (ch === \"\\\"\") {\n inToken = true;\n const scanned = scanDoubleQuoted(input, i + 1);\n if (!scanned) return {\n ok: false,\n error: \"unterminated double quote\"\n };\n current += scanned.content;\n i = scanned.next;\n } else if (ch === \"\\\\\" && i + 1 < input.length) {\n inToken = true;\n current += input[i + 1];\n i += 2;\n } else if (ch === \" \" || ch === \" \") {\n if (inToken) {\n tokens.push(current);\n current = \"\";\n inToken = false;\n }\n i++;\n } else {\n inToken = true;\n current += ch;\n i++;\n }\n }\n if (inToken) tokens.push(current);\n return {\n ok: true,\n tokens\n };\n}\nconst SAFE_UNQUOTED_RE = /^[A-Za-z0-9_\\-./=:@%+,]+$/;\nfunction quoteToken(token) {\n if (token.length > 0 && SAFE_UNQUOTED_RE.test(token)) return token;\n return \"'\" + token.replace(/'/g, \"'\\\\''\") + \"'\";\n}\nfunction joinTokens(tokens) {\n return tokens.map(quoteToken).join(\" \");\n}\n\n//#endregion\n//#region src/lib/shell-splitter.ts\nfunction splitCommand(input) {\n if (!input || !input.trim()) return [];\n const segments = [];\n let current = \"\";\n let i = 0;\n let inSingleQuote = false;\n let inDoubleQuote = false;\n while (i < input.length) {\n const ch = input[i];\n if (ch === \"'\" && !inDoubleQuote) {\n inSingleQuote = !inSingleQuote;\n current += ch;\n i++;\n continue;\n }\n if (ch === \"\\\"\" && !inSingleQuote) {\n inDoubleQuote = !inDoubleQuote;\n current += ch;\n i++;\n continue;\n }\n if (ch === \"\\\\\" && !inSingleQuote && i + 1 < input.length) {\n current += ch + input[i + 1];\n i += 2;\n continue;\n }\n if (!inSingleQuote && !inDoubleQuote) {\n if (ch === \"&\" && i + 1 < input.length && input[i + 1] === \"&\") {\n pushSegment(segments, current, \"&&\");\n current = \"\";\n i += 2;\n continue;\n }\n if (ch === \"&\" && !(i + 1 < input.length && input[i + 1] === \">\") && !(i > 0 && (input[i - 1] === \">\" || input[i - 1] === \"<\"))) {\n pushSegment(segments, current, \"&\");\n current = \"\";\n i++;\n continue;\n }\n if (ch === \"|\" && i + 1 < input.length && input[i + 1] === \"|\") {\n pushSegment(segments, current, \"||\");\n current = \"\";\n i += 2;\n continue;\n }\n if (ch === \"|\") {\n pushSegment(segments, current, \"|\");\n current = \"\";\n i++;\n continue;\n }\n if (ch === \";\") {\n pushSegment(segments, current, \";\");\n current = \"\";\n i++;\n continue;\n }\n }\n current += ch;\n i++;\n }\n const trimmed = current.trim();\n if (trimmed) segments.push({\n command: trimmed,\n operator: null\n });\n return segments;\n}\nfunction pushSegment(segments, text, operator) {\n const trimmed = text.trim();\n if (trimmed) segments.push({\n command: trimmed,\n operator\n });\n}\n\n//#endregion\n//#region src/lib/injection-guard.ts\nfunction hasBraceExpansion(token) {\n let depth = 0;\n for (let i = 0; i < token.length; i++) {\n const ch = token[i];\n if (ch === \"{\") depth++;\n else if (ch === \"}\") {\n if (depth > 0) depth--;\n } else if (depth >= 1 && (ch === \",\" || ch === \".\" && token[i + 1] === \".\")) return true;\n }\n return false;\n}\nfunction checkInjection(tokens, rawSegment) {\n if (/[\\n\\r]/.test(rawSegment)) return {\n safe: false,\n reason: \"unquoted newline in command\"\n };\n for (const token of tokens) {\n if (token === \"`\") return {\n safe: false,\n reason: \"backtick command substitution\"\n };\n if (token.includes(\"`\")) return {\n safe: false,\n reason: \"backtick command substitution\"\n };\n if (token.includes(\"$\")) return {\n safe: false,\n reason: \"unresolved dollar-expansion\"\n };\n if (hasBraceExpansion(token)) return {\n safe: false,\n reason: \"brace expansion\"\n };\n if (token.includes(\"<(\")) return {\n safe: false,\n reason: \"process substitution\"\n };\n if (token.includes(\">(\")) return {\n safe: false,\n reason: \"process substitution\"\n };\n }\n return { safe: true };\n}\n\n//#endregion\n//#region src/lib/env-file.ts\nconst ENV_SAFE_SUFFIXES = [\n \".sample\",\n \".example\",\n \".template\"\n];\nconst ENV_TOOL_OWNED_SUFFIXES = [\".check.env\"];\nconst ENV_FILE_PATTERN = /(^|[/\\\\])(\\.env(\\..*)?|.*\\.env(\\..*)?|\\.env)$/;\nfunction isEnvSafeSuffix(filename) {\n return [...ENV_SAFE_SUFFIXES, ...ENV_TOOL_OWNED_SUFFIXES].some((suffix) => filename.endsWith(suffix));\n}\nfunction isEnvFile(filePath) {\n if (isEnvSafeSuffix(path.basename(filePath))) return false;\n return ENV_FILE_PATTERN.test(filePath);\n}\n\n//#endregion\n//#region src/lib/git-state.ts\nconst GIT_TIMEOUT_MS = 5e3;\nfunction getCurrentBranch(cwd) {\n try {\n const out = execFileSync(\"git\", [\n \"rev-parse\",\n \"--abbrev-ref\",\n \"HEAD\"\n ], {\n cwd,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ],\n timeout: GIT_TIMEOUT_MS\n }).toString().trim();\n if (!out || out === \"HEAD\") return null;\n return out;\n } catch {\n return null;\n }\n}\nfunction getRepoTopLevel(cwd) {\n try {\n const out = execFileSync(\"git\", [\"rev-parse\", \"--show-toplevel\"], {\n cwd,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ],\n timeout: GIT_TIMEOUT_MS\n }).toString().trim();\n return out === \"\" ? null : out;\n } catch {\n return null;\n }\n}\nfunction getRemoteDefaultBranch(cwd) {\n try {\n const out = execFileSync(\"git\", [\n \"symbolic-ref\",\n \"--short\",\n \"refs/remotes/origin/HEAD\"\n ], {\n cwd,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ],\n timeout: GIT_TIMEOUT_MS\n }).toString().trim();\n if (!out) return null;\n return out.startsWith(\"origin/\") ? out.slice(7) : out;\n } catch {\n return null;\n }\n}\nconst DefaultBranchSchema = object({ defaultBranch: optional(string().check(_minLength(1))) });\nfunction getConfiguredDefaultBranch(topLevel) {\n let parsed;\n try {\n parsed = JSON.parse(readFileSync(join(topLevel, \".tooling.json\"), \"utf-8\"));\n } catch {\n return null;\n }\n const result = DefaultBranchSchema.safeParse(parsed);\n return result.success ? result.data.defaultBranch ?? null : null;\n}\nconst ALWAYS_PROTECTED = [\"main\", \"master\"];\nfunction getProtectedBranches(cwd, extra) {\n const set = new Set(ALWAYS_PROTECTED);\n const remote = getRemoteDefaultBranch(cwd);\n if (remote) set.add(remote);\n for (const b of extra) set.add(b);\n return set;\n}\nfunction getUpstream(cwd, branch) {\n try {\n return execFileSync(\"git\", [\n \"rev-parse\",\n \"--abbrev-ref\",\n `${branch}@{upstream}`\n ], {\n cwd,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ],\n timeout: GIT_TIMEOUT_MS\n }).toString().trim() || null;\n } catch {\n return null;\n }\n}\nfunction resolveCommit(cwd, rev) {\n try {\n return execFileSync(\"git\", [\n \"rev-parse\",\n \"--verify\",\n \"--quiet\",\n `${rev}^{commit}`\n ], {\n cwd,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ],\n timeout: GIT_TIMEOUT_MS\n }).toString().trim() || null;\n } catch {\n return null;\n }\n}\nfunction branchCarriesOtherFiles(cwd, allowed) {\n const toplevel = getRepoTopLevel(cwd);\n if (toplevel === null) return null;\n try {\n const defaultBranch = getConfiguredDefaultBranch(toplevel) ?? getRemoteDefaultBranch(toplevel) ?? \"main\";\n const mergeBase = execFileSync(\"git\", [\n \"merge-base\",\n \"HEAD\",\n `origin/${defaultBranch}`\n ], {\n cwd: toplevel,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ],\n timeout: GIT_TIMEOUT_MS\n }).toString().trim();\n if (!mergeBase) return null;\n const names = (args) => execFileSync(\"git\", args, {\n cwd: toplevel,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ],\n timeout: GIT_TIMEOUT_MS\n }).toString().split(\"\\n\").filter((line) => line.length > 0);\n return [...names([\n \"diff\",\n \"--name-only\",\n mergeBase\n ]), ...names([\n \"ls-files\",\n \"--others\",\n \"--exclude-standard\"\n ])].some((name) => !allowed.includes(name));\n } catch {\n return null;\n }\n}\nfunction isPrimaryWorktree(cwd) {\n try {\n const gitDir = execFileSync(\"git\", [\"rev-parse\", \"--absolute-git-dir\"], {\n cwd,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ],\n timeout: GIT_TIMEOUT_MS\n }).toString().trim();\n const commonRaw = execFileSync(\"git\", [\"rev-parse\", \"--git-common-dir\"], {\n cwd,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ],\n timeout: GIT_TIMEOUT_MS\n }).toString().trim();\n if (!gitDir || !commonRaw) return false;\n const commonDir = resolve(cwd, commonRaw);\n return realpathSync(gitDir) === realpathSync(commonDir);\n } catch {\n return false;\n }\n}\n\n//#endregion\n//#region src/lib/git-short-options.ts\nconst VALUE_TAKING_LETTERS = {\n switch: /* @__PURE__ */ new Set([\"c\", \"C\"]),\n checkout: /* @__PURE__ */ new Set([\"b\", \"B\"]),\n branch: /* @__PURE__ */ new Set([\"u\"]),\n fetch: /* @__PURE__ */ new Set([\"o\", \"j\"])\n};\nfunction splitShortOptionCluster(token, sub) {\n if (!token.startsWith(\"-\") || token.startsWith(\"--\") || token.length <= 1) return;\n const valueLetters = VALUE_TAKING_LETTERS[sub];\n const letters = [];\n const chars = token.slice(1);\n for (let i = 0; i < chars.length; i++) {\n const ch = chars[i];\n letters.push(ch);\n if (valueLetters.has(ch)) return {\n letters,\n attachedValue: chars.slice(i + 1)\n };\n }\n return { letters };\n}\n\n//#endregion\n//#region src/lib/path-scope.ts\nfunction pathFlavorFor(base) {\n return /^[a-zA-Z]:[\\\\/]/.test(base) ? path.win32 : path.posix;\n}\nfunction resolveIn(base, target) {\n return pathFlavorFor(base).resolve(base, target);\n}\nfunction isWithin(dir, target) {\n const pp = pathFlavorFor(dir);\n const rel = pp.relative(dir, target);\n if (rel === \"\") return true;\n if (rel === \"..\" || rel.startsWith(\"..\" + pp.sep)) return false;\n return !pp.isAbsolute(rel);\n}\nfunction samePath(a, b) {\n return pathFlavorFor(a).relative(a, b) === \"\";\n}\nfunction targetsGitDir(filePath, cwd) {\n const pp = pathFlavorFor(cwd);\n const resolved = resolveIn(cwd, filePath);\n if (!isWithin(cwd, resolved)) return false;\n const rel = pp.relative(cwd, resolved);\n if (rel === \"\") return false;\n return rel.split(pp.sep).includes(\".git\");\n}\nfunction targetsNodeModules(filePath, cwd) {\n return resolveIn(cwd, filePath).split(/[/\\\\]/).includes(\"node_modules\");\n}\n\n//#endregion\n//#region src/rules.ts\nconst GIT_FLAGS_WITH_ARG = /* @__PURE__ */ new Set([\n \"-C\",\n \"-c\",\n \"--git-dir\",\n \"--work-tree\"\n]);\nfunction getGitSubcommand(tokens) {\n const args = tokens.slice(1);\n for (let i = 0; i < args.length; i++) {\n const t = args[i];\n if (GIT_FLAGS_WITH_ARG.has(t)) {\n i++;\n continue;\n }\n if (t.startsWith(\"-\")) continue;\n return t;\n }\n}\nconst WORKTREE_ADVICE = \"You're creating a feature branch in the primary worktree. An isolated worktree keeps this work independent of other windows, and with no commits on this branch yet, converting is cheap -- `git worktree add ../<dir> -b <branch>` (a sibling outside the repo, with its own dependency install). Claude Code keeps this session inside its own working tree, so the work continues in a new session opened in the sibling worktree. If you cannot open a session there, stay in the primary checkout on this branch and check `git branch --show-current` before each commit.\";\nfunction isGitBranchCreation(tokens) {\n const sub = getGitSubcommand(tokens);\n if (sub !== \"checkout\" && sub !== \"switch\") return false;\n const after = tokens.slice(tokens.indexOf(sub) + 1);\n const creationLetters = sub === \"checkout\" ? [\"b\", \"B\"] : [\"c\", \"C\"];\n return after.some((t) => {\n const cluster = splitShortOptionCluster(t, sub);\n return cluster !== void 0 && cluster.letters.some((l) => creationLetters.includes(l));\n });\n}\nfunction worktreeAdvice(tokens, ctx) {\n return isGitBranchCreation(tokens) && isPrimaryWorktree(ctx.cwd) ? WORKTREE_ADVICE : void 0;\n}\nconst GIT_FORCE_CREATE_LETTER = {\n switch: \"C\",\n checkout: \"B\"\n};\nconst GIT_FORCE_CREATE_LONG = {\n flag: \"--force-create\",\n shortest: \"--force-\"\n};\nfunction gitForceCreateTargets(after, letterSub) {\n const letter = GIT_FORCE_CREATE_LETTER[letterSub];\n const longFlag = GIT_FORCE_CREATE_LONG;\n const targets = [];\n for (let i = 0; i < after.length; i++) {\n const t = after[i];\n if (t === void 0) continue;\n if (letterSub === \"switch\") {\n const [flag, ...rest] = t.split(\"=\");\n if (flag !== void 0 && flag.length >= longFlag.shortest.length && longFlag.flag.startsWith(flag)) {\n const target = rest.length > 0 ? rest.join(\"=\") : after[i + 1];\n if (target !== void 0) targets.push(target);\n continue;\n }\n }\n const cluster = splitShortOptionCluster(t, letterSub);\n if (cluster === void 0 || !cluster.letters.includes(letter)) continue;\n if (cluster.attachedValue !== void 0 && cluster.attachedValue !== \"\") {\n targets.push(cluster.attachedValue);\n continue;\n }\n const next = after[i + 1];\n if (next !== void 0) targets.push(next);\n }\n return targets;\n}\nfunction gitCommandForceCreateTargets(tokens) {\n const sub = getGitSubcommand(tokens);\n if (sub === \"switch\" || sub === \"checkout\") return gitForceCreateTargets(tokens.slice(tokens.indexOf(sub) + 1), sub);\n if (sub === \"worktree\") {\n const worktreeIdx = tokens.indexOf(\"worktree\");\n if (tokens[worktreeIdx + 1] !== \"add\") return [];\n return gitForceCreateTargets(tokens.slice(worktreeIdx + 2), \"checkout\");\n }\n return [];\n}\nfunction isGitProtectedBranchForceCreate(tokens, ctx) {\n const targets = gitCommandForceCreateTargets(tokens);\n if (targets.length === 0) return false;\n const protectedSet = getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []);\n return targets.some((t) => protectedSet.has(t));\n}\nfunction isGitProtectedBranchRefWrite(tokens, ctx) {\n const sub = getGitSubcommand(tokens);\n if (sub !== \"branch\") return false;\n const { flags, positionals } = splitGitBranchArgs(tokens.slice(tokens.indexOf(sub) + 1));\n if (positionals.length === 0) return false;\n let deleteMode = false;\n let renameMode = false;\n let copyMode = false;\n let forceMode = false;\n for (const flag of flags) {\n if (flag.letters !== void 0) {\n if (flag.letters.includes(\"d\") || flag.letters.includes(\"D\")) deleteMode = true;\n if (flag.letters.includes(\"m\") || flag.letters.includes(\"M\")) renameMode = true;\n if (flag.letters.includes(\"c\") || flag.letters.includes(\"C\")) copyMode = true;\n if (flag.letters.includes(\"f\") || flag.letters.includes(\"M\") || flag.letters.includes(\"C\")) forceMode = true;\n continue;\n }\n if (flag.name === \"delete\") deleteMode = true;\n if (flag.name === \"move\") renameMode = true;\n if (flag.name === \"copy\") copyMode = true;\n if (flag.name === \"force\") forceMode = true;\n }\n const protectedSet = getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []);\n if (deleteMode && positionals.some((p) => protectedSet.has(p))) return true;\n if (renameMode && positionals.length >= 2 && protectedSet.has(positionals[0])) return true;\n if (forceMode) {\n const destination = renameMode || copyMode ? positionals[positionals.length - 1] : positionals[0];\n if (destination !== void 0 && protectedSet.has(destination)) return true;\n }\n return false;\n}\nfunction refspecDestination(refspec) {\n const spec = refspec.startsWith(\"+\") ? refspec.slice(1) : refspec;\n const colon = spec.lastIndexOf(\":\");\n return (colon === -1 ? spec : spec.slice(colon + 1)).replace(/^refs\\/heads\\//, \"\");\n}\nconst GIT_BRANCH_VALUE_FLAG_NAMES = /* @__PURE__ */ new Set([\n \"contains\",\n \"no-contains\",\n \"merged\",\n \"no-merged\",\n \"points-at\",\n \"sort\",\n \"format\",\n \"set-upstream-to\"\n]);\nfunction splitGitBranchArgs(after) {\n const flags = [];\n const positionals = [];\n for (let i = 0; i < after.length; i++) {\n const t = after[i];\n if (t === void 0) continue;\n if (!t.startsWith(\"-\")) {\n positionals.push(t);\n continue;\n }\n if (t.startsWith(\"--\") && t !== \"--\") {\n const eq = t.indexOf(\"=\");\n const name = eq > 0 ? t.slice(2, eq) : t.slice(2);\n flags.push({\n raw: t,\n name\n });\n if (eq < 0 && GIT_BRANCH_VALUE_FLAG_NAMES.has(name)) i++;\n continue;\n }\n const cluster = splitShortOptionCluster(t, \"branch\");\n if (cluster === void 0) {\n flags.push({ raw: t });\n continue;\n }\n flags.push({\n raw: t,\n letters: cluster.letters\n });\n if (cluster.attachedValue === \"\") i++;\n }\n return {\n flags,\n positionals\n };\n}\nconst GIT_FETCH_VALUE_FLAG_NAMES = /* @__PURE__ */ new Set([\n \"depth\",\n \"deepen\",\n \"shallow-since\",\n \"shallow-exclude\",\n \"negotiation-tip\",\n \"filter\",\n \"jobs\",\n \"server-option\",\n \"upload-pack\",\n \"refmap\",\n \"submodule-prefix\",\n \"recurse-submodules-default\"\n]);\nfunction splitGitFetchArgs(after) {\n const flags = [];\n const positionals = [];\n const refmaps = [];\n for (let i = 0; i < after.length; i++) {\n const t = after[i];\n if (t === void 0) continue;\n if (!t.startsWith(\"-\")) {\n positionals.push(t);\n continue;\n }\n if (t === \"--\") {\n flags.push({ raw: t });\n continue;\n }\n if (t.startsWith(\"--\")) {\n const eq = t.indexOf(\"=\");\n const name = eq > 0 ? t.slice(2, eq) : t.slice(2);\n flags.push({\n raw: t,\n name\n });\n if (name === \"refmap\") {\n if (eq > 0) refmaps.push(t.slice(eq + 1));\n else if (after[i + 1] !== void 0) refmaps.push(after[i + 1]);\n }\n if (eq < 0 && GIT_FETCH_VALUE_FLAG_NAMES.has(name)) i++;\n continue;\n }\n const cluster = splitShortOptionCluster(t, \"fetch\");\n if (cluster === void 0) {\n flags.push({ raw: t });\n continue;\n }\n flags.push({\n raw: t,\n letters: cluster.letters\n });\n if (cluster.attachedValue === \"\") i++;\n }\n return {\n flags,\n positionals,\n refmaps\n };\n}\nfunction fetchRefspecTarget(spec) {\n const body = spec.startsWith(\"+\") ? spec.slice(1) : spec;\n if (body.startsWith(\"^\")) return { kind: \"none\" };\n if (!body.includes(\":\")) return { kind: \"none\" };\n const destination = refspecDestination(body);\n if (destination === \"\") return { kind: \"none\" };\n if (destination.startsWith(\"refs/remotes/\")) return { kind: \"tracking\" };\n if (destination.startsWith(\"refs/\")) return { kind: \"other\" };\n return {\n kind: \"branch\",\n name: destination\n };\n}\nfunction globToAnchoredRegex(glob) {\n const escaped = glob.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n return new RegExp(`^${escaped.replace(/\\\\\\*/g, \".*\")}$`);\n}\nfunction isProtectedFetchBranch(name, protectedSet) {\n if (protectedSet.has(name)) return true;\n if (!name.includes(\"*\")) return false;\n const pattern = globToAnchoredRegex(name);\n for (const member of protectedSet) if (pattern.test(member)) return true;\n return false;\n}\nfunction isGitProtectedBranchFetchWrite(tokens, ctx) {\n const sub = getGitSubcommand(tokens);\n if (sub !== \"fetch\") return false;\n const { positionals, refmaps } = splitGitFetchArgs(tokens.slice(tokens.indexOf(sub) + 1));\n const protectedSet = getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []);\n return [...positionals, ...refmaps].some((spec) => {\n const target = fetchRefspecTarget(spec);\n if (target.kind !== \"branch\" || target.name === void 0) return false;\n return isProtectedFetchBranch(target.name, protectedSet);\n });\n}\nfunction isGitProtectedUpdateRef(tokens, ctx) {\n const after = tokens.slice(tokens.indexOf(\"update-ref\") + 1);\n let ref;\n for (let i = 0; i < after.length; i++) {\n const t = after[i];\n if (t === \"-m\") {\n i++;\n continue;\n }\n if (t.startsWith(\"-\")) continue;\n ref = t;\n break;\n }\n if (ref === void 0) return false;\n return getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []).has(ref.replace(/^refs\\/heads\\//, \"\"));\n}\nfunction gitEffectiveCwd(tokens, cwd) {\n let dir = cwd;\n const args = tokens.slice(1);\n for (let i = 0; i < args.length; i++) {\n const t = args[i];\n if (t === \"-C\") {\n const next = args[i + 1];\n if (next !== void 0) dir = path.resolve(dir, next);\n i++;\n continue;\n }\n if (GIT_FLAGS_WITH_ARG.has(t)) {\n i++;\n continue;\n }\n if (!t.startsWith(\"-\")) break;\n }\n return dir;\n}\nconst GIT_SWITCH_NEUTRAL_FLAGS = /* @__PURE__ */ new Set([\n \"-q\",\n \"--quiet\",\n \"-f\",\n \"--force\",\n \"--discard-changes\",\n \"-m\",\n \"--merge\",\n \"--no-track\",\n \"--progress\",\n \"--no-progress\",\n \"--guess\",\n \"--no-guess\",\n \"--ignore-other-worktrees\",\n \"--recurse-submodules\",\n \"--no-recurse-submodules\"\n]);\nconst GIT_SWITCH_CREATE_FLAGS = /* @__PURE__ */ new Set([\n \"--create\",\n \"--force-create\",\n \"--orphan\"\n]);\nconst GIT_CHECKOUT_PATH_FLAGS = /* @__PURE__ */ new Set([\n \"--ours\",\n \"--theirs\",\n \"-p\",\n \"--patch\",\n \"--conflict\",\n \"--pathspec-from-file\",\n \"--pathspec-file-nul\",\n \"--overlay\",\n \"--no-overlay\"\n]);\nconst GIT_SWITCH_NEUTRAL_LETTERS = /* @__PURE__ */ new Set([\n \"q\",\n \"f\",\n \"m\"\n]);\nfunction switchTarget(after, sub, isCheckoutBranch) {\n if (sub === \"checkout\" && after.includes(\"--\")) return void 0;\n const createLetters = sub === \"checkout\" ? [\"b\", \"B\"] : [\"c\", \"C\"];\n const known = (l) => GIT_SWITCH_NEUTRAL_LETTERS.has(l) || createLetters.includes(l);\n const positionals = [];\n let created;\n for (let i = 0; i < after.length; i++) {\n const t = after[i];\n if (!t.startsWith(\"-\")) {\n positionals.push(t);\n continue;\n }\n if (GIT_SWITCH_NEUTRAL_FLAGS.has(t)) continue;\n const eq = t.indexOf(\"=\");\n if (sub === \"checkout\" && GIT_CHECKOUT_PATH_FLAGS.has(eq === -1 ? t : t.slice(0, eq))) return;\n if (GIT_SWITCH_CREATE_FLAGS.has(eq === -1 ? t : t.slice(0, eq))) {\n created = eq === -1 ? after[++i] : t.slice(eq + 1);\n continue;\n }\n const cluster = splitShortOptionCluster(t, sub);\n if (cluster === void 0 || !cluster.letters.every(known)) return null;\n if (cluster.letters.some((l) => createLetters.includes(l))) created = cluster.attachedValue ? cluster.attachedValue : after[++i];\n }\n if (created !== void 0) return created;\n if (positionals.length !== 1) return void 0;\n const target = positionals[0];\n if (target === \"HEAD\" || target === \"@\") return void 0;\n if (sub === \"checkout\" && !isCheckoutBranch(target)) return void 0;\n return target;\n}\nfunction branchSwitch(tokens, ctx, resolved) {\n const sub = getGitSubcommand(tokens);\n if (sub !== \"switch\" && sub !== \"checkout\") return void 0;\n const dir = gitEffectiveCwd(tokens, ctx.cwd);\n const isCheckoutBranch = (name) => !resolved || getProtectedBranches(dir, ctx.repoProtectedBranches ?? []).has(name) || resolveCommit(dir, name) !== null;\n const target = switchTarget(tokens.slice(tokens.indexOf(sub) + 1), sub, isCheckoutBranch);\n if (!resolved) return target === void 0 ? void 0 : {\n dir,\n branch: null\n };\n return target === void 0 ? void 0 : {\n dir,\n branch: target\n };\n}\nconst GIT_CHECKED_OUT_WRITES = /* @__PURE__ */ new Set([\n \"commit\",\n \"merge\",\n \"rebase\",\n \"cherry-pick\",\n \"revert\",\n \"am\",\n \"pull\"\n]);\nfunction subcommandArgs(tokens, sub, valueFlags = /* @__PURE__ */ new Set()) {\n const after = tokens.slice(tokens.indexOf(sub) + 1);\n const flags = [];\n const positionals = [];\n for (let i = 0; i < after.length; i++) {\n const t = after[i];\n if (!t.startsWith(\"-\")) {\n positionals.push(t);\n continue;\n }\n flags.push(t);\n if (valueFlags.has(t)) i++;\n }\n return {\n flags,\n positionals\n };\n}\nconst GIT_PUSH_VALUE_FLAGS = /* @__PURE__ */ new Set([\n \"-o\",\n \"--push-option\",\n \"--repo\",\n \"--receive-pack\",\n \"--exec\"\n]);\nfunction isMergeFastForwardFromUpstream(tokens, upstream) {\n const { flags, positionals } = subcommandArgs(tokens, \"merge\");\n if (!flags.includes(\"--ff-only\") || positionals.length !== 1) return false;\n const ref = positionals[0];\n return ref === \"@{u}\" || ref === \"@{upstream}\" || upstream !== null && ref === upstream;\n}\nconst GIT_PULL_NO_REBASE = /* @__PURE__ */ new Set([\n \"--rebase=false\",\n \"--rebase=no\",\n \"--rebase=off\",\n \"--rebase=0\"\n]);\nfunction isPullFromUpstream(tokens, upstream) {\n const { flags, positionals } = subcommandArgs(tokens, \"pull\");\n if (flags.some((t) => t.startsWith(\"--rebase\") && !GIT_PULL_NO_REBASE.has(t) || t === \"--no-ff\" || !t.startsWith(\"--\") && t.slice(1).includes(\"r\"))) return false;\n if (positionals.length === 0) return true;\n if (positionals.length !== 2 || upstream === null) return false;\n const [remote, branch] = positionals;\n return `${remote}/${branch.replace(/^refs\\/heads\\//, \"\")}` === upstream;\n}\nfunction isRefMovingReset(tokens, cwd, head) {\n if (tokens.slice(tokens.indexOf(\"reset\") + 1).includes(\"--\")) return false;\n const { positionals } = subcommandArgs(tokens, \"reset\");\n if (positionals.length !== 1) return false;\n if (head !== \"HEAD\" && /^(HEAD|@)/.test(positionals[0])) return false;\n const target = resolveCommit(cwd, positionals[0]);\n if (target === null) return false;\n return target !== resolveCommit(cwd, head);\n}\nfunction pushDestinations(tokens, current) {\n const { positionals } = subcommandArgs(tokens, \"push\", GIT_PUSH_VALUE_FLAGS);\n const refspecs = positionals.slice(1);\n if (refspecs.length === 0) return null;\n const names = [];\n for (const spec of refspecs) {\n const dst = refspecDestination(spec);\n if (dst === \"HEAD\" || dst === \"@\") {\n if (current !== null) names.push(current);\n continue;\n }\n names.push(dst);\n }\n return names;\n}\nconst GIT_PUSH_EVERY_BRANCH = /* @__PURE__ */ new Set([\n \"--all\",\n \"--branches\",\n \"--mirror\"\n]);\nfunction leavesRefInPlace(tokens, sub) {\n const { flags } = subcommandArgs(tokens, sub);\n if (flags.includes(\"--abort\") || flags.includes(\"--quit\")) return true;\n return sub === \"commit\" && flags.includes(\"--dry-run\");\n}\nfunction movesProtectedRef(tokens, ctx) {\n const sub = getGitSubcommand(tokens);\n if (sub === void 0) return false;\n if (sub === \"switch\" || sub === \"checkout\" || sub === \"worktree\") return isGitProtectedBranchForceCreate(tokens, ctx);\n if (sub === \"branch\") return isGitProtectedBranchRefWrite(tokens, ctx);\n if (sub === \"fetch\") return isGitProtectedBranchFetchWrite(tokens, ctx);\n if (sub === \"update-ref\") return isGitProtectedUpdateRef(tokens, ctx);\n if (sub !== \"push\" && sub !== \"reset\" && !GIT_CHECKED_OUT_WRITES.has(sub)) return false;\n const cwd = gitEffectiveCwd(tokens, ctx.cwd);\n const pending = ctx.pendingBranches?.[cwd];\n const current = pending ? pending.branch : getCurrentBranch(cwd);\n const protectedSet = getProtectedBranches(cwd, ctx.repoProtectedBranches ?? []);\n const onProtected = current !== null && protectedSet.has(current);\n if (sub === \"push\") {\n const { flags } = subcommandArgs(tokens, \"push\", GIT_PUSH_VALUE_FLAGS);\n if (flags.some((f) => GIT_PUSH_EVERY_BRANCH.has(f))) return true;\n const destinations = pushDestinations(tokens, current);\n if (destinations === null) {\n if (flags.includes(\"--tags\")) return false;\n return onProtected;\n }\n return destinations.some((d) => protectedSet.has(d));\n }\n if (!onProtected) return false;\n if (leavesRefInPlace(tokens, sub)) return false;\n if (sub === \"reset\") return isRefMovingReset(tokens, cwd, pending ? current : \"HEAD\");\n if (sub === \"merge\" || sub === \"pull\") {\n const upstream = getUpstream(cwd, current);\n if (sub === \"merge\" && isMergeFastForwardFromUpstream(tokens, upstream)) return false;\n if (sub === \"pull\" && isPullFromUpstream(tokens, upstream)) return false;\n }\n return true;\n}\nconst PNPM_BUILTINS = /* @__PURE__ */ new Set([\n \"install\",\n \"i\",\n \"add\",\n \"remove\",\n \"rm\",\n \"uninstall\",\n \"un\",\n \"update\",\n \"up\",\n \"upgrade\",\n \"list\",\n \"ls\",\n \"ll\",\n \"la\",\n \"why\",\n \"view\",\n \"info\",\n \"show\",\n \"outdated\",\n \"audit\",\n \"licenses\",\n \"init\",\n \"create\",\n \"link\",\n \"unlink\",\n \"pack\",\n \"publish\",\n \"config\",\n \"set\",\n \"get\",\n \"store\",\n \"cache\",\n \"rebuild\",\n \"rb\",\n \"prune\",\n \"dedupe\",\n \"dlx\",\n \"exec\",\n \"run\",\n \"patch\",\n \"patch-commit\",\n \"recursive\",\n \"-r\"\n]);\nconst NPM_READ_OPS = /* @__PURE__ */ new Set([\n \"view\",\n \"info\",\n \"show\"\n]);\nfunction isNpmReadOp(tokens) {\n const sub = tokens[1];\n return sub !== void 0 && NPM_READ_OPS.has(sub);\n}\nfunction getNpmRedirectReason(tokens) {\n const sub = tokens[1];\n const BASE = \"This project uses pnpm.\";\n if (!sub) return `${BASE} Use \\`pnpm\\` instead of npm.`;\n if (sub === \"install\" || sub === \"i\" || sub === \"ci\") {\n if (tokens.slice(2).some((t) => !t.startsWith(\"-\"))) return `${BASE} Use \\`pnpm add <pkg>\\` instead of \\`npm install <pkg>\\`.`;\n return `${BASE} Use \\`pnpm install\\` instead of npm.`;\n }\n if (sub === \"add\") return `${BASE} Use \\`pnpm add <pkg>\\` instead of npm.`;\n if (sub === \"uninstall\" || sub === \"remove\" || sub === \"rm\" || sub === \"un\" || sub === \"r\") return `${BASE} Use \\`pnpm remove <pkg>\\` instead of npm.`;\n if (sub === \"run\" || sub === \"run-script\") return `${BASE} Use \\`pnpm run <script>\\` or \\`pnpm <script>\\` instead of npm.`;\n if (sub === \"test\" || sub === \"t\" || sub === \"tst\") return `${BASE} Use \\`pnpm test\\` instead of npm.`;\n if (sub === \"start\") return `${BASE} Use \\`pnpm start\\` instead of npm.`;\n if (sub === \"stop\") return `${BASE} Use \\`pnpm stop\\` instead of npm.`;\n if (sub === \"restart\") return `${BASE} Use \\`pnpm restart\\` instead of npm.`;\n if (sub === \"exec\") return `${BASE} Use \\`pnpm exec <cmd>\\` instead of npm.`;\n if (sub === \"list\" || sub === \"ls\" || sub === \"ll\" || sub === \"la\") return `${BASE} Use \\`pnpm list\\` instead of npm.`;\n if (sub === \"why\") return `${BASE} Use \\`pnpm why\\` instead of npm.`;\n if (sub === \"outdated\") return `${BASE} Use \\`pnpm outdated\\` instead of npm.`;\n if (sub === \"audit\") return `${BASE} Use \\`pnpm audit\\` instead of npm.`;\n if (sub === \"update\" || sub === \"up\" || sub === \"upgrade\") return `${BASE} Use \\`pnpm update\\` instead of npm.`;\n if (sub === \"init\") return `${BASE} Use \\`pnpm init\\` instead of npm.`;\n if (sub === \"publish\") return `${BASE} Use \\`pnpm publish\\` instead of npm.`;\n if (sub === \"link\") return `${BASE} Use \\`pnpm link\\` instead of npm.`;\n if (sub === \"unlink\") return `${BASE} Use \\`pnpm unlink\\` instead of npm.`;\n if (sub === \"pack\") return `${BASE} Use \\`pnpm pack\\` instead of npm.`;\n if (sub === \"cache\") return `${BASE} Use \\`pnpm store\\` instead of \\`npm cache\\`.`;\n if (sub === \"config\" || sub === \"set\" || sub === \"get\") return `${BASE} Use \\`pnpm config\\` instead of npm.`;\n return `${BASE} Use the equivalent pnpm command instead of npm.`;\n}\nconst LAST_POSITIONAL_DEST = /* @__PURE__ */ new Set([\n \"cp\",\n \"mv\",\n \"rsync\",\n \"install\",\n \"ln\"\n]);\nconst ALL_POSITIONAL_DEST = /* @__PURE__ */ new Set([\"tee\", \"truncate\"]);\nfunction explicitTargetDirectory(rest) {\n for (const [i, t] of rest.entries()) {\n if (t === \"-t\" || t === \"--target-directory\") return rest[i + 1] ?? null;\n if (t.startsWith(\"--target-directory=\")) return t.slice(19);\n }\n return null;\n}\nconst OUTPUT_REDIRECT = /^(?:\\d*|&)>{1,2}$/;\nconst OUTPUT_REDIRECT_ATTACHED = /^(?:\\d*|&)>{1,2}(.+)$/;\nconst INPUT_REDIRECT = /^\\d*<$/;\nconst INPUT_REDIRECT_ATTACHED = /^\\d*<([^<].*)$/;\nfunction spacedRedirectDirection(token) {\n if (OUTPUT_REDIRECT.test(token)) return \"out\";\n if (INPUT_REDIRECT.test(token)) return \"in\";\n return null;\n}\nfunction splitRedirects(tokens) {\n const redirects = [];\n const rest = [];\n for (let i = 0; i < tokens.length; i++) {\n const t = tokens[i];\n const spaced = spacedRedirectDirection(t);\n if (spaced !== null) {\n const next = tokens[i + 1];\n if (next !== void 0) redirects.push({\n direction: spaced,\n target: next\n });\n i++;\n continue;\n }\n const out = OUTPUT_REDIRECT_ATTACHED.exec(t);\n if (out) {\n redirects.push({\n direction: \"out\",\n target: out[1]\n });\n continue;\n }\n const input = INPUT_REDIRECT_ATTACHED.exec(t);\n if (input) {\n redirects.push({\n direction: \"in\",\n target: input[1]\n });\n continue;\n }\n rest.push(t);\n }\n return {\n redirects,\n rest\n };\n}\nfunction redirectTargets(tokens, direction) {\n return splitRedirects(tokens).redirects.filter((r) => r.direction === direction).map((r) => r.target);\n}\nfunction writeDestinations(tokens) {\n const cmd = path.basename(tokens[0] ?? \"\");\n const rest = tokens.slice(1);\n const dests = redirectTargets(tokens, \"out\");\n if (cmd === \"dd\") dests.push(...rest.filter((t) => t.startsWith(\"of=\")).map((t) => t.slice(3)));\n else if (cmd === \"sed\") {\n if (rest.some((t) => t === \"--in-place\" || /^-[a-zA-Z]*i/.test(t))) dests.push(...rest.filter((t) => !t.startsWith(\"-\")));\n } else if (ALL_POSITIONAL_DEST.has(cmd)) dests.push(...rest.filter((t) => !t.startsWith(\"-\")));\n else if (LAST_POSITIONAL_DEST.has(cmd)) {\n if (cmd === \"cp\" && rest.includes(\"--remove-destination\")) return dests;\n const targetDir = explicitTargetDirectory(rest);\n if (targetDir !== null) dests.push(targetDir);\n else {\n const last = rest.findLast((t) => !t.startsWith(\"-\"));\n if (last !== void 0) dests.push(last);\n }\n }\n return dests;\n}\nfunction writesIntoNodeModules(tokens, ctx) {\n return writeDestinations(tokens).some((d) => targetsNodeModules(d, ctx.cwd));\n}\nconst NODE_MODULES_WRITE_REASON = \"Don't write into node_modules/. Its files are hardlinks into pnpm's global store, so overwriting one in place (plain `cp`, `>` redirect, `sed -i`, ...) writes through the link and corrupts that package for every project on this machine -- silently, since the directory you wrote into ends up fine. To test a local build as an installed dependency, run it directly (`node packages/<pkg>/dist/bin.mjs <cmd>`), or install into a scratch tree with an isolated store (`pnpm add --store-dir <scratch> <pkg>`). If you must replace a file in place, `cp --remove-destination` is safe -- it unlinks the target first. Run `pnpm store status` to check whether the store is already damaged.\";\nfunction isChmod777(tokens) {\n return tokens.some((t) => t === \"777\" || t === \"0777\");\n}\nfunction isGitForcePush(tokens) {\n const pushIdx = tokens.indexOf(\"push\");\n if (pushIdx === -1) return false;\n return tokens.slice(pushIdx + 1).some((t) => t === \"--force\" || t === \"-f\");\n}\nconst ENV_READ_FILE_COMMANDS = /* @__PURE__ */ new Set([\n \"cat\",\n \"head\",\n \"tail\",\n \"less\",\n \"more\",\n \"source\",\n \".\"\n]);\nconst GREP_FAMILY_COMMANDS = /* @__PURE__ */ new Set([\n \"grep\",\n \"rg\",\n \"ag\"\n]);\nfunction grepFileOperands(rest) {\n const files = [];\n let patternAccountedFor = false;\n let optionsEnded = false;\n for (let i = 0; i < rest.length; i++) {\n const t = rest[i];\n if (!optionsEnded && t === \"--\") {\n optionsEnded = true;\n continue;\n }\n if (!optionsEnded) {\n if (t === \"-f\" || t === \"--file\") {\n const value = rest[i + 1];\n if (value !== void 0) {\n files.push(value);\n i++;\n }\n patternAccountedFor = true;\n continue;\n }\n if (t.startsWith(\"--file=\")) {\n files.push(t.slice(7));\n patternAccountedFor = true;\n continue;\n }\n if (t.startsWith(\"-f\") && t !== \"-f\" && !t.startsWith(\"--\")) {\n files.push(t.slice(2));\n patternAccountedFor = true;\n continue;\n }\n if (t === \"-e\" || t === \"--regexp\") {\n i++;\n patternAccountedFor = true;\n continue;\n }\n if (t.startsWith(\"--regexp=\") || t.startsWith(\"-e\") && t !== \"-e\" && !t.startsWith(\"--\")) {\n patternAccountedFor = true;\n continue;\n }\n if (t.startsWith(\"-\")) continue;\n }\n if (!patternAccountedFor) {\n patternAccountedFor = true;\n continue;\n }\n files.push(t);\n }\n return files;\n}\nfunction envFileOperands(tokens) {\n const cmd = path.basename(tokens[0] ?? \"\");\n const { redirects, rest } = splitRedirects(tokens.slice(1));\n const operands = redirects.filter((r) => r.direction === \"in\").map((r) => r.target);\n if (GREP_FAMILY_COMMANDS.has(cmd)) operands.push(...grepFileOperands(rest));\n else if (ENV_READ_FILE_COMMANDS.has(cmd)) operands.push(...rest.filter((t) => !t.startsWith(\"-\")));\n return operands;\n}\nconst HEALTH_CEILING_FILES = [\"health-baseline.json\", \".tooling.json\"];\nconst HEALTH_BASELINE_FILE = \"health-baseline.json\";\nconst HEALTH_RAISE_REASON = \"A health ceiling raise travels alone: open a ceiling-only pull request -- only health-baseline.json and .tooling.json changed -- for a human to merge, or split the module. `health:check` refuses the raise beside code either way. To restore the default branch's copy run `git show origin/<default>:health-baseline.json > health-baseline.json`, and to adopt lower counts run `pnpm exec bst health:check --update-baseline`.\";\nfunction writesHealthBaseline(tokens, cwd) {\n const destinations = writeDestinations(tokens);\n if (!destinations.some((dest) => baseName(dest) === \"health-baseline.json\")) return false;\n const root = getRepoTopLevel(cwd) ?? cwd;\n return destinations.some((dest) => samePath(resolveIn(cwd, dest), path.join(root, HEALTH_BASELINE_FILE)));\n}\nfunction baseName(target) {\n const segments = target.split(/[/\\\\]/);\n return segments[segments.length - 1] ?? \"\";\n}\nfunction isBaselineRestore(tokens) {\n if (tokens[0] !== \"git\") return false;\n let i = 1;\n while (tokens[i]?.startsWith(\"-\") === true) i += GIT_FLAGS_WITH_ARG.has(tokens[i] ?? \"\") ? 2 : 1;\n if (tokens[i] !== \"show\") return false;\n return tokens.slice(i + 1).some((token) => token.includes(\":\") && baseName(token.slice(token.lastIndexOf(\":\") + 1)) === \"health-baseline.json\");\n}\nfunction isHealthBaselineReset(tokens) {\n return tokens.includes(\"health:check\") && tokens.includes(\"--update-baseline\") && tokens.includes(\"--reset\");\n}\nfunction raiseTravelsWithCode(cwd) {\n return branchCarriesOtherFiles(cwd, HEALTH_CEILING_FILES) === true;\n}\nconst denyRules = [\n {\n name: \"sudo\",\n match: /^sudo$/,\n action: \"deny\",\n reason: \"Don't use sudo. If you need elevated permissions, ask me to do it manually.\"\n },\n {\n name: \"git-force-push\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens) => isGitForcePush(tokens),\n reason: \"Use `git push --force-with-lease` instead of `--force`.\"\n },\n {\n name: \"git-protected-branch-write\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens, ctx) => movesProtectedRef(tokens, ctx),\n reason: \"This would move a protected branch (main, master, the remote's default branch, or one listed in .claude/hooks/bst-hook.settings.json#protectedBranches). Work on a feature branch in a worktree: `git worktree add ../<repo>-<topic> -b feat/<topic>`, then commit and push there. To bring `main` up to date, use `git pull --ff-only`.\"\n },\n {\n name: \"chmod-777\",\n match: /^chmod$/,\n action: \"deny\",\n when: (tokens) => isChmod777(tokens),\n reason: \"Don't use 777 permissions. Use more restrictive permissions.\"\n },\n {\n name: \"node-modules-write\",\n match: /./,\n action: \"deny\",\n when: (tokens, ctx) => writesIntoNodeModules(tokens, ctx),\n reason: NODE_MODULES_WRITE_REASON\n },\n {\n name: \"env-file-read\",\n match: /./,\n action: \"deny\",\n when: (tokens) => envFileOperands(tokens).some(isEnvFile),\n reason: \"Don't read .env files. These contain secrets that should not appear in the conversation.\"\n },\n {\n name: \"env-file-write\",\n match: /./,\n action: \"deny\",\n when: (tokens) => redirectTargets(tokens, \"out\").some(isEnvFile),\n reason: \"Don't write to .env files. These contain secrets.\"\n },\n {\n name: \"health-baseline-write\",\n match: /./,\n action: \"deny\",\n when: (tokens, ctx) => writesHealthBaseline(tokens, ctx.cwd) && !isBaselineRestore(tokens) && raiseTravelsWithCode(ctx.cwd),\n reason: HEALTH_RAISE_REASON\n },\n {\n name: \"health-baseline-reset\",\n match: /^bst$/,\n action: \"deny\",\n when: (tokens, ctx) => isHealthBaselineReset(tokens) && raiseTravelsWithCode(ctx.cwd),\n reason: HEALTH_RAISE_REASON\n },\n {\n name: \"npx-redirect\",\n match: /^npx$/,\n action: \"deny\",\n reason: \"This project uses pnpm. Use `pnpm dlx <pkg>` or `pnpm exec <cmd>` instead of npx.\"\n },\n {\n name: \"mise-self-update\",\n match: /^mise$/,\n action: \"deny\",\n when: (tokens) => tokens[1] === \"self-update\",\n reason: \"Don't self-update mise. The version is Renovate-managed; ask a human to bump the pin.\"\n }\n];\n\n//#endregion\n//#region src/lib/script-resolver.ts\nconst PackageJsonSchema = object({ scripts: record(string(), unknown()) });\nfunction resolveScript(scriptName, cwd) {\n const scripts = readScripts(cwd);\n if (!scripts) return null;\n const main = scripts[scriptName];\n if (typeof main !== \"string\") return null;\n const pre = scripts[`pre${scriptName}`];\n const post = scripts[`post${scriptName}`];\n return {\n pre: typeof pre === \"string\" ? pre : null,\n main,\n post: typeof post === \"string\" ? post : null\n };\n}\nfunction resolveFilteredScript(scriptName, filterPkg, cwd) {\n const pkgDir = findWorkspacePackageDir(filterPkg, cwd);\n if (!pkgDir) return null;\n const script = resolveScript(scriptName, pkgDir);\n if (!script) return null;\n return {\n ...script,\n pkgDir\n };\n}\nfunction resolveRecursiveScripts(scriptName, rootDir) {\n const pkgDirs = listWorkspacePackageDirs(rootDir);\n if (!pkgDirs) return null;\n const results = [];\n for (const pkgDir of pkgDirs) {\n const resolved = resolveScript(scriptName, pkgDir);\n if (resolved) results.push({\n ...resolved,\n pkgDir\n });\n }\n return results;\n}\nfunction listWorkspacePackageDirs(rootDir) {\n const patterns = readWorkspacePatterns(rootDir);\n if (!patterns) return null;\n const dirs = [];\n for (const pattern of patterns) {\n if (!pattern.endsWith(\"/*\")) continue;\n const baseDir = pattern.slice(0, -2);\n const parentDir = resolve(rootDir, baseDir);\n let entries;\n try {\n entries = readdirSync(parentDir);\n } catch {\n continue;\n }\n for (const entry of entries) {\n const entryDir = join(parentDir, entry);\n try {\n if (!statSync(entryDir).isDirectory()) continue;\n } catch {\n continue;\n }\n dirs.push(entryDir);\n }\n }\n return dirs.length > 0 ? dirs : null;\n}\nfunction findWorkspacePackageDir(pkgName, rootDir) {\n const pkgDirs = listWorkspacePackageDirs(rootDir);\n if (!pkgDirs) return null;\n for (const entryDir of pkgDirs) {\n const name = readPackageName(entryDir);\n if (name === pkgName || name === `@${pkgName}`) return entryDir;\n if (entryDir.split(\"/\").pop() === pkgName) return entryDir;\n if (name) {\n if ((name.startsWith(\"@\") ? name.split(\"/\")[1] : name) === pkgName) return entryDir;\n }\n }\n return null;\n}\nfunction readWorkspacePatterns(rootDir) {\n try {\n const lines = readFileSync(join(rootDir, \"pnpm-workspace.yaml\"), \"utf-8\").split(\"\\n\");\n const packagesIdx = lines.findIndex((l) => /^packages\\s*:/.test(l));\n if (packagesIdx === -1) return null;\n const patterns = [];\n for (let i = packagesIdx + 1; i < lines.length; i++) {\n const line = lines[i];\n const match = /^\\s+-\\s+(.+)$/.exec(line);\n if (!match) break;\n patterns.push(match[1].trim().replace(/['\"`]/g, \"\"));\n }\n return patterns.length > 0 ? patterns : null;\n } catch {\n return null;\n }\n}\nconst PackageNameSchema = object({ name: string() });\nfunction readPackageName(dir) {\n try {\n const raw = readFileSync(join(dir, \"package.json\"), \"utf-8\");\n const result = PackageNameSchema.safeParse(JSON.parse(raw));\n if (!result.success) return null;\n return result.data.name;\n } catch {\n return null;\n }\n}\nfunction readScripts(cwd) {\n try {\n const raw = readFileSync(join(cwd, \"package.json\"), \"utf-8\");\n const result = PackageJsonSchema.safeParse(JSON.parse(raw));\n if (!result.success) return null;\n return result.data.scripts;\n } catch {\n return null;\n }\n}\n\n//#endregion\n//#region src/lib/workspace-root.ts\nfunction findWorkspaceRoot(cwd) {\n let dir = resolve(cwd);\n while (true) {\n if (existsSync(join(dir, \"pnpm-workspace.yaml\"))) return dir;\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n//#endregion\n//#region src/evaluate.ts\nconst MAX_EVAL_DEPTH = 5;\nfunction subshellBody(command) {\n if (!command.startsWith(\"(\") || !command.endsWith(\")\")) return null;\n let depth = 0;\n let quote = null;\n for (let i = 0; i < command.length; i++) {\n const ch = command[i];\n if (quote !== null) {\n if (ch === quote) quote = null;\n else if (ch === \"\\\\\" && quote === \"\\\"\") i++;\n continue;\n }\n if (ch === \"\\\\\") i++;\n else if (ch === \"'\" || ch === \"\\\"\") quote = ch;\n else if (ch === \"(\") depth++;\n else if (ch === \")\") {\n depth--;\n if (depth === 0) return i === command.length - 1 ? command.slice(1, -1) : null;\n }\n }\n return null;\n}\nfunction extractCdDir(segment, ctx) {\n const tokenResult = tokenize(segment);\n if (!tokenResult.ok || tokenResult.tokens.length !== 2) return null;\n const tokens = tokenResult.tokens;\n if (tokens[0] !== \"cd\") return null;\n const dir = tokens[1];\n if (path.isAbsolute(dir)) return dir;\n return path.resolve(ctx.cwd, dir);\n}\nfunction extractBranchSwitch(segment, ctx) {\n const tokenResult = tokenize(segment);\n if (!tokenResult.ok) return void 0;\n let tokens = tokenResult.tokens;\n let envSkipped = 0;\n while (envSkipped < tokens.length && /^\\w+=/.test(tokens[envSkipped])) envSkipped++;\n tokens = tokens.slice(envSkipped);\n if (tokens.length === 0 || path.basename(tokens[0]) !== \"git\") return void 0;\n return branchSwitch(tokens, ctx, checkInjection(tokens, segment).safe);\n}\nfunction evaluateCommand(command, ctx) {\n if (ctx.depth >= MAX_EVAL_DEPTH) return {\n action: \"none\",\n rule: null,\n reason: \"max evaluation depth exceeded\"\n };\n const trimmed = command.trim();\n if (!trimmed) return {\n action: \"none\",\n rule: null,\n reason: null\n };\n const body = subshellBody(trimmed);\n if (body !== null) return evaluateCommand(body, {\n ...ctx,\n depth: ctx.depth + 1\n });\n const segments = splitCommand(trimmed);\n if (segments.length === 0) return {\n action: \"none\",\n rule: null,\n reason: null\n };\n let firstNone = null;\n const currentCtx = { ...ctx };\n let unknownAfterNext;\n for (const segment of segments) {\n if (segment.operator === \"&&\") {\n const cdDir = extractCdDir(segment.command, currentCtx);\n if (cdDir) currentCtx.cwd = cdDir;\n }\n const result = evaluateSegment(segment.command, currentCtx);\n if (result.action === \"deny\") return result;\n if (!firstNone) firstNone = result;\n if (unknownAfterNext) {\n currentCtx.pendingBranches = {\n ...currentCtx.pendingBranches,\n [unknownAfterNext.dir]: unknownAfterNext\n };\n unknownAfterNext = void 0;\n }\n const switched = extractBranchSwitch(segment.command, currentCtx);\n if (switched) {\n const unknown = {\n dir: switched.dir,\n branch: null\n };\n if (segment.operator === \"&&\") currentCtx.pendingBranches = {\n ...currentCtx.pendingBranches,\n [switched.dir]: switched\n };\n else if (segment.operator === \"||\") unknownAfterNext = unknown;\n else currentCtx.pendingBranches = {\n ...currentCtx.pendingBranches,\n [switched.dir]: unknown\n };\n }\n }\n return firstNone ?? {\n action: \"none\",\n rule: null,\n reason: null\n };\n}\nfunction evaluateResolvedScript(resolved, ctx) {\n if (resolved.pre) {\n const preResult = evaluateCommand(resolved.pre, ctx);\n if (preResult.action === \"deny\") return preResult;\n }\n const mainResult = evaluateCommand(resolved.main, ctx);\n if (mainResult.action === \"deny\") return mainResult;\n if (resolved.post) {\n const postResult = evaluateCommand(resolved.post, ctx);\n if (postResult.action === \"deny\") return postResult;\n }\n return {\n action: \"none\",\n rule: null,\n reason: null\n };\n}\nfunction evaluateSegment(rawSegment, ctx) {\n const trimmed = rawSegment.trim();\n const body = subshellBody(trimmed);\n if (body !== null) return evaluateCommand(body, {\n ...ctx,\n depth: ctx.depth + 1\n });\n if (trimmed.startsWith(\"(\") && trimmed.endsWith(\")\")) return {\n action: \"none\",\n rule: null,\n reason: \"subshell\"\n };\n const tokenResult = tokenize(rawSegment);\n if (!tokenResult.ok) return {\n action: \"none\",\n rule: null,\n reason: tokenResult.error\n };\n let tokens = tokenResult.tokens;\n if (tokens.length === 0) return {\n action: \"none\",\n rule: null,\n reason: null\n };\n let envSkipped = 0;\n while (envSkipped < tokens.length && /^\\w+=/.test(tokens[envSkipped])) envSkipped++;\n if (envSkipped >= tokens.length) return {\n action: \"none\",\n rule: null,\n reason: null\n };\n tokens = tokens.slice(envSkipped);\n if (writesIntoNodeModules(tokens, ctx)) return {\n action: \"deny\",\n rule: \"node-modules-write\",\n reason: NODE_MODULES_WRITE_REASON\n };\n const injection = checkInjection(tokens, rawSegment);\n if (!injection.safe) return {\n action: \"none\",\n rule: null,\n reason: injection.reason\n };\n const SAFE_REDIRECT_TOKENS = /* @__PURE__ */ new Set([\n \"2>&1\",\n \"2>/dev/null\",\n \">/dev/null\",\n \"1>/dev/null\"\n ]);\n tokens = tokens.filter((t, i) => {\n if (SAFE_REDIRECT_TOKENS.has(t)) return false;\n if ((t === \">\" || t === \">>\") && tokens[i + 1] === \"/dev/null\") return false;\n if (t === \"/dev/null\" && i > 0 && (tokens[i - 1] === \">\" || tokens[i - 1] === \">>\")) return false;\n return true;\n });\n const cmdName = path.basename(tokens[0]);\n if (cmdName === \"command\" && tokens.length > 1) return evaluateCommand(joinTokens(tokens.slice(1)), {\n ...ctx,\n depth: ctx.depth + 1\n });\n if (cmdName === \"env\") {\n let envIdx = 1;\n while (envIdx < tokens.length && /^\\w+=/.test(tokens[envIdx])) envIdx++;\n if (envIdx >= tokens.length) return {\n action: \"none\",\n rule: null,\n reason: null\n };\n return evaluateCommand(joinTokens(tokens.slice(envIdx)), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n if (cmdName === \"pnpm\") return evaluatePnpm(tokens, ctx);\n if (cmdName === \"npm\") return evaluateNpm(tokens);\n if (cmdName === \"mise\" && tokens[1] === \"exec\") {\n const inner = miseExecInner(tokens);\n if (!inner) return {\n action: \"none\",\n rule: null,\n reason: \"bare mise exec\"\n };\n return evaluateCommand(joinTokens(inner), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n if (cmdName === \"bash\" || cmdName === \"sh\" || cmdName === \"zsh\") return evaluateShellC(tokens, ctx);\n if (cmdName === \"xargs\") return evaluateXargs(tokens, ctx);\n if (cmdName === \"node\" && (tokens.includes(\"-e\") || tokens.includes(\"-p\") || tokens.includes(\"--eval\") || tokens.includes(\"--print\"))) return {\n action: \"none\",\n rule: null,\n reason: \"node eval\"\n };\n for (const rule of denyRules) if (rule.match.test(cmdName)) {\n if (!rule.when || rule.when(tokens, ctx)) return {\n action: \"deny\",\n rule: rule.name,\n reason: rule.reason ?? null\n };\n }\n const advice = cmdName === \"git\" ? worktreeAdvice(tokens, ctx) : void 0;\n return {\n action: \"none\",\n rule: null,\n reason: `no rule matched: ${cmdName}`,\n ...advice ? { advice } : {}\n };\n}\nfunction miseExecInner(tokens) {\n const dashIdx = tokens.indexOf(\"--\");\n if (dashIdx === -1) return void 0;\n const inner = tokens.slice(dashIdx + 1);\n return inner.length > 0 ? inner : void 0;\n}\nfunction scanPnpmFlags(subTokens, from, state) {\n for (let i = from; i < subTokens.length; i++) {\n const t = subTokens[i];\n if (t === \"--filter\" || t.startsWith(\"--filter=\") || t === \"-F\" || t.startsWith(\"-F=\")) return {\n name: void 0,\n filterIdx: i\n };\n if (t === \"-r\" || t === \"--recursive\") {\n state.isRecursive = true;\n continue;\n }\n if (t === \"-w\" || t === \"--workspace-root\") {\n state.isWorkspaceRoot = true;\n continue;\n }\n if (t === \"-C\" || t === \"--dir\") {\n state.dirOverride = subTokens[++i];\n continue;\n }\n if (t.startsWith(\"-C=\") || t.startsWith(\"--dir=\")) {\n state.dirOverride = t.split(\"=\", 2)[1];\n continue;\n }\n if (t.startsWith(\"-\") && !PNPM_BUILTINS.has(t)) continue;\n return { name: t };\n }\n return { name: void 0 };\n}\nfunction evaluatePnpm(tokens, ctx) {\n const subTokens = tokens.slice(1);\n const state = {\n isRecursive: false,\n isWorkspaceRoot: false,\n dirOverride: void 0\n };\n const scan = scanPnpmFlags(subTokens, 0, state);\n if (scan.filterIdx !== void 0) return evaluatePnpmFilter(tokens, subTokens, scan.filterIdx, ctx);\n const subcommand = scan.name;\n const subcommandIdx = subcommand ? subTokens.indexOf(subcommand) : -1;\n if (!subcommand) return {\n action: \"none\",\n rule: null,\n reason: null\n };\n if (subcommand === \"dlx\") return {\n action: \"none\",\n rule: null,\n reason: \"pnpm dlx\"\n };\n if (subcommand === \"add\") return {\n action: \"none\",\n rule: null,\n reason: \"pnpm add\"\n };\n if (subcommand === \"exec\") {\n const execArgs = subTokens.slice(subcommandIdx + 1);\n if (execArgs.length === 0) return {\n action: \"none\",\n rule: null,\n reason: \"bare pnpm exec\"\n };\n return evaluateCommand(joinTokens(execArgs), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n let scriptName;\n let { isRecursive, isWorkspaceRoot, dirOverride } = state;\n if (subcommand === \"run\") {\n const runScan = scanPnpmFlags(subTokens, subcommandIdx + 1, state);\n ({isRecursive, isWorkspaceRoot, dirOverride} = state);\n if (runScan.filterIdx !== void 0) return evaluatePnpmFilter(tokens, subTokens, runScan.filterIdx, ctx);\n if (!runScan.name) return {\n action: \"none\",\n rule: null,\n reason: \"pnpm run without script name\"\n };\n scriptName = runScan.name;\n } else if (!PNPM_BUILTINS.has(subcommand)) scriptName = subcommand;\n else return {\n action: \"none\",\n rule: null,\n reason: null\n };\n if (isRecursive) return evaluatePnpmRecursive(scriptName, ctx);\n if (isWorkspaceRoot) {\n const root = findWorkspaceRoot(ctx.cwd);\n if (!root) return {\n action: \"none\",\n rule: null,\n reason: \"workspace root not found\"\n };\n return evaluatePnpmScript(scriptName, {\n ...ctx,\n cwd: root\n });\n }\n if (dirOverride) {\n const resolved = path.resolve(ctx.cwd, dirOverride);\n return evaluatePnpmScript(scriptName, {\n ...ctx,\n cwd: resolved\n });\n }\n return evaluatePnpmScript(scriptName, ctx);\n}\nfunction evaluatePnpmScript(scriptName, ctx) {\n const resolved = resolveScript(scriptName, ctx.cwd);\n if (!resolved) return {\n action: \"none\",\n rule: null,\n reason: \"script resolution failed\"\n };\n const res = evaluateResolvedScript(resolved, {\n ...ctx,\n depth: ctx.depth + 1\n });\n if (res.action === \"deny\") return res;\n return {\n action: \"none\",\n rule: `pnpm-script:${scriptName}`,\n reason: null\n };\n}\nfunction evaluatePnpmRecursive(scriptName, ctx) {\n const resolved = resolveRecursiveScripts(scriptName, ctx.cwd);\n if (!resolved || resolved.length === 0) return {\n action: \"none\",\n rule: null,\n reason: \"pnpm recursive\"\n };\n for (const pkg of resolved) {\n const res = evaluateResolvedScript(pkg, {\n ...ctx,\n depth: ctx.depth + 1,\n cwd: pkg.pkgDir ?? ctx.cwd\n });\n if (res.action === \"deny\") return res;\n }\n return {\n action: \"none\",\n rule: `pnpm-recursive:${scriptName}`,\n reason: null\n };\n}\nfunction evaluateNpm(tokens) {\n if (isNpmReadOp(tokens)) return {\n action: \"none\",\n rule: null,\n reason: null\n };\n return {\n action: \"deny\",\n rule: \"npm-redirect\",\n reason: getNpmRedirectReason(tokens)\n };\n}\nfunction evaluatePnpmFilter(_tokens, subTokens, filterIdx, ctx) {\n const filterPkgs = [];\n let cursor = filterIdx;\n while (cursor < subTokens.length) {\n const t = subTokens[cursor];\n if (t === \"--filter\" || t === \"-F\") {\n const pkg = subTokens[cursor + 1];\n if (!pkg || pkg.startsWith(\"-\")) return {\n action: \"none\",\n rule: null,\n reason: \"pnpm --filter without package\"\n };\n filterPkgs.push(pkg);\n cursor += 2;\n } else if (t.startsWith(\"--filter=\") || t.startsWith(\"-F=\")) {\n const pkg = t.startsWith(\"-F=\") ? t.slice(3) : t.slice(9);\n if (!pkg) return {\n action: \"none\",\n rule: null,\n reason: \"pnpm --filter without package\"\n };\n filterPkgs.push(pkg);\n cursor += 1;\n } else break;\n }\n if (filterPkgs.length === 0) return {\n action: \"none\",\n rule: null,\n reason: \"pnpm --filter without package\"\n };\n let filterSubcommand;\n let subcmdIdx = -1;\n for (let i = cursor; i < subTokens.length; i++) {\n const t = subTokens[i];\n if (t === \"--filter\" || t === \"-F\") {\n i += 1;\n continue;\n }\n if (t.startsWith(\"--filter=\") || t.startsWith(\"-F=\")) continue;\n if (t.startsWith(\"-\") && !PNPM_BUILTINS.has(t)) continue;\n filterSubcommand = t;\n subcmdIdx = i;\n break;\n }\n if (!filterSubcommand) return {\n action: \"none\",\n rule: null,\n reason: \"pnpm --filter without subcommand\"\n };\n if (PNPM_BUILTINS.has(filterSubcommand) && filterSubcommand !== \"run\" && filterSubcommand !== \"exec\") return {\n action: \"none\",\n rule: null,\n reason: null\n };\n if (filterSubcommand === \"exec\") {\n const execArgs = subTokens.slice(subcmdIdx + 1);\n if (execArgs.length === 0) return {\n action: \"none\",\n rule: null,\n reason: \"bare pnpm exec\"\n };\n return evaluateCommand(joinTokens(execArgs), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n const scriptName = filterSubcommand === \"run\" ? subTokens[subcmdIdx + 1] : filterSubcommand;\n if (!scriptName || scriptName.startsWith(\"-\")) return {\n action: \"none\",\n rule: null,\n reason: \"pnpm --filter run without script name\"\n };\n if (PNPM_BUILTINS.has(scriptName) && scriptName !== \"run\") return {\n action: \"none\",\n rule: null,\n reason: null\n };\n for (const pkg of filterPkgs) {\n const resolved = resolveFilteredScript(scriptName, pkg, ctx.cwd);\n if (!resolved) return {\n action: \"none\",\n rule: null,\n reason: \"filtered script resolution failed\"\n };\n const res = evaluateResolvedScript(resolved, {\n ...ctx,\n depth: ctx.depth + 1,\n cwd: resolved.pkgDir ?? ctx.cwd\n });\n if (res.action === \"deny\") return res;\n }\n return {\n action: \"none\",\n rule: `pnpm-filter:${filterPkgs.join(\",\")}:${scriptName}`,\n reason: null\n };\n}\nconst SHELL_LOGIN_FLAGS = /* @__PURE__ */ new Set([\"-l\", \"--login\"]);\nfunction evaluateShellC(tokens, ctx) {\n let i = 1;\n while (i < tokens.length && SHELL_LOGIN_FLAGS.has(tokens[i])) i++;\n const flag = tokens[i];\n if (flag === void 0 || !/^-[lc]+$/.test(flag) || !flag.includes(\"c\")) return {\n action: \"none\",\n rule: null,\n reason: \"shell without a plain -c\"\n };\n const cmdArg = tokens[i + 1];\n if (!cmdArg) return {\n action: \"none\",\n rule: null,\n reason: \"shell -c without command\"\n };\n if (tokens.length > i + 2) return {\n action: \"none\",\n rule: null,\n reason: \"shell -c with positional args\"\n };\n return evaluateCommand(cmdArg, {\n ...ctx,\n depth: ctx.depth + 1\n });\n}\nconst XARGS_ARG_FLAGS = /* @__PURE__ */ new Set([\n \"-I\",\n \"-d\",\n \"-n\",\n \"-P\",\n \"-s\",\n \"-L\",\n \"-E\",\n \"-a\",\n \"--max-args\",\n \"--max-procs\",\n \"--delimiter\",\n \"--replace\",\n \"--max-lines\",\n \"--max-chars\",\n \"--arg-file\",\n \"--process-slot-var\"\n]);\nconst XARGS_BOOL_FLAGS = /* @__PURE__ */ new Set([\n \"-0\",\n \"--null\",\n \"-t\",\n \"--verbose\",\n \"-r\",\n \"--no-run-if-empty\",\n \"-p\",\n \"--interactive\",\n \"-x\",\n \"--exit\",\n \"--show-limits\"\n]);\nfunction evaluateXargs(tokens, ctx) {\n let i = 1;\n while (i < tokens.length) {\n const t = tokens[i];\n if (!t.startsWith(\"-\")) break;\n if (XARGS_ARG_FLAGS.has(t)) {\n i += 2;\n continue;\n }\n const eqIdx = t.indexOf(\"=\");\n if (eqIdx !== -1) {\n const flagName = t.slice(0, eqIdx);\n if (XARGS_ARG_FLAGS.has(flagName)) {\n i++;\n continue;\n }\n }\n if (t.length > 2 && XARGS_ARG_FLAGS.has(t.slice(0, 2))) {\n i++;\n continue;\n }\n if (XARGS_BOOL_FLAGS.has(t)) {\n i++;\n continue;\n }\n return {\n action: \"none\",\n rule: null,\n reason: \"xargs with unrecognised flags\"\n };\n }\n if (i >= tokens.length) return {\n action: \"none\",\n rule: null,\n reason: \"xargs without command\"\n };\n return evaluateCommand(joinTokens(tokens.slice(i)), {\n ...ctx,\n depth: ctx.depth + 1\n });\n}\n\n//#endregion\n//#region src/file-rules.ts\nconst READ_TOOLS = /* @__PURE__ */ new Set([\n \"Read\",\n \"Glob\",\n \"Grep\"\n]);\nconst WRITE_TOOLS = /* @__PURE__ */ new Set([\"Write\", \"Edit\"]);\nconst fileDenyRules = [\n {\n name: \"env-file-read\",\n tools: READ_TOOLS,\n action: \"deny\",\n when: (filePath) => isEnvFile(filePath),\n reason: \"Don't read .env files. These contain secrets that should not appear in the conversation.\"\n },\n {\n name: \"env-file-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath) => isEnvFile(filePath),\n reason: \"Don't write to .env files. These contain secrets.\"\n },\n {\n name: \"hooks-folder-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx) => {\n const resolved = resolveIn(ctx.cwd, filePath);\n const hooksDir = path.join(ctx.cwd, \".claude\", \"hooks\");\n return isWithin(hooksDir, resolved);\n },\n reason: \"Don't write to .claude/hooks/. These files are managed by the project and should not be modified directly.\"\n },\n {\n name: \"git-dir-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx) => targetsGitDir(filePath, ctx.cwd),\n reason: \"Don't write into .git/ directly. Its internals (config, hooks, refs) are not plain project files -- a config edit can redirect remotes or push destinations that other rules trust, and a hooks/ script runs automatically on the next commit/push. Use git subcommands instead, or ask the user.\"\n },\n {\n name: \"node-modules-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx) => targetsNodeModules(filePath, ctx.cwd),\n reason: \"Don't write into node_modules/. Its files are hardlinks into pnpm's global store -- editing one in place corrupts that package for every project on this machine. Edit the source package and rebuild, or install into a scratch tree with an isolated store (`pnpm add --store-dir <scratch> <pkg>`).\"\n },\n {\n name: \"claude-settings-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx) => {\n const resolved = resolveIn(ctx.cwd, filePath);\n return samePath(resolved, path.join(ctx.cwd, \".claude\", \"settings.json\"));\n },\n reason: \"Don't write to .claude/settings.json. This file is managed by the project and should not be modified directly.\"\n },\n {\n name: \"health-baseline-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx, toolInput) => {\n const resolved = resolveIn(ctx.cwd, filePath);\n if (path.basename(resolved) !== \"health-baseline.json\") return false;\n const root = getRepoTopLevel(ctx.cwd) ?? ctx.cwd;\n if (!samePath(resolved, path.join(root, \"health-baseline.json\"))) return false;\n if (!writeRaisesBaseline(resolved, toolInput)) return false;\n return branchCarriesOtherFiles(ctx.cwd, HEALTH_CEILING_FILES) === true;\n },\n reason: HEALTH_RAISE_REASON\n }\n];\nfunction writeRaisesBaseline(file, toolInput) {\n let before;\n try {\n before = readFileSync(file, \"utf8\");\n } catch (_error) {\n return false;\n }\n const after = textAfterWrite(before, toolInput);\n if (after === void 0) return false;\n const beforeJson = parseJson(before);\n const afterJson = parseJson(after);\n if (beforeJson === void 0 || afterJson === void 0) return false;\n return numberRises(beforeJson, afterJson);\n}\nfunction textAfterWrite(before, toolInput) {\n const { content, old_string: oldText, new_string: newText, replace_all: all } = toolInput;\n if (typeof content === \"string\") return content;\n if (typeof oldText !== \"string\" || typeof newText !== \"string\" || oldText === \"\") return void 0;\n const at = before.indexOf(oldText);\n if (at === -1) return void 0;\n if (all === true) return before.split(oldText).join(newText);\n return before.slice(0, at) + newText + before.slice(at + oldText.length);\n}\nfunction parseJson(text) {\n try {\n const result = record(string(), unknown()).safeParse(JSON.parse(text));\n return result.success ? result.data : void 0;\n } catch (_error) {\n return;\n }\n}\nfunction numberRises(before, after) {\n if (typeof after === \"number\") return typeof before !== \"number\" || after > before;\n if (typeof after !== \"object\" || after === null) return false;\n const beforeObject = typeof before === \"object\" && before !== null ? before : {};\n return Object.entries(after).some(([key, value]) => numberRises(Object.entries(beforeObject).find(([k]) => k === key)?.[1], value));\n}\n\n//#endregion\n//#region src/evaluate-tool.ts\nconst FILE_PATH_TOOLS = /* @__PURE__ */ new Set([\n \"Read\",\n \"Write\",\n \"Edit\"\n]);\nconst SEARCH_TOOLS = /* @__PURE__ */ new Set([\"Glob\", \"Grep\"]);\nfunction extractPath(toolName, toolInput, cwd) {\n if (FILE_PATH_TOOLS.has(toolName)) return parseFilePathInput(toolInput)?.file_path ?? null;\n if (SEARCH_TOOLS.has(toolName)) return parseSearchInput(toolInput)?.path ?? cwd;\n return null;\n}\nfunction evaluateToolUse(toolName, toolInput, ctx) {\n const filePath = extractPath(toolName, toolInput, ctx.cwd);\n if (filePath === null) return {\n action: \"none\",\n rule: null,\n reason: \"unknown tool input shape\"\n };\n for (const rule of fileDenyRules) if (rule.tools.has(toolName) && rule.when(filePath, ctx, toolInput)) return {\n action: \"deny\",\n rule: rule.name,\n reason: rule.reason ?? null\n };\n return {\n action: \"none\",\n rule: null,\n reason: \"no file rule matched\"\n };\n}\n\n//#endregion\n//#region src/lib/settings.ts\nconst SETTINGS_PATH = \".claude/hooks/bst-hook.settings.json\";\nconst BstHookSettingsSchema = object({ protectedBranches: _default(array(string().check(_minLength(1))), []) });\nfunction isFileClean(cwd, relativePath) {\n try {\n execFileSync(\"git\", [\n \"ls-files\",\n \"--error-unmatch\",\n relativePath\n ], {\n cwd,\n stdio: \"ignore\"\n });\n execFileSync(\"git\", [\n \"diff\",\n \"--quiet\",\n \"HEAD\",\n \"--\",\n relativePath\n ], {\n cwd,\n stdio: \"ignore\"\n });\n return true;\n } catch {\n return false;\n }\n}\nfunction loadRepoSettings(cwd) {\n const root = findWorkspaceRoot(cwd) ?? cwd;\n const settingsFile = join(root, SETTINGS_PATH);\n const empty = { protectedBranches: [] };\n if (!existsSync(settingsFile)) return empty;\n if (!isFileClean(root, SETTINGS_PATH)) return empty;\n try {\n const raw = readFileSync(settingsFile, \"utf-8\");\n const parsed = JSON.parse(raw);\n const result = BstHookSettingsSchema.safeParse(parsed);\n if (!result.success) return empty;\n return { protectedBranches: result.data.protectedBranches };\n } catch {\n return empty;\n }\n}\n\n//#endregion\n//#region src/index.ts\nconst inputResult = await readHookInput();\nif (!inputResult.ok) {\n process.stderr.write(`[bst-hook] invalid hook input: ${inputResult.error}\\n`);\n audit({\n sessionId: \"unknown\",\n cwd: \"unknown\",\n command: `<invalid input: ${inputResult.error}>`,\n decision: \"none\",\n rule: null,\n reason: `validation failed: ${inputResult.error}`,\n permissionMode: void 0\n });\n process.exit(0);\n}\nconst input = inputResult.data;\nif (input.hook_event_name !== \"PreToolUse\") process.exit(0);\nif (!(/* @__PURE__ */ new Set([\n \"Bash\",\n \"Read\",\n \"Write\",\n \"Edit\",\n \"Glob\",\n \"Grep\"\n])).has(input.tool_name)) process.exit(0);\nconst repoSettings = loadRepoSettings(input.cwd);\nlet decision;\nlet auditCommand;\nif (input.tool_name === \"Bash\") {\n const bashInput = parseBashInput(input.tool_input);\n if (!bashInput) {\n audit({\n sessionId: input.session_id,\n cwd: input.cwd,\n toolName: input.tool_name,\n command: `<invalid Bash input: ${JSON.stringify(input.tool_input).slice(0, 200)}>`,\n decision: \"none\",\n rule: null,\n reason: \"Bash tool_input missing or invalid 'command' field\",\n permissionMode: input.permission_mode\n });\n process.exit(0);\n }\n decision = evaluateCommand(bashInput.command, {\n cwd: input.cwd,\n depth: 0,\n repoProtectedBranches: repoSettings.protectedBranches\n });\n auditCommand = bashInput.command;\n} else {\n const projectDir = findWorkspaceRoot(input.cwd) ?? process.env[\"CLAUDE_PROJECT_DIR\"] ?? input.cwd;\n decision = evaluateToolUse(input.tool_name, input.tool_input, {\n cwd: projectDir,\n sessionId: input.session_id\n });\n const filePath = String(input.tool_input[\"file_path\"] ?? input.tool_input[\"path\"] ?? \"\");\n auditCommand = `${input.tool_name} ${filePath}`;\n}\naudit({\n sessionId: input.session_id,\n cwd: input.cwd,\n toolName: input.tool_name,\n command: auditCommand,\n decision: decision.action,\n rule: decision.rule,\n reason: decision.reason,\n permissionMode: input.permission_mode\n});\nif (decision.action === \"deny\") writeDeny(decision.reason);\nelse if (decision.advice) writeAdvice(decision.advice);\n\n//#endregion\nexport { };",
|
|
8475
|
+
hook: "/**\n * bst-hook -- Claude Code PreToolUse hook\n * @bensandee/claude-hooks\n * https://github.com/bensandee/tooling\n *\n * Auto-generated by @bensandee/tooling repo:sync -- do not edit manually.\n */\nimport * as fs from \"node:fs\";\nimport { existsSync, readFileSync, readdirSync, realpathSync, statSync } from \"node:fs\";\nimport * as path from \"node:path\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { execFileSync } from \"node:child_process\";\n\n//#region ../../node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/util.js\nfunction joinValues(array, separator = \"|\") {\n return array.map((val) => stringifyPrimitive(val)).join(separator);\n}\nfunction jsonStringifyReplacer(_, value) {\n if (typeof value === \"bigint\") return value.toString();\n return value;\n}\nvar Cached = class {\n constructor(getter) {\n this._getter = getter;\n this._value = void 0;\n }\n get value() {\n const getter = this._getter;\n if (getter !== void 0) {\n this._value = getter();\n this._getter = void 0;\n }\n return this._value;\n }\n};\nfunction cached(getter) {\n return new Cached(getter);\n}\nfunction nullish(input) {\n return input === null || input === void 0;\n}\nfunction cleanRegex(source) {\n const start = source.startsWith(\"^\") ? 1 : 0;\n const end = source.endsWith(\"$\") ? source.length - 1 : source.length;\n return source.slice(start, end);\n}\nfunction assignProp(target, prop, value) {\n Object.defineProperty(target, prop, {\n value,\n writable: true,\n enumerable: true,\n configurable: true\n });\n}\nconst captureStackTrace = \"captureStackTrace\" in Error ? Error.captureStackTrace : (..._args) => {};\nfunction isObject(data) {\n return typeof data === \"object\" && data !== null && !Array.isArray(data);\n}\nfunction isPlainObject(o) {\n if (isObject(o) === false) return false;\n const ctor = o.constructor;\n if (ctor === void 0) return true;\n if (typeof ctor !== \"function\") return true;\n const prot = ctor.prototype;\n if (isObject(prot) === false) return false;\n if (Object.prototype.hasOwnProperty.call(prot, \"isPrototypeOf\") === false) return false;\n return true;\n}\nfunction shallowClone(o) {\n if (isPlainObject(o)) return { ...o };\n if (Array.isArray(o)) return [...o];\n if (o instanceof Map) return new Map(o);\n if (o instanceof Set) return new Set(o);\n return o;\n}\nfunction clone(inst, def, params) {\n const cl = new inst._zod.constr(def ?? inst._zod.def);\n if (!def || params?.parent) cl._zod.parent = inst;\n return cl;\n}\nfunction normalizeParams(_params) {\n const params = _params;\n if (!params) return {};\n if (typeof params === \"string\") return { error: () => params };\n if (params?.message !== void 0) {\n if (params?.error !== void 0) throw new Error(\"Cannot specify both `message` and `error` params\");\n params.error = params.message;\n }\n delete params.message;\n if (typeof params.error === \"string\") return {\n ...params,\n error: () => params.error\n };\n return params;\n}\nfunction stringifyPrimitive(value) {\n if (typeof value === \"bigint\") return value.toString() + \"n\";\n if (typeof value === \"string\") return `\"${value}\"`;\n return `${value}`;\n}\nfunction optionalKeys(shape) {\n return Object.keys(shape).filter((k) => {\n return shape[k]._zod.optin !== void 0 && shape[k]._zod.optout === \"optional\";\n });\n}\nfunction aborted(x, startIndex = 0) {\n if (x.aborted === true) return true;\n for (let i = startIndex; i < x.issues.length; i++) if (x.issues[i]?.continue !== true) return true;\n return false;\n}\nfunction explicitlyAborted(x, startIndex = 0) {\n if (x.aborted === true) return true;\n for (let i = startIndex; i < x.issues.length; i++) if (x.issues[i]?.continue === false) return true;\n return false;\n}\nfunction prefixIssues(path, issues) {\n return issues.map((iss) => {\n var _a;\n (_a = iss).path ?? (_a.path = []);\n iss.path.unshift(path);\n return iss;\n });\n}\nfunction unwrapMessage(message) {\n return typeof message === \"string\" ? message : message?.message;\n}\nfunction sizeCaps(defChecks) {\n const caps = [];\n for (const ch of defChecks ?? []) {\n const d = ch._zod.def;\n if (!/_(length|size)$|_equals$/.test(d.check)) break;\n const max = d.maximum ?? d.length ?? d.size;\n if (max >= 0) caps.push([max, ch]);\n }\n return caps;\n}\nfunction attachSchema(issues, start, inst) {\n var _a;\n for (let i = start; i < issues.length; i++) (_a = issues[i]).schema ?? (_a.schema = inst);\n}\nfunction finalizeIssue(iss, ctx, config) {\n var _a;\n const traits = iss.inst?._zod?.traits;\n if (traits?.has(\"$ZodType\")) {\n if (traits.has(\"$ZodCheck\")) (_a = iss).schema ?? (_a.schema = iss.inst);\n else iss.schema = iss.inst;\n }\n const schemaError = iss.schema !== iss.inst ? iss.schema?._zod.def?.error : void 0;\n const message = iss.message ? iss.message : unwrapMessage(iss.inst?._zod.def?.error?.(iss)) ?? unwrapMessage(schemaError?.(iss)) ?? unwrapMessage(ctx?.error?.(iss)) ?? unwrapMessage(config.customError?.(iss)) ?? unwrapMessage(config.localeError?.(iss)) ?? \"Invalid input\";\n const full = {};\n for (const k of Object.keys(iss)) {\n if (k === \"inst\" || k === \"schema\" || k === \"continue\" || k === \"input\" || k === \"__proto__\") continue;\n full[k] = iss[k];\n }\n full.path ?? (full.path = []);\n full.message = message;\n if (ctx?.reportInput) full.input = iss.input;\n return full;\n}\nconst highSurrogate = /[\\uD800-\\uDBFF]/;\nfunction codePointLength(str) {\n const units = str.length;\n if (!highSurrogate.test(str)) return units;\n let count = units;\n for (let i = 0; i < units - 1; i++) if ((str.charCodeAt(i) & 64512) === 55296 && (str.charCodeAt(i + 1) & 64512) === 56320) {\n count--;\n i++;\n }\n return count;\n}\nfunction getLengthableOrigin(input) {\n if (Array.isArray(input)) return \"array\";\n if (typeof input === \"string\") return \"string\";\n return \"unknown\";\n}\nfunction parsedType(data) {\n const t = typeof data;\n switch (t) {\n case \"number\": return Number.isNaN(data) ? \"nan\" : \"number\";\n case \"object\": {\n if (data === null) return \"null\";\n if (Array.isArray(data)) return \"array\";\n const obj = data;\n if (obj && Object.getPrototypeOf(obj) !== Object.prototype && \"constructor\" in obj && obj.constructor) return obj.constructor.name;\n }\n }\n return t;\n}\nfunction members(proto, table) {\n for (const key in table) {\n const desc = Object.getOwnPropertyDescriptor(table, key);\n if (desc.get) Object.defineProperty(proto, key, {\n ...desc,\n enumerable: false\n });\n else defineBound(proto, key, desc.value);\n }\n}\nfunction own(inst, key, value, enumerable = true) {\n Object.defineProperty(inst, key, {\n configurable: true,\n writable: true,\n enumerable,\n value\n });\n return value;\n}\nfunction hide(inst, key, value) {\n return own(inst, key, value, false);\n}\nfunction defineBound(proto, key, fn) {\n Object.defineProperty(proto, key, {\n configurable: true,\n get() {\n return this == null ? fn : own(this, key, fn.bind(this));\n },\n set(value) {\n own(this, key, value);\n }\n });\n}\nfunction claim(inst, sentinel) {\n const proto = Object.getPrototypeOf(inst);\n return sentinel in proto ? void 0 : proto;\n}\nlet installing;\nlet broke = false;\nconst breaker = {\n configurable: true,\n get() {\n broke = true;\n }\n};\nfunction defineLazyInternal(inst, key, compute) {\n const proto = Object.getPrototypeOf(inst._zod);\n if (key in proto && installing !== inst._zod) {\n installing = void 0;\n return;\n }\n installing = inst._zod;\n Object.defineProperty(proto, key, {\n configurable: true,\n get() {\n Object.defineProperty(this, key, breaker);\n const outer = broke;\n broke = false;\n try {\n const value = compute(this);\n if (broke) delete this[key];\n else Object.defineProperty(this, key, {\n configurable: true,\n writable: true,\n value\n });\n broke = broke || outer;\n return value;\n } catch (err) {\n delete this[key];\n broke = broke || outer;\n throw err;\n }\n },\n set(value) {\n Object.defineProperty(this, key, {\n configurable: true,\n writable: true,\n value\n });\n }\n });\n}\nfunction installLazyProp(inst, key, make, enumerable) {\n const proto = claim(inst, key);\n if (!proto) return;\n Object.defineProperty(proto, key, {\n configurable: true,\n get() {\n const desc = {\n configurable: true,\n writable: true,\n enumerable,\n value: void 0\n };\n Object.defineProperty(this, key, desc);\n desc.value = make(this);\n Object.defineProperty(this, key, desc);\n return desc.value;\n },\n set(value) {\n Object.defineProperty(this, key, {\n configurable: true,\n writable: true,\n enumerable,\n value\n });\n }\n });\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/core.js\nvar _a;\nconst _zodDesc = {\n value: void 0,\n enumerable: false\n};\nlet _E = \"captureStackTrace\" in Error ? Error : null;\nfunction newError(Definition) {\n const E = _E;\n if (E) {\n const saved = E.stackTraceLimit;\n if (typeof saved === \"number\") {\n try {\n E.stackTraceLimit = 0;\n } catch {\n _E = null;\n return new Definition();\n }\n try {\n return new Definition();\n } finally {\n E.stackTraceLimit = saved;\n }\n }\n }\n return new Definition();\n}\n/* @__NO_SIDE_EFFECTS__ */\nfunction $constructor(name, initializer, proto, params) {\n const zodProto = {};\n function Internals(def) {\n this.def = def;\n this.constr = _;\n this.traits = /* @__PURE__ */ new Set();\n }\n Internals.prototype = zodProto;\n const protoMembers = proto;\n const initialized = protoMembers && /* @__PURE__ */ new WeakSet();\n function init(inst, def) {\n if (!inst._zod) {\n _zodDesc.value = new Internals(def);\n try {\n Object.defineProperty(inst, \"_zod\", _zodDesc);\n } finally {\n _zodDesc.value = void 0;\n }\n } else if (inst._zod.traits.has(name)) return;\n inst._zod.traits.add(name);\n initializer(inst, def);\n if (initialized) {\n const own = Object.getPrototypeOf(inst);\n const ctorProto = inst._zod.constr.prototype;\n let up = own;\n while (up && up !== ctorProto) up = Object.getPrototypeOf(up);\n const target = up ?? own;\n if (!initialized.has(target)) {\n initialized.add(target);\n members(target, protoMembers);\n }\n }\n const proto = _.prototype;\n for (const k in proto) {\n if (!Object.prototype.hasOwnProperty.call(proto, k)) continue;\n if (!(k in inst)) inst[k] = proto[k].bind(inst);\n }\n }\n const Parent = params?.Parent ?? Object;\n class Definition extends Parent {}\n Object.defineProperty(Definition, \"name\", { value: name });\n function _(def) {\n const inst = params?.Parent ? newError(Definition) : this;\n init(inst, def);\n const deferred = inst._zod.deferred;\n if (deferred) {\n for (const fn of deferred) fn();\n inst._zod.deferred = void 0;\n }\n const pp = globalThis.__zod_globalConfig?.postProcessor;\n if (pp) pp(inst);\n return inst;\n }\n Object.defineProperty(_, \"init\", { value: init });\n Object.defineProperty(_, Symbol.hasInstance, { value: (inst) => {\n if (params?.Parent && inst instanceof params.Parent) return true;\n return inst?._zod?.traits?.has(name);\n } });\n Object.defineProperty(_, \"name\", { value: name });\n return _;\n}\nvar $ZodAsyncError = class extends Error {\n constructor() {\n super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`);\n }\n};\n(_a = globalThis).__zod_globalConfig ?? (_a.__zod_globalConfig = {});\nconst globalConfig = globalThis.__zod_globalConfig;\nfunction config(newConfig) {\n if (newConfig) Object.assign(globalConfig, newConfig);\n return globalConfig;\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/errors.js\nfunction _getMessage() {\n const internals = this._zod;\n internals.message ?? (internals.message = JSON.stringify(internals.def, jsonStringifyReplacer, 2));\n return internals.message;\n}\nfunction _setMessage(value) {\n this._zod.message = value;\n}\nconst _messageDesc = {\n get: _getMessage,\n set: _setMessage,\n enumerable: true,\n configurable: true\n};\nconst _issuesDesc = {\n value: void 0,\n enumerable: false\n};\nconst _installedToString = /* @__PURE__ */ new WeakSet([Object.prototype, Error.prototype]);\nconst initializer = (inst, def) => {\n inst.name = \"$ZodError\";\n _issuesDesc.value = def;\n Object.defineProperty(inst, \"issues\", _issuesDesc);\n _issuesDesc.value = void 0;\n Object.defineProperty(inst, \"message\", _messageDesc);\n const proto = Object.getPrototypeOf(inst);\n if (!_installedToString.has(proto)) {\n _installedToString.add(proto);\n Object.defineProperty(proto, \"toString\", {\n configurable: true,\n enumerable: false,\n get() {\n const value = () => this.message;\n Object.defineProperty(this, \"toString\", {\n value,\n configurable: true,\n writable: true\n });\n return value;\n },\n set(value) {\n Object.defineProperty(this, \"toString\", {\n value,\n configurable: true,\n writable: true\n });\n }\n });\n }\n};\nconst $ZodRealError = /* @__PURE__ */ $constructor(\"$ZodError\", initializer, void 0, { Parent: Error });\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/parse.js\nconst _parse = (_Err) => {\n const fn = (schema, value, _ctx, _params) => {\n const ctx = _ctx ? {\n ..._ctx,\n async: false\n } : { async: false };\n const result = schema._zod.run({\n value,\n issues: []\n }, ctx);\n if (result instanceof Promise) throw new $ZodAsyncError();\n if (result.issues.length) {\n const e = new ((_params?.Err) ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));\n captureStackTrace(e, _params?.callee ?? fn);\n throw e;\n }\n return result.value;\n };\n return fn;\n};\nconst parse = /* @__PURE__*/ _parse($ZodRealError);\nconst _parseAsync = (_Err) => {\n const fn = async (schema, value, _ctx, params) => {\n const ctx = _ctx ? {\n ..._ctx,\n async: true\n } : { async: true };\n let result = schema._zod.run({\n value,\n issues: []\n }, ctx);\n if (result instanceof Promise) result = await result;\n if (result.issues.length) {\n const e = new ((params?.Err) ?? _Err)(result.issues.map((iss) => finalizeIssue(iss, ctx, config())));\n captureStackTrace(e, params?.callee ?? fn);\n throw e;\n }\n return result.value;\n };\n return fn;\n};\nconst parseAsync = /* @__PURE__*/ _parseAsync($ZodRealError);\nconst _safeParse = (_Err) => (schema, value, _ctx) => {\n const ctx = _ctx ? {\n ..._ctx,\n async: false\n } : { async: false };\n const result = schema._zod.run({\n value,\n issues: []\n }, ctx);\n if (result instanceof Promise) throw new $ZodAsyncError();\n return result.issues.length ? failure(_Err, result.issues, ctx) : {\n success: true,\n data: result.value\n };\n};\nconst safeParse = /* @__PURE__*/ _safeParse($ZodRealError);\nfunction failure(Err, issues, ctx) {\n let error;\n return {\n success: false,\n get error() {\n if (!error) {\n error = new Err(issues.map((iss) => finalizeIssue(iss, ctx, config())));\n issues = void 0;\n ctx = void 0;\n }\n return error;\n },\n set error(e) {\n error = e;\n issues = void 0;\n ctx = void 0;\n }\n };\n}\nconst _safeParseAsync = (_Err) => async (schema, value, _ctx) => {\n const ctx = _ctx ? {\n ..._ctx,\n async: true\n } : { async: true };\n let result = schema._zod.run({\n value,\n issues: []\n }, ctx);\n if (result instanceof Promise) result = await result;\n return result.issues.length ? failure(_Err, result.issues, ctx) : {\n success: true,\n data: result.value\n };\n};\nconst safeParseAsync = /* @__PURE__*/ _safeParseAsync($ZodRealError);\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/regexes.js\nconst anyString = /^[\\s\\S]{0,}$/;\nconst number = /^-?\\d+(?:\\.\\d+)?$/;\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/checks.js\nconst $ZodCheck = /*@__PURE__*/ $constructor(\"$ZodCheck\", (inst, def) => {\n var _a;\n inst._zod ?? (inst._zod = {});\n inst._zod.def = def;\n (_a = inst._zod).onattach ?? (_a.onattach = []);\n});\nconst _whenHasLength = (payload) => {\n const val = payload.value;\n return !nullish(val) && val.length !== void 0;\n};\nconst $ZodCheckMinLength = /*@__PURE__*/ $constructor(\"$ZodCheckMinLength\", (inst, def) => {\n var _a;\n $ZodCheck.init(inst, def);\n (_a = inst._zod.def).when ?? (_a.when = _whenHasLength);\n inst._zod.check = (payload) => {\n const input = payload.value;\n const units = input.length;\n if ((typeof input === \"string\" && units >= def.minimum && units < def.minimum * 2 ? codePointLength(input) : units) >= def.minimum) return;\n const origin = getLengthableOrigin(input);\n payload.issues.push({\n origin,\n code: \"too_small\",\n minimum: def.minimum,\n inclusive: true,\n input,\n inst,\n continue: !def.abort\n });\n };\n});\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/versions.js\nconst version = {\n major: 4,\n minor: 6,\n patch: 7\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/schemas.js\nconst $ZodType = /*@__PURE__*/ $constructor(\"$ZodType\", (inst, def) => {\n var _a;\n inst ?? (inst = {});\n inst._zod.def = def;\n inst._zod.bag = inst._zod.bag || {};\n inst._zod.version = version;\n const defChecks = inst._zod.def.checks;\n const checks = inst._zod.traits.has(\"$ZodCheck\") ? [inst, ...defChecks ?? []] : defChecks?.length ? [...defChecks] : [];\n for (const ch of checks) for (const fn of ch._zod.onattach) fn(inst);\n if (checks.length === 0) {\n (_a = inst._zod).deferred ?? (_a.deferred = []);\n inst._zod.deferred?.push(() => {\n inst._zod.run = inst._zod.parse;\n });\n } else {\n const runChecks = (payload, checks, ctx) => {\n if (payload.memo) return payload;\n let isAborted = aborted(payload);\n let asyncResult;\n for (const ch of checks) {\n if (ch._zod.def.when) {\n if (explicitlyAborted(payload)) continue;\n if (!ch._zod.def.when(payload)) continue;\n } else if (isAborted) continue;\n const currLen = payload.issues.length;\n const _ = ch._zod.check(payload);\n if (_ instanceof Promise && ctx?.async === false) throw new $ZodAsyncError();\n if (asyncResult || _ instanceof Promise) asyncResult = (asyncResult ?? Promise.resolve()).then(async () => {\n await _;\n if (payload.issues.length === currLen) return;\n attachSchema(payload.issues, currLen, inst);\n if (!isAborted) isAborted = aborted(payload, currLen);\n });\n else {\n if (payload.issues.length === currLen) continue;\n attachSchema(payload.issues, currLen, inst);\n if (!isAborted) isAborted = aborted(payload, currLen);\n }\n }\n if (asyncResult) return asyncResult.then(() => {\n return payload;\n });\n return payload;\n };\n const handleCanaryResult = (canary, payload, ctx) => {\n if (aborted(canary)) {\n canary.aborted = true;\n return canary;\n }\n const checkResult = runChecks(payload, checks, ctx);\n if (checkResult instanceof Promise) {\n if (ctx.async === false) throw new $ZodAsyncError();\n return checkResult.then((checkResult) => inst._zod.parse(checkResult, ctx));\n }\n return inst._zod.parse(checkResult, ctx);\n };\n inst._zod.run = (payload, ctx) => {\n if (ctx.skipChecks) return inst._zod.parse(payload, ctx);\n if (ctx.direction === \"backward\") {\n const canary = inst._zod.parse({\n value: payload.value,\n issues: []\n }, {\n ...ctx,\n skipChecks: true\n });\n if (canary instanceof Promise) return canary.then((canary) => {\n return handleCanaryResult(canary, payload, ctx);\n });\n return handleCanaryResult(canary, payload, ctx);\n }\n const result = inst._zod.parse(payload, ctx);\n if (result instanceof Promise) {\n if (ctx.async === false) throw new $ZodAsyncError();\n return result.then((result) => runChecks(result, checks, ctx));\n }\n return runChecks(result, checks, ctx);\n };\n }\n}, {\n get \"~standard\"() {\n return hide(this, \"~standard\", standardProps(this));\n },\n set \"~standard\"(value) {\n own(this, \"~standard\", value);\n }\n});\nconst toStandardResult = (r, ctx) => r.issues.length ? { issues: r.issues.map((iss) => finalizeIssue(iss, ctx, config())) } : { value: r.value };\nasync function validateAsync(inst, value) {\n const ctx = { async: true };\n return toStandardResult(await inst._zod.run({\n value,\n issues: []\n }, ctx), ctx);\n}\nfunction standardProps(inst) {\n return {\n validate: (value) => {\n const ctx = { async: false };\n try {\n const r = inst._zod.run({\n value,\n issues: []\n }, ctx);\n if (!(r instanceof Promise)) return toStandardResult(r, ctx);\n } catch (_) {}\n return validateAsync(inst, value);\n },\n vendor: \"zod\",\n version: 1\n };\n}\nconst $ZodString = /*@__PURE__*/ $constructor(\"$ZodString\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.pattern = def.pattern ?? anyString;\n inst._zod.parse = (payload, _) => {\n if (def.coerce) try {\n payload.value = String(payload.value);\n } catch (_) {}\n if (typeof payload.value === \"string\") return payload;\n payload.issues.push({\n expected: \"string\",\n code: \"invalid_type\",\n input: payload.value,\n inst\n });\n return payload;\n };\n});\nconst $ZodUnknown = /*@__PURE__*/ $constructor(\"$ZodUnknown\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.parse = (payload) => payload;\n});\nfunction capSize(inst, size) {\n const caps = sizeCaps(inst._zod.def.checks);\n if (!caps.length) return;\n const cap = Math.min(...caps.map((c) => c[0]));\n const parse = inst._zod.parse;\n inst._zod.parse = (payload, ctx) => {\n const n = size(payload.value);\n if (n > cap && !ctx?.skipChecks && ctx?.direction !== \"backward\") {\n for (const [max, ch] of sizeCaps(inst._zod.def.checks)) if (n > max && ch._zod.def.when(payload)) {\n const start = payload.issues.length;\n ch._zod.check(payload);\n payload.issues[start].continue = false;\n attachSchema(payload.issues, start, inst);\n return payload;\n }\n }\n return parse(payload, ctx);\n };\n}\nconst arraySize = (input) => Array.isArray(input) ? input.length : -1;\nfunction handleArrayResult(result, final, index) {\n if (result.issues.length) final.issues.push(...prefixIssues(index, result.issues));\n final.value[index] = result.value;\n}\nconst $ZodArray = /*@__PURE__*/ $constructor(\"$ZodArray\", (inst, def) => {\n $ZodType.init(inst, def);\n const memo = globalConfig.memoizer;\n memo?.attach(inst);\n inst._zod.parse = (payload, ctx) => {\n const input = payload.value;\n if (!Array.isArray(input)) {\n payload.issues.push({\n expected: \"array\",\n code: \"invalid_type\",\n input,\n inst\n });\n return payload;\n }\n payload.value = memo ? memo.alloc(inst, payload, Array(input.length), ctx) : Array(input.length);\n const proms = [];\n const abortEarly = ctx?.abortEarly;\n for (let i = 0; i < input.length; i++) {\n const item = input[i];\n const result = def.element._zod.run({\n value: item,\n issues: []\n }, ctx);\n if (result instanceof Promise) proms.push(result.then((result) => handleArrayResult(result, payload, i)));\n else {\n handleArrayResult(result, payload, i);\n if (abortEarly && result.issues.length !== 0 && aborted(result)) break;\n }\n }\n if (proms.length) return Promise.all(proms).then(() => payload);\n return payload;\n };\n capSize(inst, arraySize);\n});\nfunction handlePropertyResult(result, final, key, input, optin, optout) {\n const isPresent = key in input;\n const isOptionalOut = optout === \"optional\";\n if (!isPresent && isOptionalOut && optin === \"optional\") return;\n if (result.issues.length) {\n if (optin !== void 0 && isOptionalOut && !isPresent) return;\n final.issues.push(...prefixIssues(key, result.issues));\n }\n if (!isPresent && optin === void 0) {\n if (!result.issues.length) final.issues.push({\n code: \"invalid_type\",\n expected: \"nonoptional\",\n input: void 0,\n path: [key]\n });\n return;\n }\n if (result.value === void 0) {\n if (isPresent || optin === \"defaulted\" && !isOptionalOut) final.value[key] = void 0;\n } else final.value[key] = result.value;\n}\nconst NO_SYMBOL_KEYS = [];\nfunction normalizeDef(def) {\n const keys = Object.keys(def.shape);\n const ownSymbols = Object.getOwnPropertySymbols(def.shape);\n const symbolKeys = ownSymbols.length ? ownSymbols : NO_SYMBOL_KEYS;\n const allKeys = symbolKeys.length ? [...keys, ...symbolKeys] : keys;\n for (const k of allKeys) if (!def.shape?.[k]?._zod?.traits?.has(\"$ZodType\")) throw new Error(`Invalid element at key \"${String(k)}\": expected a Zod schema`);\n const okeys = optionalKeys(def.shape);\n return {\n ...def,\n allKeys,\n symbolKeys,\n keySet: new Set(keys),\n numKeys: keys.length,\n optionalKeys: new Set(okeys)\n };\n}\nfunction handleCatchall(proms, input, payload, ctx, def, inst, abortEarly) {\n const unrecognized = [];\n const keySet = def.keySet;\n const _catchall = def.catchall._zod;\n const t = _catchall.def.type;\n const optin = _catchall.optin;\n const optout = _catchall.optout;\n let seen = 0;\n for (const key in input) {\n if (abortEarly && payload.issues.length !== seen) {\n if (aborted(payload, seen)) break;\n seen = payload.issues.length;\n }\n if (keySet.has(key)) continue;\n if (key === \"__proto__\") {\n if (t === \"never\") unrecognized.push(key);\n continue;\n }\n if (t === \"never\") {\n unrecognized.push(key);\n continue;\n }\n const r = _catchall.run({\n value: input[key],\n issues: []\n }, ctx);\n if (r instanceof Promise) proms.push(r.then((r) => handlePropertyResult(r, payload, key, input, optin, optout)));\n else handlePropertyResult(r, payload, key, input, optin, optout);\n }\n if (unrecognized.length) payload.issues.push({\n code: \"unrecognized_keys\",\n keys: unrecognized,\n input,\n inst,\n continue: true\n });\n if (!proms.length) return payload;\n return Promise.all(proms).then(() => {\n return payload;\n });\n}\nconst $ZodObject = /*@__PURE__*/ $constructor(\"$ZodObject\", (inst, def) => {\n $ZodType.init(inst, def);\n const desc = Object.getOwnPropertyDescriptor(def, \"shape\");\n const sh = desc?.get ? desc.get.raw : def.shape ?? {};\n if (sh) {\n const get = () => {\n const newSh = { ...sh };\n Object.defineProperty(def, \"shape\", { value: newSh });\n get.raw = newSh;\n return newSh;\n };\n get.raw = sh;\n Object.defineProperty(def, \"shape\", { get });\n }\n const _normalized = cached(() => normalizeDef(def));\n defineLazyInternal(inst, \"propValues\", (zod) => {\n const shape = zod.def.shape;\n const propValues = {};\n for (const key in shape) {\n const field = shape[key]._zod;\n if (field.values) {\n if (!Object.prototype.hasOwnProperty.call(propValues, key)) assignProp(propValues, key, /* @__PURE__ */ new Set());\n for (const v of field.values) propValues[key].add(v);\n if (field.optin !== void 0) propValues[key].add(void 0);\n }\n }\n return propValues;\n });\n const isObject$1 = isObject;\n const catchall = def.catchall;\n let value;\n const memo = globalConfig.memoizer;\n memo?.attach(inst);\n inst._zod.parse = (payload, ctx) => {\n value ?? (value = _normalized.value);\n const input = payload.value;\n if (!isObject$1(input)) {\n payload.issues.push({\n expected: \"object\",\n code: \"invalid_type\",\n input,\n inst\n });\n return payload;\n }\n payload.value = memo ? memo.alloc(inst, payload, {}, ctx) : {};\n const proms = [];\n const shape = value.shape;\n const abortEarly = ctx?.abortEarly;\n let seen = payload.issues.length;\n for (const key of value.allKeys) {\n if (abortEarly && payload.issues.length !== seen) {\n if (aborted(payload, seen)) break;\n seen = payload.issues.length;\n }\n if (key === \"__proto__\") continue;\n const el = shape[key];\n const optin = el._zod.optin;\n const optout = el._zod.optout;\n const r = el._zod.run({\n value: input[key],\n issues: []\n }, ctx);\n if (r instanceof Promise) proms.push(r.then((r) => handlePropertyResult(r, payload, key, input, optin, optout)));\n else handlePropertyResult(r, payload, key, input, optin, optout);\n }\n if (!catchall) return proms.length ? Promise.all(proms).then(() => payload) : payload;\n return handleCatchall(proms, input, payload, ctx, _normalized.value, inst, abortEarly === true);\n };\n});\nconst $ZodRecord = /*@__PURE__*/ $constructor(\"$ZodRecord\", (inst, def) => {\n $ZodType.init(inst, def);\n const memo = globalConfig.memoizer;\n memo?.attach(inst);\n inst._zod.parse = (payload, ctx) => {\n const input = payload.value;\n if (!isPlainObject(input)) {\n payload.issues.push({\n expected: \"record\",\n code: \"invalid_type\",\n input,\n inst\n });\n return payload;\n }\n const proms = [];\n const values = def.keyType._zod.values;\n if (values && !def.partial) {\n payload.value = memo ? memo.alloc(inst, payload, {}, ctx) : {};\n const recordKeys = /* @__PURE__ */ new Set();\n for (const key of values) if (typeof key === \"string\" || typeof key === \"number\" || typeof key === \"symbol\") {\n recordKeys.add(typeof key === \"number\" ? key.toString() : key);\n if (key === \"__proto__\") continue;\n const keyResult = def.keyType._zod.run({\n value: key,\n issues: []\n }, ctx);\n if (keyResult instanceof Promise) throw new Error(\"Async schemas not supported in object keys currently\");\n if (keyResult.issues.length) {\n payload.issues.push({\n code: \"invalid_key\",\n origin: \"record\",\n issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),\n input: key,\n path: [key],\n inst\n });\n continue;\n }\n const outKey = keyResult.value;\n if (outKey === \"__proto__\") continue;\n const result = def.valueType._zod.run({\n value: input[key],\n issues: []\n }, ctx);\n if (result instanceof Promise) proms.push(result.then((result) => {\n if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));\n payload.value[outKey] = result.value;\n }));\n else {\n if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));\n payload.value[outKey] = result.value;\n }\n }\n let unrecognized;\n for (const key in input) if (!recordKeys.has(key)) {\n if (def.mode === \"loose\") {\n if (key === \"__proto__\") continue;\n payload.value[key] = input[key];\n } else {\n unrecognized = unrecognized ?? [];\n unrecognized.push(key);\n }\n }\n if (unrecognized && unrecognized.length > 0) payload.issues.push({\n code: \"unrecognized_keys\",\n input,\n inst,\n keys: unrecognized,\n continue: true\n });\n } else {\n payload.value = memo ? memo.alloc(inst, payload, {}, ctx) : {};\n let unrecognized;\n for (const key of Reflect.ownKeys(input)) {\n if (key === \"__proto__\") continue;\n if (!Object.prototype.propertyIsEnumerable.call(input, key)) continue;\n let keyResult = def.keyType._zod.run({\n value: key,\n issues: []\n }, ctx);\n if (keyResult instanceof Promise) throw new Error(\"Async schemas not supported in object keys currently\");\n if (typeof key === \"string\" && number.test(key) && keyResult.issues.length) {\n const retryResult = def.keyType._zod.run({\n value: Number(key),\n issues: []\n }, ctx);\n if (retryResult instanceof Promise) throw new Error(\"Async schemas not supported in object keys currently\");\n if (retryResult.issues.length === 0) keyResult = retryResult;\n }\n if (keyResult.issues.length) {\n if (def.mode === \"loose\") payload.value[key] = input[key];\n else if (values) {\n unrecognized = unrecognized ?? [];\n unrecognized.push(key);\n } else payload.issues.push({\n code: \"invalid_key\",\n origin: \"record\",\n issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),\n input: key,\n path: [key],\n inst\n });\n continue;\n }\n const outKey = keyResult.value;\n if (outKey === \"__proto__\") continue;\n const result = def.valueType._zod.run({\n value: input[key],\n issues: []\n }, ctx);\n if (result instanceof Promise) proms.push(result.then((result) => {\n if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));\n payload.value[outKey] = result.value;\n }));\n else {\n if (result.issues.length) payload.issues.push(...prefixIssues(key, result.issues));\n payload.value[outKey] = result.value;\n }\n }\n if (unrecognized && unrecognized.length > 0) payload.issues.push({\n code: \"unrecognized_keys\",\n input,\n inst,\n keys: unrecognized,\n continue: true\n });\n }\n if (proms.length) return Promise.all(proms).then(() => payload);\n return payload;\n };\n});\nfunction handleOptionalResult(payload, result) {\n payload.value = result.issues.length ? void 0 : result.value;\n return payload;\n}\nconst $ZodOptional = /*@__PURE__*/ $constructor(\"$ZodOptional\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazyInternal(inst, \"optin\", (zod) => zod.def.innerType._zod.optin === \"defaulted\" ? \"defaulted\" : \"optional\");\n inst._zod.optout = \"optional\";\n defineLazyInternal(inst, \"values\", (zod) => {\n const values = zod.def.innerType._zod.values;\n return values ? /* @__PURE__ */ new Set([...values, void 0]) : void 0;\n });\n defineLazyInternal(inst, \"pattern\", (zod) => {\n const pattern = zod.def.innerType._zod.pattern;\n return pattern ? new RegExp(`^(${cleanRegex(pattern.source)})?$`) : void 0;\n });\n inst._zod.parse = (payload, ctx) => {\n if (payload.value === void 0) {\n if (def.innerType._zod.optin !== \"defaulted\") return payload;\n const result = def.innerType._zod.run({\n value: payload.value,\n issues: []\n }, ctx);\n if (result instanceof Promise) return result.then((result) => handleOptionalResult(payload, result));\n return handleOptionalResult(payload, result);\n }\n return def.innerType._zod.run(payload, ctx);\n };\n});\nconst $ZodDefault = /*@__PURE__*/ $constructor(\"$ZodDefault\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.optin = \"defaulted\";\n defineLazyInternal(inst, \"values\", (zod) => zod.def.innerType._zod.values);\n inst._zod.parse = (payload, ctx) => {\n if (ctx.direction === \"backward\") return def.innerType._zod.run(payload, ctx);\n if (payload.value === void 0) {\n payload.value = def.defaultValue;\n return payload;\n }\n const result = def.innerType._zod.run(payload, ctx);\n if (result instanceof Promise) return result.then((result) => handleDefaultResult(result, def));\n return handleDefaultResult(result, def);\n };\n});\nfunction handleDefaultResult(payload, def) {\n if (payload.value === void 0) payload.value = def.defaultValue;\n return payload;\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/locales/en.js\nconst error = () => {\n const Sizable = {\n string: {\n unit: \"characters\",\n verb: \"to have\"\n },\n file: {\n unit: \"bytes\",\n verb: \"to have\"\n },\n array: {\n unit: \"items\",\n verb: \"to have\"\n },\n set: {\n unit: \"items\",\n verb: \"to have\"\n },\n map: {\n unit: \"entries\",\n verb: \"to have\"\n }\n };\n function getSizing(origin) {\n return Sizable[origin] ?? null;\n }\n const FormatDictionary = {\n regex: \"input\",\n email: \"email address\",\n url: \"URL\",\n emoji: \"emoji\",\n uuid: \"UUID\",\n uuidv4: \"UUIDv4\",\n uuidv6: \"UUIDv6\",\n nanoid: \"nanoid\",\n guid: \"GUID\",\n cuid: \"cuid\",\n cuid2: \"cuid2\",\n ulid: \"ULID\",\n xid: \"XID\",\n ksuid: \"KSUID\",\n datetime: \"ISO datetime\",\n date: \"ISO date\",\n time: \"ISO time\",\n duration: \"ISO duration\",\n ipv4: \"IPv4 address\",\n ipv6: \"IPv6 address\",\n mac: \"MAC address\",\n cidrv4: \"IPv4 range\",\n cidrv6: \"IPv6 range\",\n base64: \"base64-encoded string\",\n base64url: \"base64url-encoded string\",\n json_string: \"JSON string\",\n e164: \"E.164 number\",\n currency_code: \"currency code\",\n credit_card: \"credit card number\",\n iban: \"IBAN\",\n jwt: \"JWT\",\n template_literal: \"input\"\n };\n const TypeDictionary = { nan: \"NaN\" };\n function getTypeName(type, input) {\n if (type === \"number\" && typeof input === \"number\" && !Number.isFinite(input)) return String(input);\n return TypeDictionary[type] ?? type;\n }\n return (issue) => {\n switch (issue.code) {\n case \"invalid_type\": return `Invalid input: expected ${getTypeName(issue.expected)}, received ${getTypeName(parsedType(issue.input), issue.input)}`;\n case \"invalid_value\":\n if (issue.values.length === 1) return `Invalid input: expected ${stringifyPrimitive(issue.values[0])}`;\n return `Invalid option: expected one of ${joinValues(issue.values, \"|\")}`;\n case \"too_big\": {\n const adj = issue.exact ? \"exactly \" : issue.inclusive ? \"<=\" : \"<\";\n const sizing = getSizing(issue.origin);\n if (sizing) return `Too big: expected ${issue.origin ?? \"value\"} to have ${adj}${issue.maximum.toString()} ${sizing.unit ?? \"elements\"}`;\n return `Too big: expected ${issue.origin ?? \"value\"} to be ${adj}${issue.maximum.toString()}`;\n }\n case \"too_small\": {\n const adj = issue.exact ? \"exactly \" : issue.inclusive ? \">=\" : \">\";\n const sizing = getSizing(issue.origin);\n if (sizing) return `Too small: expected ${issue.origin} to have ${adj}${issue.minimum.toString()} ${sizing.unit}`;\n return `Too small: expected ${issue.origin} to be ${adj}${issue.minimum.toString()}`;\n }\n case \"invalid_format\": {\n const _issue = issue;\n if (_issue.format === \"starts_with\") return `Invalid string: must start with \"${_issue.prefix}\"`;\n if (_issue.format === \"ends_with\") return `Invalid string: must end with \"${_issue.suffix}\"`;\n if (_issue.format === \"includes\") return `Invalid string: must include \"${_issue.includes}\"`;\n if (_issue.format === \"regex\") return `Invalid string: must match pattern ${_issue.pattern}`;\n return `Invalid ${FormatDictionary[_issue.format] ?? issue.format}`;\n }\n case \"not_multiple_of\": return `Invalid number: must be a multiple of ${issue.divisor}`;\n case \"unrecognized_keys\": return `Unrecognized key${issue.keys.length > 1 ? \"s\" : \"\"}: ${joinValues(issue.keys, \", \")}`;\n case \"invalid_key\": return `Invalid key in ${issue.origin}`;\n case \"invalid_union\":\n if (issue.options && Array.isArray(issue.options) && issue.options.length > 0) return `Invalid discriminator value. Expected ${issue.options.map((o) => `'${o}'`).join(\" | \")}`;\n if (issue.inclusive === false) return \"Invalid input: more than one option matched\";\n return \"Invalid input\";\n case \"invalid_element\": return `Invalid value in ${issue.origin}`;\n default: return `Invalid input`;\n }\n };\n};\nfunction en_default() {\n return { localeError: error() };\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/core/api.js\nfunction snapshotChecks(def) {\n if (def.checks) def.checks = [...def.checks];\n return def;\n}\n// @__NO_SIDE_EFFECTS__\nfunction _string(Class, params) {\n return new Class(snapshotChecks({\n type: \"string\",\n ...normalizeParams(params)\n }));\n}\n// @__NO_SIDE_EFFECTS__\nfunction _unknown(Class) {\n return new Class({ type: \"unknown\" });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _minLength(minimum, params) {\n return new $ZodCheckMinLength({\n check: \"min_length\",\n ...normalizeParams(params),\n minimum\n });\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.7/node_modules/zod/v4/mini/schemas.js\nconst ZodMiniType = /*@__PURE__*/ $constructor(\"ZodMiniType\", (inst, def) => {\n if (!inst._zod) throw new Error(\"Uninitialized schema in ZodMiniType.\");\n $ZodType.init(inst, def);\n inst.def = def;\n inst.type = def.type;\n}, {\n get with() {\n return this.check;\n },\n set with(value) {\n own(this, \"with\", value);\n },\n parse(data, params) {\n return parse(this, data, params, { callee: this.parse });\n },\n parseAsync(data, params) {\n return parseAsync(this, data, params, { callee: this.parseAsync });\n },\n safeParse(data, params) {\n return safeParse(this, data, params);\n },\n safeParseAsync(data, params) {\n return safeParseAsync(this, data, params);\n },\n check(...checks) {\n const def = this.def;\n return this.clone({\n ...def,\n checks: [...def.checks ?? [], ...checks.map((ch) => typeof ch === \"function\" ? { _zod: {\n check: ch,\n def: { check: \"custom\" },\n onattach: []\n } } : ch)]\n }, { parent: true });\n },\n clone(_def, params) {\n return clone(this, _def, params);\n },\n brand() {\n return this;\n },\n register(reg, meta) {\n reg.add(this, meta);\n return this;\n },\n apply(fn, ...args) {\n return args.length === 0 ? fn(this) : fn(this, ...args);\n }\n});\nconst ZodMiniString = /*@__PURE__*/ $constructor(\"ZodMiniString\", (inst, def) => {\n $ZodString.init(inst, def);\n ZodMiniType.init(inst, def);\n});\n// @__NO_SIDE_EFFECTS__\nfunction string(params) {\n return _string(ZodMiniString, params);\n}\nconst ZodMiniUnknown = /*@__PURE__*/ $constructor(\"ZodMiniUnknown\", (inst, def) => {\n $ZodUnknown.init(inst, def);\n ZodMiniType.init(inst, def);\n});\n// @__NO_SIDE_EFFECTS__\nfunction unknown() {\n return _unknown(ZodMiniUnknown);\n}\nconst ZodMiniArray = /*@__PURE__*/ $constructor(\"ZodMiniArray\", (inst, def) => {\n $ZodArray.init(inst, def);\n ZodMiniType.init(inst, def);\n});\n// @__NO_SIDE_EFFECTS__\nfunction array(element, params) {\n return new ZodMiniArray({\n type: \"array\",\n element,\n ...normalizeParams(params)\n });\n}\nconst ZodMiniObject = /*@__PURE__*/ $constructor(\"ZodMiniObject\", (inst, def) => {\n $ZodObject.init(inst, def);\n ZodMiniType.init(inst, def);\n installLazyProp(inst, \"shape\", (self) => self._zod.def.shape, false);\n});\n// @__NO_SIDE_EFFECTS__\nfunction object(shape, params) {\n const def = {\n type: \"object\",\n shape: shape ?? {},\n ...normalizeParams(params)\n };\n return new ZodMiniObject(def);\n}\nconst ZodMiniRecord = /*@__PURE__*/ $constructor(\"ZodMiniRecord\", (inst, def) => {\n $ZodRecord.init(inst, def);\n ZodMiniType.init(inst, def);\n});\n// @__NO_SIDE_EFFECTS__\nfunction record(keyType, valueType, params) {\n if (!valueType || !valueType._zod) return new ZodMiniRecord({\n type: \"record\",\n keyType: /* @__PURE__ */ string(),\n valueType: keyType,\n ...normalizeParams(valueType)\n });\n return new ZodMiniRecord({\n type: \"record\",\n keyType,\n valueType,\n ...normalizeParams(params)\n });\n}\nconst ZodMiniOptional = /*@__PURE__*/ $constructor(\"ZodMiniOptional\", (inst, def) => {\n $ZodOptional.init(inst, def);\n ZodMiniType.init(inst, def);\n});\n// @__NO_SIDE_EFFECTS__\nfunction optional(innerType) {\n return new ZodMiniOptional({\n type: \"optional\",\n innerType\n });\n}\nconst ZodMiniDefault = /*@__PURE__*/ $constructor(\"ZodMiniDefault\", (inst, def) => {\n $ZodDefault.init(inst, def);\n ZodMiniType.init(inst, def);\n});\n// @__NO_SIDE_EFFECTS__\nfunction _default(innerType, defaultValue) {\n return new ZodMiniDefault({\n type: \"default\",\n innerType,\n get defaultValue() {\n return typeof defaultValue === \"function\" ? defaultValue() : shallowClone(defaultValue);\n }\n });\n}\n\n//#endregion\n//#region src/lib/hook-io.ts\nconfig(en_default());\nconst HookInputSchema = object({\n session_id: string(),\n transcript_path: string(),\n cwd: string(),\n permission_mode: string(),\n hook_event_name: string(),\n tool_name: string(),\n tool_input: record(string(), unknown())\n});\nasync function readHookInput() {\n const chunks = [];\n for await (const chunk of process.stdin) chunks.push(typeof chunk === \"string\" ? chunk : String(chunk));\n let parsed;\n try {\n parsed = JSON.parse(chunks.join(\"\"));\n } catch (_error) {\n return {\n ok: false,\n raw: chunks.join(\"\").slice(0, 200),\n error: \"invalid JSON\"\n };\n }\n const result = HookInputSchema.safeParse(parsed);\n if (!result.success) return {\n ok: false,\n raw: parsed,\n error: result.error.message\n };\n return {\n ok: true,\n data: result.data\n };\n}\nconst BashInputSchema = object({\n command: string(),\n description: optional(string())\n});\nconst FilePathInputSchema = object({ file_path: string() });\nconst SearchInputSchema = object({\n pattern: string(),\n path: optional(string())\n});\nfunction parseBashInput(toolInput) {\n const result = BashInputSchema.safeParse(toolInput);\n return result.success ? result.data : null;\n}\nfunction parseFilePathInput(toolInput) {\n const result = FilePathInputSchema.safeParse(toolInput);\n return result.success ? result.data : null;\n}\nfunction parseSearchInput(toolInput) {\n const result = SearchInputSchema.safeParse(toolInput);\n return result.success ? result.data : null;\n}\nfunction writeDeny(reason) {\n const decision = { hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"deny\",\n ...reason ? { permissionDecisionReason: reason } : {}\n } };\n process.stdout.write(JSON.stringify(decision));\n}\nfunction writeAdvice(advice) {\n const decision = { hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n additionalContext: advice\n } };\n process.stdout.write(JSON.stringify(decision));\n}\n\n//#endregion\n//#region src/lib/audit.ts\nconst MAX_LOG_SIZE = 1048576;\nfunction getLogPath() {\n const hookDir = path.dirname(fileURLToPath(import.meta.url));\n return path.join(hookDir, \"bst-hook.audit.local.jsonl\");\n}\nfunction rotateIfNeeded(logPath) {\n try {\n if (fs.statSync(logPath).size > MAX_LOG_SIZE) {\n const rotatedPath = `${logPath}.1`;\n fs.renameSync(logPath, rotatedPath);\n }\n } catch (_error) {}\n}\nlet rotationChecked = false;\nfunction audit(entry) {\n try {\n const logPath = getLogPath();\n if (!rotationChecked) {\n rotateIfNeeded(logPath);\n rotationChecked = true;\n }\n const record = {\n timestamp: (/* @__PURE__ */ new Date()).toISOString(),\n ...entry\n };\n fs.appendFileSync(logPath, JSON.stringify(record) + \"\\n\");\n } catch (err) {\n process.stderr.write(`[bst-hook] audit write failed: ${err instanceof Error ? err.message : String(err)}\\n`);\n }\n}\n\n//#endregion\n//#region src/lib/shell-tokenizer.ts\nfunction scanDoubleQuoted(input, start) {\n let content = \"\";\n let i = start;\n while (i < input.length && input[i] !== \"\\\"\") {\n if (input[i] === \"\\\\\" && i + 1 < input.length) {\n const next = input[i + 1];\n if (next === \"\\\"\" || next === \"\\\\\" || next === \"$\" || next === \"`\") {\n content += next;\n i += 2;\n continue;\n }\n }\n content += input[i];\n i++;\n }\n if (i >= input.length) return null;\n return {\n content,\n next: i + 1\n };\n}\nfunction tokenize(input) {\n const tokens = [];\n let current = \"\";\n let i = 0;\n let inToken = false;\n while (i < input.length) {\n const ch = input[i];\n if (ch === \"'\") {\n inToken = true;\n i++;\n while (i < input.length && input[i] !== \"'\") {\n current += input[i];\n i++;\n }\n if (i >= input.length) return {\n ok: false,\n error: \"unterminated single quote\"\n };\n i++;\n } else if (ch === \"\\\"\") {\n inToken = true;\n const scanned = scanDoubleQuoted(input, i + 1);\n if (!scanned) return {\n ok: false,\n error: \"unterminated double quote\"\n };\n current += scanned.content;\n i = scanned.next;\n } else if (ch === \"\\\\\" && i + 1 < input.length) {\n inToken = true;\n current += input[i + 1];\n i += 2;\n } else if (ch === \" \" || ch === \" \") {\n if (inToken) {\n tokens.push(current);\n current = \"\";\n inToken = false;\n }\n i++;\n } else {\n inToken = true;\n current += ch;\n i++;\n }\n }\n if (inToken) tokens.push(current);\n return {\n ok: true,\n tokens\n };\n}\nconst SAFE_UNQUOTED_RE = /^[A-Za-z0-9_\\-./=:@%+,]+$/;\nfunction quoteToken(token) {\n if (token.length > 0 && SAFE_UNQUOTED_RE.test(token)) return token;\n return \"'\" + token.replace(/'/g, \"'\\\\''\") + \"'\";\n}\nfunction joinTokens(tokens) {\n return tokens.map(quoteToken).join(\" \");\n}\n\n//#endregion\n//#region src/lib/shell-splitter.ts\nfunction splitCommand(input) {\n if (!input || !input.trim()) return [];\n const segments = [];\n let current = \"\";\n let i = 0;\n let inSingleQuote = false;\n let inDoubleQuote = false;\n while (i < input.length) {\n const ch = input[i];\n if (ch === \"'\" && !inDoubleQuote) {\n inSingleQuote = !inSingleQuote;\n current += ch;\n i++;\n continue;\n }\n if (ch === \"\\\"\" && !inSingleQuote) {\n inDoubleQuote = !inDoubleQuote;\n current += ch;\n i++;\n continue;\n }\n if (ch === \"\\\\\" && !inSingleQuote && i + 1 < input.length) {\n current += ch + input[i + 1];\n i += 2;\n continue;\n }\n if (!inSingleQuote && !inDoubleQuote) {\n if (ch === \"&\" && i + 1 < input.length && input[i + 1] === \"&\") {\n pushSegment(segments, current, \"&&\");\n current = \"\";\n i += 2;\n continue;\n }\n if (ch === \"&\" && !(i + 1 < input.length && input[i + 1] === \">\") && !(i > 0 && (input[i - 1] === \">\" || input[i - 1] === \"<\"))) {\n pushSegment(segments, current, \"&\");\n current = \"\";\n i++;\n continue;\n }\n if (ch === \"|\" && i + 1 < input.length && input[i + 1] === \"|\") {\n pushSegment(segments, current, \"||\");\n current = \"\";\n i += 2;\n continue;\n }\n if (ch === \"|\") {\n pushSegment(segments, current, \"|\");\n current = \"\";\n i++;\n continue;\n }\n if (ch === \";\") {\n pushSegment(segments, current, \";\");\n current = \"\";\n i++;\n continue;\n }\n }\n current += ch;\n i++;\n }\n const trimmed = current.trim();\n if (trimmed) segments.push({\n command: trimmed,\n operator: null\n });\n return segments;\n}\nfunction pushSegment(segments, text, operator) {\n const trimmed = text.trim();\n if (trimmed) segments.push({\n command: trimmed,\n operator\n });\n}\n\n//#endregion\n//#region src/lib/injection-guard.ts\nfunction hasBraceExpansion(token) {\n let depth = 0;\n for (let i = 0; i < token.length; i++) {\n const ch = token[i];\n if (ch === \"{\") depth++;\n else if (ch === \"}\") {\n if (depth > 0) depth--;\n } else if (depth >= 1 && (ch === \",\" || ch === \".\" && token[i + 1] === \".\")) return true;\n }\n return false;\n}\nfunction checkInjection(tokens, rawSegment) {\n if (/[\\n\\r]/.test(rawSegment)) return {\n safe: false,\n reason: \"unquoted newline in command\"\n };\n for (const token of tokens) {\n if (token === \"`\") return {\n safe: false,\n reason: \"backtick command substitution\"\n };\n if (token.includes(\"`\")) return {\n safe: false,\n reason: \"backtick command substitution\"\n };\n if (token.includes(\"$\")) return {\n safe: false,\n reason: \"unresolved dollar-expansion\"\n };\n if (hasBraceExpansion(token)) return {\n safe: false,\n reason: \"brace expansion\"\n };\n if (token.includes(\"<(\")) return {\n safe: false,\n reason: \"process substitution\"\n };\n if (token.includes(\">(\")) return {\n safe: false,\n reason: \"process substitution\"\n };\n }\n return { safe: true };\n}\n\n//#endregion\n//#region src/lib/env-file.ts\nconst ENV_SAFE_SUFFIXES = [\n \".sample\",\n \".example\",\n \".template\"\n];\nconst ENV_TOOL_OWNED_SUFFIXES = [\".check.env\"];\nconst ENV_FILE_PATTERN = /(^|[/\\\\])(\\.env(\\..*)?|.*\\.env(\\..*)?|\\.env)$/;\nfunction isEnvSafeSuffix(filename) {\n return [...ENV_SAFE_SUFFIXES, ...ENV_TOOL_OWNED_SUFFIXES].some((suffix) => filename.endsWith(suffix));\n}\nfunction isEnvFile(filePath) {\n if (isEnvSafeSuffix(path.basename(filePath))) return false;\n return ENV_FILE_PATTERN.test(filePath);\n}\n\n//#endregion\n//#region src/lib/git-state.ts\nconst GIT_TIMEOUT_MS = 5e3;\nfunction getCurrentBranch(cwd) {\n try {\n const out = execFileSync(\"git\", [\n \"rev-parse\",\n \"--abbrev-ref\",\n \"HEAD\"\n ], {\n cwd,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ],\n timeout: GIT_TIMEOUT_MS\n }).toString().trim();\n if (!out || out === \"HEAD\") return null;\n return out;\n } catch {\n return null;\n }\n}\nfunction getRepoTopLevel(cwd) {\n try {\n const out = execFileSync(\"git\", [\"rev-parse\", \"--show-toplevel\"], {\n cwd,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ],\n timeout: GIT_TIMEOUT_MS\n }).toString().trim();\n return out === \"\" ? null : out;\n } catch {\n return null;\n }\n}\nfunction getRemoteDefaultBranch(cwd) {\n try {\n const out = execFileSync(\"git\", [\n \"symbolic-ref\",\n \"--short\",\n \"refs/remotes/origin/HEAD\"\n ], {\n cwd,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ],\n timeout: GIT_TIMEOUT_MS\n }).toString().trim();\n if (!out) return null;\n return out.startsWith(\"origin/\") ? out.slice(7) : out;\n } catch {\n return null;\n }\n}\nconst DefaultBranchSchema = object({ defaultBranch: optional(string().check(_minLength(1))) });\nfunction getConfiguredDefaultBranch(topLevel) {\n let parsed;\n try {\n parsed = JSON.parse(readFileSync(join(topLevel, \".tooling.json\"), \"utf-8\"));\n } catch {\n return null;\n }\n const result = DefaultBranchSchema.safeParse(parsed);\n return result.success ? result.data.defaultBranch ?? null : null;\n}\nconst ALWAYS_PROTECTED = [\"main\", \"master\"];\nfunction getProtectedBranches(cwd, extra) {\n const set = new Set(ALWAYS_PROTECTED);\n const remote = getRemoteDefaultBranch(cwd);\n if (remote) set.add(remote);\n for (const b of extra) set.add(b);\n return set;\n}\nfunction getUpstream(cwd, branch) {\n try {\n return execFileSync(\"git\", [\n \"rev-parse\",\n \"--abbrev-ref\",\n `${branch}@{upstream}`\n ], {\n cwd,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ],\n timeout: 5e3\n }).toString().trim() || null;\n } catch {\n return null;\n }\n}\nfunction resolveCommit(cwd, rev) {\n try {\n return execFileSync(\"git\", [\n \"rev-parse\",\n \"--verify\",\n \"--quiet\",\n `${rev}^{commit}`\n ], {\n cwd,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ],\n timeout: 5e3\n }).toString().trim() || null;\n } catch {\n return null;\n }\n}\nfunction branchCarriesOtherFiles(cwd, allowed) {\n const toplevel = getRepoTopLevel(cwd);\n if (toplevel === null) return null;\n try {\n const defaultBranch = getConfiguredDefaultBranch(toplevel) ?? getRemoteDefaultBranch(toplevel) ?? \"main\";\n const mergeBase = execFileSync(\"git\", [\n \"merge-base\",\n \"HEAD\",\n `origin/${defaultBranch}`\n ], {\n cwd: toplevel,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ],\n timeout: GIT_TIMEOUT_MS\n }).toString().trim();\n if (!mergeBase) return null;\n const names = (args) => execFileSync(\"git\", args, {\n cwd: toplevel,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ],\n timeout: GIT_TIMEOUT_MS\n }).toString().split(\"\\n\").filter((line) => line.length > 0);\n return [...names([\n \"diff\",\n \"--name-only\",\n mergeBase\n ]), ...names([\n \"ls-files\",\n \"--others\",\n \"--exclude-standard\"\n ])].some((name) => !allowed.includes(name));\n } catch {\n return null;\n }\n}\nfunction isPrimaryWorktree(cwd) {\n try {\n const gitDir = execFileSync(\"git\", [\"rev-parse\", \"--absolute-git-dir\"], {\n cwd,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ],\n timeout: GIT_TIMEOUT_MS\n }).toString().trim();\n const commonRaw = execFileSync(\"git\", [\"rev-parse\", \"--git-common-dir\"], {\n cwd,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ],\n timeout: GIT_TIMEOUT_MS\n }).toString().trim();\n if (!gitDir || !commonRaw) return false;\n const commonDir = resolve(cwd, commonRaw);\n return realpathSync(gitDir) === realpathSync(commonDir);\n } catch {\n return false;\n }\n}\n\n//#endregion\n//#region src/lib/git-short-options.ts\nconst VALUE_TAKING_LETTERS = {\n switch: /* @__PURE__ */ new Set([\"c\", \"C\"]),\n checkout: /* @__PURE__ */ new Set([\"b\", \"B\"]),\n branch: /* @__PURE__ */ new Set([\"u\"]),\n fetch: /* @__PURE__ */ new Set([\"o\", \"j\"])\n};\nfunction splitShortOptionCluster(token, sub) {\n if (!token.startsWith(\"-\") || token.startsWith(\"--\") || token.length <= 1) return;\n const valueLetters = VALUE_TAKING_LETTERS[sub];\n const letters = [];\n const chars = token.slice(1);\n for (let i = 0; i < chars.length; i++) {\n const ch = chars[i];\n letters.push(ch);\n if (valueLetters.has(ch)) return {\n letters,\n attachedValue: chars.slice(i + 1)\n };\n }\n return { letters };\n}\n\n//#endregion\n//#region src/lib/path-scope.ts\nfunction pathFlavorFor(base) {\n return /^[a-zA-Z]:[\\\\/]/.test(base) ? path.win32 : path.posix;\n}\nfunction resolveIn(base, target) {\n return pathFlavorFor(base).resolve(base, target);\n}\nfunction isWithin(dir, target) {\n const pp = pathFlavorFor(dir);\n const rel = pp.relative(dir, target);\n if (rel === \"\") return true;\n if (rel === \"..\" || rel.startsWith(\"..\" + pp.sep)) return false;\n return !pp.isAbsolute(rel);\n}\nfunction samePath(a, b) {\n return pathFlavorFor(a).relative(a, b) === \"\";\n}\nfunction targetsGitDir(filePath, cwd) {\n const pp = pathFlavorFor(cwd);\n const resolved = resolveIn(cwd, filePath);\n if (!isWithin(cwd, resolved)) return false;\n const rel = pp.relative(cwd, resolved);\n if (rel === \"\") return false;\n return rel.split(pp.sep).includes(\".git\");\n}\nfunction targetsNodeModules(filePath, cwd) {\n return resolveIn(cwd, filePath).split(/[/\\\\]/).includes(\"node_modules\");\n}\n\n//#endregion\n//#region src/rules.ts\nconst GIT_FLAGS_WITH_ARG = /* @__PURE__ */ new Set([\n \"-C\",\n \"-c\",\n \"--git-dir\",\n \"--work-tree\"\n]);\nfunction getGitSubcommand(tokens) {\n const args = tokens.slice(1);\n for (let i = 0; i < args.length; i++) {\n const t = args[i];\n if (GIT_FLAGS_WITH_ARG.has(t)) {\n i++;\n continue;\n }\n if (t.startsWith(\"-\")) continue;\n return t;\n }\n}\nconst WORKTREE_ADVICE = \"You're creating a feature branch in the primary worktree. An isolated worktree keeps this work independent of other windows, and with no commits on this branch yet, converting is cheap -- `git worktree add ../<dir> -b <branch>` (a sibling outside the repo, with its own dependency install). Claude Code keeps this session inside its own working tree, so the work continues in a new session opened in the sibling worktree. If you cannot open a session there, stay in the primary checkout on this branch and check `git branch --show-current` before each commit.\";\nfunction isGitBranchCreation(tokens) {\n const sub = getGitSubcommand(tokens);\n if (sub !== \"checkout\" && sub !== \"switch\") return false;\n const after = tokens.slice(tokens.indexOf(sub) + 1);\n const creationLetters = sub === \"checkout\" ? [\"b\", \"B\"] : [\"c\", \"C\"];\n return after.some((t) => {\n const cluster = splitShortOptionCluster(t, sub);\n return cluster !== void 0 && cluster.letters.some((l) => creationLetters.includes(l));\n });\n}\nfunction worktreeAdvice(tokens, ctx) {\n return isGitBranchCreation(tokens) && isPrimaryWorktree(ctx.cwd) ? WORKTREE_ADVICE : void 0;\n}\nconst GIT_FORCE_CREATE_LETTER = {\n switch: \"C\",\n checkout: \"B\"\n};\nconst GIT_FORCE_CREATE_LONG = {\n flag: \"--force-create\",\n shortest: \"--force-\"\n};\nfunction gitForceCreateTargets(after, letterSub) {\n const letter = GIT_FORCE_CREATE_LETTER[letterSub];\n const longFlag = GIT_FORCE_CREATE_LONG;\n const targets = [];\n for (let i = 0; i < after.length; i++) {\n const t = after[i];\n if (t === void 0) continue;\n if (letterSub === \"switch\") {\n const [flag, ...rest] = t.split(\"=\");\n if (flag !== void 0 && flag.length >= longFlag.shortest.length && longFlag.flag.startsWith(flag)) {\n const target = rest.length > 0 ? rest.join(\"=\") : after[i + 1];\n if (target !== void 0) targets.push(target);\n continue;\n }\n }\n const cluster = splitShortOptionCluster(t, letterSub);\n if (cluster === void 0 || !cluster.letters.includes(letter)) continue;\n if (cluster.attachedValue !== void 0 && cluster.attachedValue !== \"\") {\n targets.push(cluster.attachedValue);\n continue;\n }\n const next = after[i + 1];\n if (next !== void 0) targets.push(next);\n }\n return targets;\n}\nfunction gitCommandForceCreateTargets(tokens) {\n const sub = getGitSubcommand(tokens);\n if (sub === \"switch\" || sub === \"checkout\") return gitForceCreateTargets(tokens.slice(tokens.indexOf(sub) + 1), sub);\n if (sub === \"worktree\") {\n const worktreeIdx = tokens.indexOf(\"worktree\");\n if (tokens[worktreeIdx + 1] !== \"add\") return [];\n return gitForceCreateTargets(tokens.slice(worktreeIdx + 2), \"checkout\");\n }\n return [];\n}\nfunction isGitProtectedBranchForceCreate(tokens, ctx) {\n const targets = gitCommandForceCreateTargets(tokens);\n if (targets.length === 0) return false;\n const protectedSet = getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []);\n return targets.some((t) => protectedSet.has(t));\n}\nfunction isGitProtectedBranchRefWrite(tokens, ctx) {\n const sub = getGitSubcommand(tokens);\n if (sub !== \"branch\") return false;\n const { flags, positionals } = splitGitBranchArgs(tokens.slice(tokens.indexOf(sub) + 1));\n if (positionals.length === 0) return false;\n let deleteMode = false;\n let renameMode = false;\n let copyMode = false;\n let forceMode = false;\n for (const flag of flags) {\n if (flag.letters !== void 0) {\n if (flag.letters.includes(\"d\") || flag.letters.includes(\"D\")) deleteMode = true;\n if (flag.letters.includes(\"m\") || flag.letters.includes(\"M\")) renameMode = true;\n if (flag.letters.includes(\"c\") || flag.letters.includes(\"C\")) copyMode = true;\n if (flag.letters.includes(\"f\") || flag.letters.includes(\"M\") || flag.letters.includes(\"C\")) forceMode = true;\n continue;\n }\n if (flag.name === \"delete\") deleteMode = true;\n if (flag.name === \"move\") renameMode = true;\n if (flag.name === \"copy\") copyMode = true;\n if (flag.name === \"force\") forceMode = true;\n }\n const protectedSet = getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []);\n if (deleteMode && positionals.some((p) => protectedSet.has(p))) return true;\n if (renameMode && positionals.length >= 2 && protectedSet.has(positionals[0])) return true;\n if (forceMode) {\n const destination = renameMode || copyMode ? positionals[positionals.length - 1] : positionals[0];\n if (destination !== void 0 && protectedSet.has(destination)) return true;\n }\n return false;\n}\nfunction refspecDestination(refspec) {\n const spec = refspec.startsWith(\"+\") ? refspec.slice(1) : refspec;\n const colon = spec.lastIndexOf(\":\");\n return (colon === -1 ? spec : spec.slice(colon + 1)).replace(/^refs\\/heads\\//, \"\");\n}\nconst GIT_BRANCH_VALUE_FLAG_NAMES = /* @__PURE__ */ new Set([\n \"contains\",\n \"no-contains\",\n \"merged\",\n \"no-merged\",\n \"points-at\",\n \"sort\",\n \"format\",\n \"set-upstream-to\"\n]);\nfunction splitGitBranchArgs(after) {\n const flags = [];\n const positionals = [];\n for (let i = 0; i < after.length; i++) {\n const t = after[i];\n if (t === void 0) continue;\n if (!t.startsWith(\"-\")) {\n positionals.push(t);\n continue;\n }\n if (t.startsWith(\"--\") && t !== \"--\") {\n const eq = t.indexOf(\"=\");\n const name = eq > 0 ? t.slice(2, eq) : t.slice(2);\n flags.push({\n raw: t,\n name\n });\n if (eq < 0 && GIT_BRANCH_VALUE_FLAG_NAMES.has(name)) i++;\n continue;\n }\n const cluster = splitShortOptionCluster(t, \"branch\");\n if (cluster === void 0) {\n flags.push({ raw: t });\n continue;\n }\n flags.push({\n raw: t,\n letters: cluster.letters\n });\n if (cluster.attachedValue === \"\") i++;\n }\n return {\n flags,\n positionals\n };\n}\nconst GIT_FETCH_VALUE_FLAG_NAMES = /* @__PURE__ */ new Set([\n \"depth\",\n \"deepen\",\n \"shallow-since\",\n \"shallow-exclude\",\n \"negotiation-tip\",\n \"filter\",\n \"jobs\",\n \"server-option\",\n \"upload-pack\",\n \"refmap\",\n \"submodule-prefix\",\n \"recurse-submodules-default\"\n]);\nfunction splitGitFetchArgs(after) {\n const flags = [];\n const positionals = [];\n const refmaps = [];\n for (let i = 0; i < after.length; i++) {\n const t = after[i];\n if (t === void 0) continue;\n if (!t.startsWith(\"-\")) {\n positionals.push(t);\n continue;\n }\n if (t === \"--\") {\n flags.push({ raw: t });\n continue;\n }\n if (t.startsWith(\"--\")) {\n const eq = t.indexOf(\"=\");\n const name = eq > 0 ? t.slice(2, eq) : t.slice(2);\n flags.push({\n raw: t,\n name\n });\n if (name === \"refmap\") {\n if (eq > 0) refmaps.push(t.slice(eq + 1));\n else if (after[i + 1] !== void 0) refmaps.push(after[i + 1]);\n }\n if (eq < 0 && GIT_FETCH_VALUE_FLAG_NAMES.has(name)) i++;\n continue;\n }\n const cluster = splitShortOptionCluster(t, \"fetch\");\n if (cluster === void 0) {\n flags.push({ raw: t });\n continue;\n }\n flags.push({\n raw: t,\n letters: cluster.letters\n });\n if (cluster.attachedValue === \"\") i++;\n }\n return {\n flags,\n positionals,\n refmaps\n };\n}\nfunction fetchRefspecTarget(spec) {\n const body = spec.startsWith(\"+\") ? spec.slice(1) : spec;\n if (body.startsWith(\"^\")) return { kind: \"none\" };\n if (!body.includes(\":\")) return { kind: \"none\" };\n const destination = refspecDestination(body);\n if (destination === \"\") return { kind: \"none\" };\n if (destination.startsWith(\"refs/remotes/\")) return { kind: \"tracking\" };\n if (destination.startsWith(\"refs/\")) return { kind: \"other\" };\n return {\n kind: \"branch\",\n name: destination\n };\n}\nfunction globToAnchoredRegex(glob) {\n const escaped = glob.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n return new RegExp(`^${escaped.replace(/\\\\\\*/g, \".*\")}$`);\n}\nfunction isProtectedFetchBranch(name, protectedSet) {\n if (protectedSet.has(name)) return true;\n if (!name.includes(\"*\")) return false;\n const pattern = globToAnchoredRegex(name);\n for (const member of protectedSet) if (pattern.test(member)) return true;\n return false;\n}\nfunction isGitProtectedBranchFetchWrite(tokens, ctx) {\n const sub = getGitSubcommand(tokens);\n if (sub !== \"fetch\") return false;\n const { positionals, refmaps } = splitGitFetchArgs(tokens.slice(tokens.indexOf(sub) + 1));\n const protectedSet = getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []);\n return [...positionals, ...refmaps].some((spec) => {\n const target = fetchRefspecTarget(spec);\n if (target.kind !== \"branch\" || target.name === void 0) return false;\n return isProtectedFetchBranch(target.name, protectedSet);\n });\n}\nfunction isGitProtectedUpdateRef(tokens, ctx) {\n const after = tokens.slice(tokens.indexOf(\"update-ref\") + 1);\n let ref;\n for (let i = 0; i < after.length; i++) {\n const t = after[i];\n if (t === \"-m\") {\n i++;\n continue;\n }\n if (t.startsWith(\"-\")) continue;\n ref = t;\n break;\n }\n if (ref === void 0) return false;\n return getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []).has(ref.replace(/^refs\\/heads\\//, \"\"));\n}\nfunction gitEffectiveCwd(tokens, cwd) {\n let dir = cwd;\n const args = tokens.slice(1);\n for (let i = 0; i < args.length; i++) {\n const t = args[i];\n if (t === \"-C\") {\n const next = args[i + 1];\n if (next !== void 0) dir = path.resolve(dir, next);\n i++;\n continue;\n }\n if (GIT_FLAGS_WITH_ARG.has(t)) {\n i++;\n continue;\n }\n if (!t.startsWith(\"-\")) break;\n }\n return dir;\n}\nconst GIT_SWITCH_NEUTRAL_FLAGS = /* @__PURE__ */ new Set([\n \"-q\",\n \"--quiet\",\n \"-f\",\n \"--force\",\n \"--discard-changes\",\n \"-m\",\n \"--merge\",\n \"--no-track\",\n \"--progress\",\n \"--no-progress\",\n \"--guess\",\n \"--no-guess\",\n \"--ignore-other-worktrees\",\n \"--recurse-submodules\",\n \"--no-recurse-submodules\"\n]);\nconst GIT_SWITCH_CREATE_FLAGS = /* @__PURE__ */ new Set([\n \"--create\",\n \"--force-create\",\n \"--orphan\"\n]);\nconst GIT_CHECKOUT_PATH_FLAGS = /* @__PURE__ */ new Set([\n \"--ours\",\n \"--theirs\",\n \"-p\",\n \"--patch\",\n \"--conflict\",\n \"--pathspec-from-file\",\n \"--pathspec-file-nul\",\n \"--overlay\",\n \"--no-overlay\"\n]);\nconst GIT_SWITCH_NEUTRAL_LETTERS = /* @__PURE__ */ new Set([\n \"q\",\n \"f\",\n \"m\"\n]);\nfunction switchTarget(after, sub, isCheckoutBranch) {\n if (sub === \"checkout\" && after.includes(\"--\")) return void 0;\n const createLetters = sub === \"checkout\" ? [\"b\", \"B\"] : [\"c\", \"C\"];\n const known = (l) => GIT_SWITCH_NEUTRAL_LETTERS.has(l) || createLetters.includes(l);\n const positionals = [];\n let created;\n for (let i = 0; i < after.length; i++) {\n const t = after[i];\n if (!t.startsWith(\"-\")) {\n positionals.push(t);\n continue;\n }\n if (GIT_SWITCH_NEUTRAL_FLAGS.has(t)) continue;\n const eq = t.indexOf(\"=\");\n if (sub === \"checkout\" && GIT_CHECKOUT_PATH_FLAGS.has(eq === -1 ? t : t.slice(0, eq))) return;\n if (GIT_SWITCH_CREATE_FLAGS.has(eq === -1 ? t : t.slice(0, eq))) {\n created = eq === -1 ? after[++i] : t.slice(eq + 1);\n continue;\n }\n const cluster = splitShortOptionCluster(t, sub);\n if (cluster === void 0 || !cluster.letters.every(known)) return null;\n if (cluster.letters.some((l) => createLetters.includes(l))) created = cluster.attachedValue ? cluster.attachedValue : after[++i];\n }\n if (created !== void 0) return created;\n if (positionals.length !== 1) return void 0;\n const target = positionals[0];\n if (target === \"HEAD\" || target === \"@\") return void 0;\n if (sub === \"checkout\" && !isCheckoutBranch(target)) return void 0;\n return target;\n}\nfunction branchSwitch(tokens, ctx, resolved) {\n const sub = getGitSubcommand(tokens);\n if (sub !== \"switch\" && sub !== \"checkout\") return void 0;\n const dir = gitEffectiveCwd(tokens, ctx.cwd);\n const isCheckoutBranch = (name) => !resolved || getProtectedBranches(dir, ctx.repoProtectedBranches ?? []).has(name) || resolveCommit(dir, name) !== null;\n const target = switchTarget(tokens.slice(tokens.indexOf(sub) + 1), sub, isCheckoutBranch);\n if (!resolved) return target === void 0 ? void 0 : {\n dir,\n branch: null\n };\n return target === void 0 ? void 0 : {\n dir,\n branch: target\n };\n}\nconst GIT_CHECKED_OUT_WRITES = /* @__PURE__ */ new Set([\n \"commit\",\n \"merge\",\n \"rebase\",\n \"cherry-pick\",\n \"revert\",\n \"am\",\n \"pull\"\n]);\nfunction subcommandArgs(tokens, sub, valueFlags = /* @__PURE__ */ new Set()) {\n const after = tokens.slice(tokens.indexOf(sub) + 1);\n const flags = [];\n const positionals = [];\n for (let i = 0; i < after.length; i++) {\n const t = after[i];\n if (!t.startsWith(\"-\")) {\n positionals.push(t);\n continue;\n }\n flags.push(t);\n if (valueFlags.has(t)) i++;\n }\n return {\n flags,\n positionals\n };\n}\nconst GIT_PUSH_VALUE_FLAGS = /* @__PURE__ */ new Set([\n \"-o\",\n \"--push-option\",\n \"--repo\",\n \"--receive-pack\",\n \"--exec\"\n]);\nfunction isMergeFastForwardFromUpstream(tokens, upstream) {\n const { flags, positionals } = subcommandArgs(tokens, \"merge\");\n if (!flags.includes(\"--ff-only\") || positionals.length !== 1) return false;\n const ref = positionals[0];\n return ref === \"@{u}\" || ref === \"@{upstream}\" || upstream !== null && ref === upstream;\n}\nconst GIT_PULL_NO_REBASE = /* @__PURE__ */ new Set([\n \"--rebase=false\",\n \"--rebase=no\",\n \"--rebase=off\",\n \"--rebase=0\"\n]);\nfunction isPullFromUpstream(tokens, upstream) {\n const { flags, positionals } = subcommandArgs(tokens, \"pull\");\n if (flags.some((t) => t.startsWith(\"--rebase\") && !GIT_PULL_NO_REBASE.has(t) || t === \"--no-ff\" || !t.startsWith(\"--\") && t.slice(1).includes(\"r\"))) return false;\n if (positionals.length === 0) return true;\n if (positionals.length !== 2 || upstream === null) return false;\n const [remote, branch] = positionals;\n return `${remote}/${branch.replace(/^refs\\/heads\\//, \"\")}` === upstream;\n}\nfunction isRefMovingReset(tokens, cwd, head) {\n if (tokens.slice(tokens.indexOf(\"reset\") + 1).includes(\"--\")) return false;\n const { positionals } = subcommandArgs(tokens, \"reset\");\n if (positionals.length !== 1) return false;\n if (head !== \"HEAD\" && /^(HEAD|@)/.test(positionals[0])) return false;\n const target = resolveCommit(cwd, positionals[0]);\n if (target === null) return false;\n return target !== resolveCommit(cwd, head);\n}\nfunction pushDestinations(tokens, current) {\n const { positionals } = subcommandArgs(tokens, \"push\", GIT_PUSH_VALUE_FLAGS);\n const refspecs = positionals.slice(1);\n if (refspecs.length === 0) return null;\n const names = [];\n for (const spec of refspecs) {\n const dst = refspecDestination(spec);\n if (dst === \"HEAD\" || dst === \"@\") {\n if (current !== null) names.push(current);\n continue;\n }\n names.push(dst);\n }\n return names;\n}\nconst GIT_PUSH_EVERY_BRANCH = /* @__PURE__ */ new Set([\n \"--all\",\n \"--branches\",\n \"--mirror\"\n]);\nfunction leavesRefInPlace(tokens, sub) {\n const { flags } = subcommandArgs(tokens, sub);\n if (flags.includes(\"--abort\") || flags.includes(\"--quit\")) return true;\n return sub === \"commit\" && flags.includes(\"--dry-run\");\n}\nfunction movesProtectedRef(tokens, ctx) {\n const sub = getGitSubcommand(tokens);\n if (sub === void 0) return false;\n if (sub === \"switch\" || sub === \"checkout\" || sub === \"worktree\") return isGitProtectedBranchForceCreate(tokens, ctx);\n if (sub === \"branch\") return isGitProtectedBranchRefWrite(tokens, ctx);\n if (sub === \"fetch\") return isGitProtectedBranchFetchWrite(tokens, ctx);\n if (sub === \"update-ref\") return isGitProtectedUpdateRef(tokens, ctx);\n if (sub !== \"push\" && sub !== \"reset\" && !GIT_CHECKED_OUT_WRITES.has(sub)) return false;\n const cwd = gitEffectiveCwd(tokens, ctx.cwd);\n const pending = ctx.pendingBranches?.[cwd];\n const current = pending ? pending.branch : getCurrentBranch(cwd);\n const protectedSet = getProtectedBranches(cwd, ctx.repoProtectedBranches ?? []);\n const onProtected = current !== null && protectedSet.has(current);\n if (sub === \"push\") {\n const { flags } = subcommandArgs(tokens, \"push\", GIT_PUSH_VALUE_FLAGS);\n if (flags.some((f) => GIT_PUSH_EVERY_BRANCH.has(f))) return true;\n const destinations = pushDestinations(tokens, current);\n if (destinations === null) {\n if (flags.includes(\"--tags\")) return false;\n return onProtected;\n }\n return destinations.some((d) => protectedSet.has(d));\n }\n if (!onProtected) return false;\n if (leavesRefInPlace(tokens, sub)) return false;\n if (sub === \"reset\") return isRefMovingReset(tokens, cwd, pending ? current : \"HEAD\");\n if (sub === \"merge\" || sub === \"pull\") {\n const upstream = getUpstream(cwd, current);\n if (sub === \"merge\" && isMergeFastForwardFromUpstream(tokens, upstream)) return false;\n if (sub === \"pull\" && isPullFromUpstream(tokens, upstream)) return false;\n }\n return true;\n}\nconst PNPM_BUILTINS = /* @__PURE__ */ new Set([\n \"install\",\n \"i\",\n \"add\",\n \"remove\",\n \"rm\",\n \"uninstall\",\n \"un\",\n \"update\",\n \"up\",\n \"upgrade\",\n \"list\",\n \"ls\",\n \"ll\",\n \"la\",\n \"why\",\n \"view\",\n \"info\",\n \"show\",\n \"outdated\",\n \"audit\",\n \"licenses\",\n \"init\",\n \"create\",\n \"link\",\n \"unlink\",\n \"pack\",\n \"publish\",\n \"config\",\n \"set\",\n \"get\",\n \"store\",\n \"cache\",\n \"rebuild\",\n \"rb\",\n \"prune\",\n \"dedupe\",\n \"dlx\",\n \"exec\",\n \"run\",\n \"patch\",\n \"patch-commit\",\n \"recursive\",\n \"-r\"\n]);\nconst NPM_READ_OPS = /* @__PURE__ */ new Set([\n \"view\",\n \"info\",\n \"show\"\n]);\nfunction isNpmReadOp(tokens) {\n const sub = tokens[1];\n return sub !== void 0 && NPM_READ_OPS.has(sub);\n}\nfunction getNpmRedirectReason(tokens) {\n const sub = tokens[1];\n const BASE = \"This project uses pnpm.\";\n if (!sub) return `${BASE} Use \\`pnpm\\` instead of npm.`;\n if (sub === \"install\" || sub === \"i\" || sub === \"ci\") {\n if (tokens.slice(2).some((t) => !t.startsWith(\"-\"))) return `${BASE} Use \\`pnpm add <pkg>\\` instead of \\`npm install <pkg>\\`.`;\n return `${BASE} Use \\`pnpm install\\` instead of npm.`;\n }\n if (sub === \"add\") return `${BASE} Use \\`pnpm add <pkg>\\` instead of npm.`;\n if (sub === \"uninstall\" || sub === \"remove\" || sub === \"rm\" || sub === \"un\" || sub === \"r\") return `${BASE} Use \\`pnpm remove <pkg>\\` instead of npm.`;\n if (sub === \"run\" || sub === \"run-script\") return `${BASE} Use \\`pnpm run <script>\\` or \\`pnpm <script>\\` instead of npm.`;\n if (sub === \"test\" || sub === \"t\" || sub === \"tst\") return `${BASE} Use \\`pnpm test\\` instead of npm.`;\n if (sub === \"start\") return `${BASE} Use \\`pnpm start\\` instead of npm.`;\n if (sub === \"stop\") return `${BASE} Use \\`pnpm stop\\` instead of npm.`;\n if (sub === \"restart\") return `${BASE} Use \\`pnpm restart\\` instead of npm.`;\n if (sub === \"exec\") return `${BASE} Use \\`pnpm exec <cmd>\\` instead of npm.`;\n if (sub === \"list\" || sub === \"ls\" || sub === \"ll\" || sub === \"la\") return `${BASE} Use \\`pnpm list\\` instead of npm.`;\n if (sub === \"why\") return `${BASE} Use \\`pnpm why\\` instead of npm.`;\n if (sub === \"outdated\") return `${BASE} Use \\`pnpm outdated\\` instead of npm.`;\n if (sub === \"audit\") return `${BASE} Use \\`pnpm audit\\` instead of npm.`;\n if (sub === \"update\" || sub === \"up\" || sub === \"upgrade\") return `${BASE} Use \\`pnpm update\\` instead of npm.`;\n if (sub === \"init\") return `${BASE} Use \\`pnpm init\\` instead of npm.`;\n if (sub === \"publish\") return `${BASE} Use \\`pnpm publish\\` instead of npm.`;\n if (sub === \"link\") return `${BASE} Use \\`pnpm link\\` instead of npm.`;\n if (sub === \"unlink\") return `${BASE} Use \\`pnpm unlink\\` instead of npm.`;\n if (sub === \"pack\") return `${BASE} Use \\`pnpm pack\\` instead of npm.`;\n if (sub === \"cache\") return `${BASE} Use \\`pnpm store\\` instead of \\`npm cache\\`.`;\n if (sub === \"config\" || sub === \"set\" || sub === \"get\") return `${BASE} Use \\`pnpm config\\` instead of npm.`;\n return `${BASE} Use the equivalent pnpm command instead of npm.`;\n}\nconst LAST_POSITIONAL_DEST = /* @__PURE__ */ new Set([\n \"cp\",\n \"mv\",\n \"rsync\",\n \"install\",\n \"ln\"\n]);\nconst ALL_POSITIONAL_DEST = /* @__PURE__ */ new Set([\"tee\", \"truncate\"]);\nfunction explicitTargetDirectory(rest) {\n for (const [i, t] of rest.entries()) {\n if (t === \"-t\" || t === \"--target-directory\") return rest[i + 1] ?? null;\n if (t.startsWith(\"--target-directory=\")) return t.slice(19);\n }\n return null;\n}\nconst OUTPUT_REDIRECT = /^(?:\\d*|&)>{1,2}$/;\nconst OUTPUT_REDIRECT_ATTACHED = /^(?:\\d*|&)>{1,2}(.+)$/;\nconst INPUT_REDIRECT = /^\\d*<$/;\nconst INPUT_REDIRECT_ATTACHED = /^\\d*<([^<].*)$/;\nfunction spacedRedirectDirection(token) {\n if (OUTPUT_REDIRECT.test(token)) return \"out\";\n if (INPUT_REDIRECT.test(token)) return \"in\";\n return null;\n}\nfunction splitRedirects(tokens) {\n const redirects = [];\n const rest = [];\n for (let i = 0; i < tokens.length; i++) {\n const t = tokens[i];\n const spaced = spacedRedirectDirection(t);\n if (spaced !== null) {\n const next = tokens[i + 1];\n if (next !== void 0) redirects.push({\n direction: spaced,\n target: next\n });\n i++;\n continue;\n }\n const out = OUTPUT_REDIRECT_ATTACHED.exec(t);\n if (out) {\n redirects.push({\n direction: \"out\",\n target: out[1]\n });\n continue;\n }\n const input = INPUT_REDIRECT_ATTACHED.exec(t);\n if (input) {\n redirects.push({\n direction: \"in\",\n target: input[1]\n });\n continue;\n }\n rest.push(t);\n }\n return {\n redirects,\n rest\n };\n}\nfunction redirectTargets(tokens, direction) {\n return splitRedirects(tokens).redirects.filter((r) => r.direction === direction).map((r) => r.target);\n}\nfunction writeDestinations(tokens) {\n const cmd = path.basename(tokens[0] ?? \"\");\n const rest = tokens.slice(1);\n const dests = redirectTargets(tokens, \"out\");\n if (cmd === \"dd\") dests.push(...rest.filter((t) => t.startsWith(\"of=\")).map((t) => t.slice(3)));\n else if (cmd === \"sed\") {\n if (rest.some((t) => t === \"--in-place\" || /^-[a-zA-Z]*i/.test(t))) dests.push(...rest.filter((t) => !t.startsWith(\"-\")));\n } else if (ALL_POSITIONAL_DEST.has(cmd)) dests.push(...rest.filter((t) => !t.startsWith(\"-\")));\n else if (LAST_POSITIONAL_DEST.has(cmd)) {\n if (cmd === \"cp\" && rest.includes(\"--remove-destination\")) return dests;\n const targetDir = explicitTargetDirectory(rest);\n if (targetDir !== null) dests.push(targetDir);\n else {\n const last = rest.findLast((t) => !t.startsWith(\"-\"));\n if (last !== void 0) dests.push(last);\n }\n }\n return dests;\n}\nfunction writesIntoNodeModules(tokens, ctx) {\n return writeDestinations(tokens).some((d) => targetsNodeModules(d, ctx.cwd));\n}\nconst NODE_MODULES_WRITE_REASON = \"Don't write into node_modules/. Its files are hardlinks into pnpm's global store, so overwriting one in place (plain `cp`, `>` redirect, `sed -i`, ...) writes through the link and corrupts that package for every project on this machine -- silently, since the directory you wrote into ends up fine. To test a local build as an installed dependency, run it directly (`node packages/<pkg>/dist/bin.mjs <cmd>`), or install into a scratch tree with an isolated store (`pnpm add --store-dir <scratch> <pkg>`). If you must replace a file in place, `cp --remove-destination` is safe -- it unlinks the target first. Run `pnpm store status` to check whether the store is already damaged.\";\nfunction isChmod777(tokens) {\n return tokens.some((t) => t === \"777\" || t === \"0777\");\n}\nfunction isGitForcePush(tokens) {\n const pushIdx = tokens.indexOf(\"push\");\n if (pushIdx === -1) return false;\n return tokens.slice(pushIdx + 1).some((t) => t === \"--force\" || t === \"-f\");\n}\nconst ENV_READ_FILE_COMMANDS = /* @__PURE__ */ new Set([\n \"cat\",\n \"head\",\n \"tail\",\n \"less\",\n \"more\",\n \"source\",\n \".\"\n]);\nconst GREP_FAMILY_COMMANDS = /* @__PURE__ */ new Set([\n \"grep\",\n \"rg\",\n \"ag\"\n]);\nfunction grepFileOperands(rest) {\n const files = [];\n let patternAccountedFor = false;\n let optionsEnded = false;\n for (let i = 0; i < rest.length; i++) {\n const t = rest[i];\n if (!optionsEnded && t === \"--\") {\n optionsEnded = true;\n continue;\n }\n if (!optionsEnded) {\n if (t === \"-f\" || t === \"--file\") {\n const value = rest[i + 1];\n if (value !== void 0) {\n files.push(value);\n i++;\n }\n patternAccountedFor = true;\n continue;\n }\n if (t.startsWith(\"--file=\")) {\n files.push(t.slice(7));\n patternAccountedFor = true;\n continue;\n }\n if (t.startsWith(\"-f\") && t !== \"-f\" && !t.startsWith(\"--\")) {\n files.push(t.slice(2));\n patternAccountedFor = true;\n continue;\n }\n if (t === \"-e\" || t === \"--regexp\") {\n i++;\n patternAccountedFor = true;\n continue;\n }\n if (t.startsWith(\"--regexp=\") || t.startsWith(\"-e\") && t !== \"-e\" && !t.startsWith(\"--\")) {\n patternAccountedFor = true;\n continue;\n }\n if (t.startsWith(\"-\")) continue;\n }\n if (!patternAccountedFor) {\n patternAccountedFor = true;\n continue;\n }\n files.push(t);\n }\n return files;\n}\nfunction envFileOperands(tokens) {\n const cmd = path.basename(tokens[0] ?? \"\");\n const { redirects, rest } = splitRedirects(tokens.slice(1));\n const operands = redirects.filter((r) => r.direction === \"in\").map((r) => r.target);\n if (GREP_FAMILY_COMMANDS.has(cmd)) operands.push(...grepFileOperands(rest));\n else if (ENV_READ_FILE_COMMANDS.has(cmd)) operands.push(...rest.filter((t) => !t.startsWith(\"-\")));\n return operands;\n}\nconst HEALTH_CEILING_FILES = [\"health-baseline.json\", \".tooling.json\"];\nconst HEALTH_BASELINE_FILE = \"health-baseline.json\";\nconst HEALTH_RAISE_REASON = \"A health ceiling raise travels alone: open a ceiling-only pull request -- only health-baseline.json and .tooling.json changed -- for a human to merge, or split the module. `health:check` refuses the raise beside code either way. To restore the default branch's copy run `git show origin/<default>:health-baseline.json > health-baseline.json`, and to adopt lower counts run `pnpm exec bst health:check --update-baseline`.\";\nfunction writesHealthBaseline(tokens, cwd) {\n const destinations = writeDestinations(tokens);\n if (!destinations.some((dest) => baseName(dest) === \"health-baseline.json\")) return false;\n const root = getRepoTopLevel(cwd) ?? cwd;\n return destinations.some((dest) => samePath(resolveIn(cwd, dest), path.join(root, HEALTH_BASELINE_FILE)));\n}\nfunction baseName(target) {\n const segments = target.split(/[/\\\\]/);\n return segments[segments.length - 1] ?? \"\";\n}\nfunction isBaselineRestore(tokens) {\n if (path.basename(tokens[0] ?? \"\") !== \"git\") return false;\n let i = 1;\n while (tokens[i]?.startsWith(\"-\") === true) i += GIT_FLAGS_WITH_ARG.has(tokens[i] ?? \"\") ? 2 : 1;\n if (tokens[i] !== \"show\") return false;\n return tokens.slice(i + 1).some((token) => token.includes(\":\") && baseName(token.slice(token.lastIndexOf(\":\") + 1)) === \"health-baseline.json\");\n}\nfunction isHealthBaselineReset(tokens) {\n return tokens.includes(\"health:check\") && tokens.includes(\"--update-baseline\") && tokens.includes(\"--reset\");\n}\nfunction raiseTravelsWithCode(cwd) {\n return branchCarriesOtherFiles(cwd, HEALTH_CEILING_FILES) === true;\n}\nconst denyRules = [\n {\n name: \"sudo\",\n match: /^sudo$/,\n action: \"deny\",\n reason: \"Don't use sudo. If you need elevated permissions, ask me to do it manually.\"\n },\n {\n name: \"git-force-push\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens) => isGitForcePush(tokens),\n reason: \"Use `git push --force-with-lease` instead of `--force`.\"\n },\n {\n name: \"git-protected-branch-write\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens, ctx) => movesProtectedRef(tokens, ctx),\n reason: \"This would move a protected branch (main, master, the remote's default branch, or one listed in .claude/hooks/bst-hook.settings.json#protectedBranches). Work on a feature branch in a worktree: `git worktree add ../<repo>-<topic> -b feat/<topic>`, then commit and push there. To bring `main` up to date, use `git pull --ff-only`.\"\n },\n {\n name: \"chmod-777\",\n match: /^chmod$/,\n action: \"deny\",\n when: (tokens) => isChmod777(tokens),\n reason: \"Don't use 777 permissions. Use more restrictive permissions.\"\n },\n {\n name: \"node-modules-write\",\n match: /./,\n action: \"deny\",\n when: (tokens, ctx) => writesIntoNodeModules(tokens, ctx),\n reason: NODE_MODULES_WRITE_REASON\n },\n {\n name: \"env-file-read\",\n match: /./,\n action: \"deny\",\n when: (tokens) => envFileOperands(tokens).some(isEnvFile),\n reason: \"Don't read .env files. These contain secrets that should not appear in the conversation.\"\n },\n {\n name: \"env-file-write\",\n match: /./,\n action: \"deny\",\n when: (tokens) => redirectTargets(tokens, \"out\").some(isEnvFile),\n reason: \"Don't write to .env files. These contain secrets.\"\n },\n {\n name: \"health-baseline-write\",\n match: /./,\n action: \"deny\",\n when: (tokens, ctx) => writesHealthBaseline(tokens, ctx.cwd) && !isBaselineRestore(tokens) && raiseTravelsWithCode(ctx.cwd),\n reason: HEALTH_RAISE_REASON\n },\n {\n name: \"health-baseline-reset\",\n match: /^bst$/,\n action: \"deny\",\n when: (tokens, ctx) => isHealthBaselineReset(tokens) && raiseTravelsWithCode(ctx.cwd),\n reason: HEALTH_RAISE_REASON\n },\n {\n name: \"npx-redirect\",\n match: /^npx$/,\n action: \"deny\",\n reason: \"This project uses pnpm. Use `pnpm dlx <pkg>` or `pnpm exec <cmd>` instead of npx.\"\n },\n {\n name: \"mise-self-update\",\n match: /^mise$/,\n action: \"deny\",\n when: (tokens) => tokens[1] === \"self-update\",\n reason: \"Don't self-update mise. The version is Renovate-managed; ask a human to bump the pin.\"\n }\n];\n\n//#endregion\n//#region src/lib/script-resolver.ts\nconst PackageJsonSchema = object({ scripts: record(string(), unknown()) });\nfunction resolveScript(scriptName, cwd) {\n const scripts = readScripts(cwd);\n if (!scripts) return null;\n const main = scripts[scriptName];\n if (typeof main !== \"string\") return null;\n const pre = scripts[`pre${scriptName}`];\n const post = scripts[`post${scriptName}`];\n return {\n pre: typeof pre === \"string\" ? pre : null,\n main,\n post: typeof post === \"string\" ? post : null\n };\n}\nfunction resolveFilteredScript(scriptName, filterPkg, cwd) {\n const pkgDir = findWorkspacePackageDir(filterPkg, cwd);\n if (!pkgDir) return null;\n const script = resolveScript(scriptName, pkgDir);\n if (!script) return null;\n return {\n ...script,\n pkgDir\n };\n}\nfunction resolveRecursiveScripts(scriptName, rootDir) {\n const pkgDirs = listWorkspacePackageDirs(rootDir);\n if (!pkgDirs) return null;\n const results = [];\n for (const pkgDir of pkgDirs) {\n const resolved = resolveScript(scriptName, pkgDir);\n if (resolved) results.push({\n ...resolved,\n pkgDir\n });\n }\n return results;\n}\nfunction listWorkspacePackageDirs(rootDir) {\n const patterns = readWorkspacePatterns(rootDir);\n if (!patterns) return null;\n const dirs = [];\n for (const pattern of patterns) {\n if (!pattern.endsWith(\"/*\")) continue;\n const baseDir = pattern.slice(0, -2);\n const parentDir = resolve(rootDir, baseDir);\n let entries;\n try {\n entries = readdirSync(parentDir);\n } catch {\n continue;\n }\n for (const entry of entries) {\n const entryDir = join(parentDir, entry);\n try {\n if (!statSync(entryDir).isDirectory()) continue;\n } catch {\n continue;\n }\n dirs.push(entryDir);\n }\n }\n return dirs.length > 0 ? dirs : null;\n}\nfunction findWorkspacePackageDir(pkgName, rootDir) {\n const pkgDirs = listWorkspacePackageDirs(rootDir);\n if (!pkgDirs) return null;\n for (const entryDir of pkgDirs) {\n const name = readPackageName(entryDir);\n if (name === pkgName || name === `@${pkgName}`) return entryDir;\n if (entryDir.split(\"/\").pop() === pkgName) return entryDir;\n if (name) {\n if ((name.startsWith(\"@\") ? name.split(\"/\")[1] : name) === pkgName) return entryDir;\n }\n }\n return null;\n}\nfunction readWorkspacePatterns(rootDir) {\n try {\n const lines = readFileSync(join(rootDir, \"pnpm-workspace.yaml\"), \"utf-8\").split(\"\\n\");\n const packagesIdx = lines.findIndex((l) => /^packages\\s*:/.test(l));\n if (packagesIdx === -1) return null;\n const patterns = [];\n for (let i = packagesIdx + 1; i < lines.length; i++) {\n const line = lines[i];\n const match = /^\\s+-\\s+(.+)$/.exec(line);\n if (!match) break;\n patterns.push(match[1].trim().replace(/['\"`]/g, \"\"));\n }\n return patterns.length > 0 ? patterns : null;\n } catch {\n return null;\n }\n}\nconst PackageNameSchema = object({ name: string() });\nfunction readPackageName(dir) {\n try {\n const raw = readFileSync(join(dir, \"package.json\"), \"utf-8\");\n const result = PackageNameSchema.safeParse(JSON.parse(raw));\n if (!result.success) return null;\n return result.data.name;\n } catch {\n return null;\n }\n}\nfunction readScripts(cwd) {\n try {\n const raw = readFileSync(join(cwd, \"package.json\"), \"utf-8\");\n const result = PackageJsonSchema.safeParse(JSON.parse(raw));\n if (!result.success) return null;\n return result.data.scripts;\n } catch {\n return null;\n }\n}\n\n//#endregion\n//#region src/lib/workspace-root.ts\nfunction findWorkspaceRoot(cwd) {\n let dir = resolve(cwd);\n while (true) {\n if (existsSync(join(dir, \"pnpm-workspace.yaml\"))) return dir;\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n//#endregion\n//#region src/evaluate.ts\nconst MAX_EVAL_DEPTH = 5;\nfunction subshellBody(command) {\n if (!command.startsWith(\"(\") || !command.endsWith(\")\")) return null;\n let depth = 0;\n let quote = null;\n for (let i = 0; i < command.length; i++) {\n const ch = command[i];\n if (quote !== null) {\n if (ch === quote) quote = null;\n else if (ch === \"\\\\\" && quote === \"\\\"\") i++;\n continue;\n }\n if (ch === \"\\\\\") i++;\n else if (ch === \"'\" || ch === \"\\\"\") quote = ch;\n else if (ch === \"(\") depth++;\n else if (ch === \")\") {\n depth--;\n if (depth === 0) return i === command.length - 1 ? command.slice(1, -1) : null;\n }\n }\n return null;\n}\nfunction extractCdDir(segment, ctx) {\n const tokenResult = tokenize(segment);\n if (!tokenResult.ok || tokenResult.tokens.length !== 2) return null;\n const tokens = tokenResult.tokens;\n if (tokens[0] !== \"cd\") return null;\n const dir = tokens[1];\n if (path.isAbsolute(dir)) return dir;\n return path.resolve(ctx.cwd, dir);\n}\nfunction extractBranchSwitch(segment, ctx) {\n const tokenResult = tokenize(segment);\n if (!tokenResult.ok) return void 0;\n let tokens = tokenResult.tokens;\n let envSkipped = 0;\n while (envSkipped < tokens.length && /^\\w+=/.test(tokens[envSkipped])) envSkipped++;\n tokens = tokens.slice(envSkipped);\n if (tokens.length === 0 || path.basename(tokens[0]) !== \"git\") return void 0;\n return branchSwitch(tokens, ctx, checkInjection(tokens, segment).safe);\n}\nfunction evaluateCommand(command, ctx) {\n if (ctx.depth >= MAX_EVAL_DEPTH) return {\n action: \"none\",\n rule: null,\n reason: \"max evaluation depth exceeded\"\n };\n const trimmed = command.trim();\n if (!trimmed) return {\n action: \"none\",\n rule: null,\n reason: null\n };\n const body = subshellBody(trimmed);\n if (body !== null) return evaluateCommand(body, {\n ...ctx,\n depth: ctx.depth + 1\n });\n const segments = splitCommand(trimmed);\n if (segments.length === 0) return {\n action: \"none\",\n rule: null,\n reason: null\n };\n let firstNone = null;\n const currentCtx = { ...ctx };\n let unknownAfterNext;\n for (const segment of segments) {\n if (segment.operator === \"&&\") {\n const cdDir = extractCdDir(segment.command, currentCtx);\n if (cdDir) currentCtx.cwd = cdDir;\n }\n const result = evaluateSegment(segment.command, currentCtx);\n if (result.action === \"deny\") return result;\n if (!firstNone) firstNone = result;\n if (unknownAfterNext) {\n currentCtx.pendingBranches = {\n ...currentCtx.pendingBranches,\n [unknownAfterNext.dir]: unknownAfterNext\n };\n unknownAfterNext = void 0;\n }\n const switched = extractBranchSwitch(segment.command, currentCtx);\n if (switched) {\n const unknown = {\n dir: switched.dir,\n branch: null\n };\n if (segment.operator === \"&&\") currentCtx.pendingBranches = {\n ...currentCtx.pendingBranches,\n [switched.dir]: switched\n };\n else if (segment.operator === \"||\") unknownAfterNext = unknown;\n else currentCtx.pendingBranches = {\n ...currentCtx.pendingBranches,\n [switched.dir]: unknown\n };\n }\n }\n return firstNone ?? {\n action: \"none\",\n rule: null,\n reason: null\n };\n}\nfunction evaluateResolvedScript(resolved, ctx) {\n if (resolved.pre) {\n const preResult = evaluateCommand(resolved.pre, ctx);\n if (preResult.action === \"deny\") return preResult;\n }\n const mainResult = evaluateCommand(resolved.main, ctx);\n if (mainResult.action === \"deny\") return mainResult;\n if (resolved.post) {\n const postResult = evaluateCommand(resolved.post, ctx);\n if (postResult.action === \"deny\") return postResult;\n }\n return {\n action: \"none\",\n rule: null,\n reason: null\n };\n}\nfunction evaluateSegment(rawSegment, ctx) {\n const trimmed = rawSegment.trim();\n const body = subshellBody(trimmed);\n if (body !== null) return evaluateCommand(body, {\n ...ctx,\n depth: ctx.depth + 1\n });\n if (trimmed.startsWith(\"(\") && trimmed.endsWith(\")\")) return {\n action: \"none\",\n rule: null,\n reason: \"subshell\"\n };\n const tokenResult = tokenize(rawSegment);\n if (!tokenResult.ok) return {\n action: \"none\",\n rule: null,\n reason: tokenResult.error\n };\n let tokens = tokenResult.tokens;\n if (tokens.length === 0) return {\n action: \"none\",\n rule: null,\n reason: null\n };\n let envSkipped = 0;\n while (envSkipped < tokens.length && /^\\w+=/.test(tokens[envSkipped])) envSkipped++;\n if (envSkipped >= tokens.length) return {\n action: \"none\",\n rule: null,\n reason: null\n };\n tokens = tokens.slice(envSkipped);\n if (writesIntoNodeModules(tokens, ctx)) return {\n action: \"deny\",\n rule: \"node-modules-write\",\n reason: NODE_MODULES_WRITE_REASON\n };\n const injection = checkInjection(tokens, rawSegment);\n if (!injection.safe) return {\n action: \"none\",\n rule: null,\n reason: injection.reason\n };\n const SAFE_REDIRECT_TOKENS = /* @__PURE__ */ new Set([\n \"2>&1\",\n \"2>/dev/null\",\n \">/dev/null\",\n \"1>/dev/null\"\n ]);\n tokens = tokens.filter((t, i) => {\n if (SAFE_REDIRECT_TOKENS.has(t)) return false;\n if ((t === \">\" || t === \">>\") && tokens[i + 1] === \"/dev/null\") return false;\n if (t === \"/dev/null\" && i > 0 && (tokens[i - 1] === \">\" || tokens[i - 1] === \">>\")) return false;\n return true;\n });\n const cmdName = path.basename(tokens[0]);\n if (cmdName === \"command\" && tokens.length > 1) return evaluateCommand(joinTokens(tokens.slice(1)), {\n ...ctx,\n depth: ctx.depth + 1\n });\n if (cmdName === \"env\") {\n let envIdx = 1;\n while (envIdx < tokens.length && /^\\w+=/.test(tokens[envIdx])) envIdx++;\n if (envIdx >= tokens.length) return {\n action: \"none\",\n rule: null,\n reason: null\n };\n return evaluateCommand(joinTokens(tokens.slice(envIdx)), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n if (cmdName === \"pnpm\") return evaluatePnpm(tokens, ctx);\n if (cmdName === \"npm\") return evaluateNpm(tokens);\n if (cmdName === \"mise\" && tokens[1] === \"exec\") {\n const inner = miseExecInner(tokens);\n if (!inner) return {\n action: \"none\",\n rule: null,\n reason: \"bare mise exec\"\n };\n return evaluateCommand(joinTokens(inner), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n if (cmdName === \"bash\" || cmdName === \"sh\" || cmdName === \"zsh\") return evaluateShellC(tokens, ctx);\n if (cmdName === \"xargs\") return evaluateXargs(tokens, ctx);\n if (cmdName === \"node\" && (tokens.includes(\"-e\") || tokens.includes(\"-p\") || tokens.includes(\"--eval\") || tokens.includes(\"--print\"))) return {\n action: \"none\",\n rule: null,\n reason: \"node eval\"\n };\n for (const rule of denyRules) if (rule.match.test(cmdName)) {\n if (!rule.when || rule.when(tokens, ctx)) return {\n action: \"deny\",\n rule: rule.name,\n reason: rule.reason ?? null\n };\n }\n const advice = cmdName === \"git\" ? worktreeAdvice(tokens, ctx) : void 0;\n return {\n action: \"none\",\n rule: null,\n reason: `no rule matched: ${cmdName}`,\n ...advice ? { advice } : {}\n };\n}\nfunction miseExecInner(tokens) {\n const dashIdx = tokens.indexOf(\"--\");\n if (dashIdx === -1) return void 0;\n const inner = tokens.slice(dashIdx + 1);\n return inner.length > 0 ? inner : void 0;\n}\nfunction scanPnpmFlags(subTokens, from, state) {\n for (let i = from; i < subTokens.length; i++) {\n const t = subTokens[i];\n if (t === \"--filter\" || t.startsWith(\"--filter=\") || t === \"-F\" || t.startsWith(\"-F=\")) return {\n name: void 0,\n filterIdx: i\n };\n if (t === \"-r\" || t === \"--recursive\") {\n state.isRecursive = true;\n continue;\n }\n if (t === \"-w\" || t === \"--workspace-root\") {\n state.isWorkspaceRoot = true;\n continue;\n }\n if (t === \"-C\" || t === \"--dir\") {\n state.dirOverride = subTokens[++i];\n continue;\n }\n if (t.startsWith(\"-C=\") || t.startsWith(\"--dir=\")) {\n state.dirOverride = t.split(\"=\", 2)[1];\n continue;\n }\n if (t.startsWith(\"-\") && !PNPM_BUILTINS.has(t)) continue;\n return { name: t };\n }\n return { name: void 0 };\n}\nfunction evaluatePnpm(tokens, ctx) {\n const subTokens = tokens.slice(1);\n const state = {\n isRecursive: false,\n isWorkspaceRoot: false,\n dirOverride: void 0\n };\n const scan = scanPnpmFlags(subTokens, 0, state);\n if (scan.filterIdx !== void 0) return evaluatePnpmFilter(tokens, subTokens, scan.filterIdx, ctx, state.dirOverride);\n const subcommand = scan.name;\n const subcommandIdx = subcommand ? subTokens.indexOf(subcommand) : -1;\n if (!subcommand) return {\n action: \"none\",\n rule: null,\n reason: null\n };\n if (subcommand === \"dlx\") return {\n action: \"none\",\n rule: null,\n reason: \"pnpm dlx\"\n };\n if (subcommand === \"add\") return {\n action: \"none\",\n rule: null,\n reason: \"pnpm add\"\n };\n if (subcommand === \"exec\") {\n const execArgs = subTokens.slice(subcommandIdx + 1);\n if (execArgs.length === 0) return {\n action: \"none\",\n rule: null,\n reason: \"bare pnpm exec\"\n };\n return evaluateCommand(joinTokens(execArgs), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n let scriptName;\n let { isRecursive, isWorkspaceRoot, dirOverride } = state;\n if (subcommand === \"run\") {\n const runScan = scanPnpmFlags(subTokens, subcommandIdx + 1, state);\n ({isRecursive, isWorkspaceRoot, dirOverride} = state);\n if (runScan.filterIdx !== void 0) return evaluatePnpmFilter(tokens, subTokens, runScan.filterIdx, ctx, state.dirOverride);\n if (!runScan.name) return {\n action: \"none\",\n rule: null,\n reason: \"pnpm run without script name\"\n };\n scriptName = runScan.name;\n } else if (!PNPM_BUILTINS.has(subcommand)) scriptName = subcommand;\n else return {\n action: \"none\",\n rule: null,\n reason: null\n };\n if (isRecursive) return evaluatePnpmRecursive(scriptName, ctx);\n if (isWorkspaceRoot) {\n const root = findWorkspaceRoot(ctx.cwd);\n if (!root) return {\n action: \"none\",\n rule: null,\n reason: \"workspace root not found\"\n };\n return evaluatePnpmScript(scriptName, {\n ...ctx,\n cwd: root\n });\n }\n if (dirOverride) {\n const resolved = path.resolve(ctx.cwd, dirOverride);\n return evaluatePnpmScript(scriptName, {\n ...ctx,\n cwd: resolved\n });\n }\n return evaluatePnpmScript(scriptName, ctx);\n}\nfunction evaluatePnpmScript(scriptName, ctx) {\n const resolved = resolveScript(scriptName, ctx.cwd);\n if (!resolved) return {\n action: \"none\",\n rule: null,\n reason: \"script resolution failed\"\n };\n const res = evaluateResolvedScript(resolved, {\n ...ctx,\n depth: ctx.depth + 1\n });\n if (res.action === \"deny\") return res;\n return {\n action: \"none\",\n rule: `pnpm-script:${scriptName}`,\n reason: null\n };\n}\nfunction evaluatePnpmRecursive(scriptName, ctx) {\n const resolved = resolveRecursiveScripts(scriptName, ctx.cwd);\n if (!resolved || resolved.length === 0) return {\n action: \"none\",\n rule: null,\n reason: \"pnpm recursive\"\n };\n for (const pkg of resolved) {\n const res = evaluateResolvedScript(pkg, {\n ...ctx,\n depth: ctx.depth + 1,\n cwd: pkg.pkgDir ?? ctx.cwd\n });\n if (res.action === \"deny\") return res;\n }\n return {\n action: \"none\",\n rule: `pnpm-recursive:${scriptName}`,\n reason: null\n };\n}\nfunction evaluateNpm(tokens) {\n if (isNpmReadOp(tokens)) return {\n action: \"none\",\n rule: null,\n reason: null\n };\n return {\n action: \"deny\",\n rule: \"npm-redirect\",\n reason: getNpmRedirectReason(tokens)\n };\n}\nfunction collectFiltersAndScan(subTokens, from, filterPkgs) {\n let dirOverride;\n for (let i = from; i < subTokens.length; i += 1) {\n const t = subTokens[i];\n if (t === \"--filter\" || t === \"-F\") {\n const pkg = subTokens[i + 1];\n if (pkg === void 0 || pkg.startsWith(\"-\")) return void 0;\n filterPkgs.push(pkg);\n i += 1;\n continue;\n }\n if (t.startsWith(\"--filter=\") || t.startsWith(\"-F=\")) {\n const pkg = t.startsWith(\"-F=\") ? t.slice(3) : t.slice(9);\n if (pkg === \"\") return void 0;\n filterPkgs.push(pkg);\n continue;\n }\n if (t === \"-r\" || t === \"--recursive\" || t === \"-w\" || t === \"--workspace-root\") continue;\n if (t === \"-C\" || t === \"--dir\") {\n dirOverride = subTokens[i + 1];\n i += 1;\n continue;\n }\n if (t.startsWith(\"-C=\") || t.startsWith(\"--dir=\")) {\n dirOverride = t.split(\"=\", 2)[1];\n continue;\n }\n if (t.startsWith(\"-\")) continue;\n return {\n name: t,\n index: i,\n dirOverride\n };\n }\n return {\n name: void 0,\n index: -1,\n dirOverride\n };\n}\nfunction evaluatePnpmFilter(_tokens, subTokens, filterIdx, ctx, stateDir) {\n const filterPkgs = [];\n const scan = collectFiltersAndScan(subTokens, filterIdx, filterPkgs);\n if (scan === void 0) return {\n action: \"none\",\n rule: null,\n reason: \"pnpm --filter without package\"\n };\n let dirOverride = scan.dirOverride ?? stateDir;\n if (filterPkgs.length === 0) return {\n action: \"none\",\n rule: null,\n reason: \"pnpm --filter without package\"\n };\n const filterSubcommand = scan.name;\n const subcmdIdx = scan.index;\n if (filterSubcommand === void 0) return {\n action: \"none\",\n rule: null,\n reason: \"pnpm --filter without subcommand\"\n };\n if (PNPM_BUILTINS.has(filterSubcommand) && filterSubcommand !== \"run\" && filterSubcommand !== \"exec\") return {\n action: \"none\",\n rule: null,\n reason: null\n };\n if (filterSubcommand === \"exec\") {\n const execArgs = subTokens.slice(subcmdIdx + 1);\n if (execArgs.length === 0) return {\n action: \"none\",\n rule: null,\n reason: \"bare pnpm exec\"\n };\n return evaluateCommand(joinTokens(execArgs), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n let scriptName = filterSubcommand;\n if (filterSubcommand === \"run\") {\n const runScan = collectFiltersAndScan(subTokens, subcmdIdx + 1, filterPkgs);\n if (runScan === void 0) return {\n action: \"none\",\n rule: null,\n reason: \"pnpm --filter without package\"\n };\n scriptName = runScan.name;\n if (runScan.dirOverride !== void 0) dirOverride = runScan.dirOverride;\n }\n if (scriptName === void 0 || scriptName.startsWith(\"-\")) return {\n action: \"none\",\n rule: null,\n reason: \"pnpm --filter run without script name\"\n };\n if (PNPM_BUILTINS.has(scriptName) && scriptName !== \"run\") return {\n action: \"none\",\n rule: null,\n reason: null\n };\n const baseDir = dirOverride ? path.resolve(ctx.cwd, dirOverride) : ctx.cwd;\n for (const pkg of filterPkgs) {\n const resolved = resolveFilteredScript(scriptName, pkg, baseDir);\n if (!resolved) return {\n action: \"none\",\n rule: null,\n reason: \"filtered script resolution failed\"\n };\n const res = evaluateResolvedScript(resolved, {\n ...ctx,\n depth: ctx.depth + 1,\n cwd: resolved.pkgDir ?? baseDir\n });\n if (res.action === \"deny\") return res;\n }\n return {\n action: \"none\",\n rule: `pnpm-filter:${filterPkgs.join(\",\")}:${scriptName}`,\n reason: null\n };\n}\nconst SHELL_LOGIN_FLAGS = /* @__PURE__ */ new Set([\"-l\", \"--login\"]);\nfunction evaluateShellC(tokens, ctx) {\n let i = 1;\n while (i < tokens.length && SHELL_LOGIN_FLAGS.has(tokens[i])) i++;\n const flag = tokens[i];\n if (flag === void 0 || !/^-[lc]+$/.test(flag) || !flag.includes(\"c\")) return {\n action: \"none\",\n rule: null,\n reason: \"shell without a plain -c\"\n };\n const cmdArg = tokens[i + 1];\n if (!cmdArg) return {\n action: \"none\",\n rule: null,\n reason: \"shell -c without command\"\n };\n if (tokens.length > i + 2) return {\n action: \"none\",\n rule: null,\n reason: \"shell -c with positional args\"\n };\n return evaluateCommand(cmdArg, {\n ...ctx,\n depth: ctx.depth + 1\n });\n}\nconst XARGS_ARG_FLAGS = /* @__PURE__ */ new Set([\n \"-I\",\n \"-d\",\n \"-n\",\n \"-P\",\n \"-s\",\n \"-L\",\n \"-E\",\n \"-a\",\n \"--max-args\",\n \"--max-procs\",\n \"--delimiter\",\n \"--replace\",\n \"--max-lines\",\n \"--max-chars\",\n \"--arg-file\",\n \"--process-slot-var\"\n]);\nconst XARGS_BOOL_FLAGS = /* @__PURE__ */ new Set([\n \"-0\",\n \"--null\",\n \"-t\",\n \"--verbose\",\n \"-r\",\n \"--no-run-if-empty\",\n \"-p\",\n \"--interactive\",\n \"-x\",\n \"--exit\",\n \"--show-limits\"\n]);\nfunction evaluateXargs(tokens, ctx) {\n let i = 1;\n while (i < tokens.length) {\n const t = tokens[i];\n if (!t.startsWith(\"-\")) break;\n if (XARGS_ARG_FLAGS.has(t)) {\n i += 2;\n continue;\n }\n const eqIdx = t.indexOf(\"=\");\n if (eqIdx !== -1) {\n const flagName = t.slice(0, eqIdx);\n if (XARGS_ARG_FLAGS.has(flagName)) {\n i++;\n continue;\n }\n }\n if (t.length > 2 && XARGS_ARG_FLAGS.has(t.slice(0, 2))) {\n i++;\n continue;\n }\n if (XARGS_BOOL_FLAGS.has(t)) {\n i++;\n continue;\n }\n return {\n action: \"none\",\n rule: null,\n reason: \"xargs with unrecognised flags\"\n };\n }\n if (i >= tokens.length) return {\n action: \"none\",\n rule: null,\n reason: \"xargs without command\"\n };\n return evaluateCommand(joinTokens(tokens.slice(i)), {\n ...ctx,\n depth: ctx.depth + 1\n });\n}\n\n//#endregion\n//#region src/file-rules.ts\nconst READ_TOOLS = /* @__PURE__ */ new Set([\n \"Read\",\n \"Glob\",\n \"Grep\"\n]);\nconst WRITE_TOOLS = /* @__PURE__ */ new Set([\"Write\", \"Edit\"]);\nconst fileDenyRules = [\n {\n name: \"env-file-read\",\n tools: READ_TOOLS,\n action: \"deny\",\n when: (filePath) => isEnvFile(filePath),\n reason: \"Don't read .env files. These contain secrets that should not appear in the conversation.\"\n },\n {\n name: \"env-file-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath) => isEnvFile(filePath),\n reason: \"Don't write to .env files. These contain secrets.\"\n },\n {\n name: \"hooks-folder-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx) => {\n const resolved = resolveIn(ctx.cwd, filePath);\n const hooksDir = path.join(ctx.cwd, \".claude\", \"hooks\");\n return isWithin(hooksDir, resolved);\n },\n reason: \"Don't write to .claude/hooks/. These files are managed by the project and should not be modified directly.\"\n },\n {\n name: \"git-dir-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx) => targetsGitDir(filePath, ctx.cwd),\n reason: \"Don't write into .git/ directly. Its internals (config, hooks, refs) are not plain project files -- a config edit can redirect remotes or push destinations that other rules trust, and a hooks/ script runs automatically on the next commit/push. Use git subcommands instead, or ask the user.\"\n },\n {\n name: \"node-modules-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx) => targetsNodeModules(filePath, ctx.cwd),\n reason: \"Don't write into node_modules/. Its files are hardlinks into pnpm's global store -- editing one in place corrupts that package for every project on this machine. Edit the source package and rebuild, or install into a scratch tree with an isolated store (`pnpm add --store-dir <scratch> <pkg>`).\"\n },\n {\n name: \"claude-settings-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx) => {\n const resolved = resolveIn(ctx.cwd, filePath);\n return samePath(resolved, path.join(ctx.cwd, \".claude\", \"settings.json\"));\n },\n reason: \"Don't write to .claude/settings.json. This file is managed by the project and should not be modified directly.\"\n },\n {\n name: \"health-baseline-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx, toolInput) => {\n const resolved = resolveIn(ctx.cwd, filePath);\n if (path.basename(resolved) !== \"health-baseline.json\") return false;\n const root = getRepoTopLevel(ctx.cwd) ?? ctx.cwd;\n if (!samePath(resolved, path.join(root, \"health-baseline.json\"))) return false;\n if (!writeRaisesBaseline(resolved, toolInput)) return false;\n return branchCarriesOtherFiles(ctx.cwd, HEALTH_CEILING_FILES) === true;\n },\n reason: HEALTH_RAISE_REASON\n }\n];\nfunction writeRaisesBaseline(file, toolInput) {\n let before;\n try {\n before = readFileSync(file, \"utf8\");\n } catch (_error) {\n return false;\n }\n const after = textAfterWrite(before, toolInput);\n if (after === void 0) return false;\n const beforeJson = parseJson(before);\n const afterJson = parseJson(after);\n if (beforeJson === void 0 || afterJson === void 0) return false;\n return numberRises(beforeJson, afterJson);\n}\nfunction textAfterWrite(before, toolInput) {\n const { content, old_string: oldText, new_string: newText, replace_all: all } = toolInput;\n if (typeof content === \"string\") return content;\n if (typeof oldText !== \"string\" || typeof newText !== \"string\" || oldText === \"\") return void 0;\n const at = before.indexOf(oldText);\n if (at === -1) return void 0;\n if (all === true) return before.split(oldText).join(newText);\n return before.slice(0, at) + newText + before.slice(at + oldText.length);\n}\nfunction parseJson(text) {\n try {\n const result = record(string(), unknown()).safeParse(JSON.parse(text));\n return result.success ? result.data : void 0;\n } catch (_error) {\n return;\n }\n}\nfunction numberRises(before, after) {\n if (typeof after === \"number\") return typeof before !== \"number\" || after > before;\n if (typeof after !== \"object\" || after === null) return false;\n const beforeObject = typeof before === \"object\" && before !== null ? before : {};\n return Object.entries(after).some(([key, value]) => numberRises(Object.entries(beforeObject).find(([k]) => k === key)?.[1], value));\n}\n\n//#endregion\n//#region src/evaluate-tool.ts\nconst FILE_PATH_TOOLS = /* @__PURE__ */ new Set([\n \"Read\",\n \"Write\",\n \"Edit\"\n]);\nconst SEARCH_TOOLS = /* @__PURE__ */ new Set([\"Glob\", \"Grep\"]);\nfunction extractPath(toolName, toolInput, cwd) {\n if (FILE_PATH_TOOLS.has(toolName)) return parseFilePathInput(toolInput)?.file_path ?? null;\n if (SEARCH_TOOLS.has(toolName)) return parseSearchInput(toolInput)?.path ?? cwd;\n return null;\n}\nfunction evaluateToolUse(toolName, toolInput, ctx) {\n const filePath = extractPath(toolName, toolInput, ctx.cwd);\n if (filePath === null) return {\n action: \"none\",\n rule: null,\n reason: \"unknown tool input shape\"\n };\n for (const rule of fileDenyRules) if (rule.tools.has(toolName) && rule.when(filePath, ctx, toolInput)) return {\n action: \"deny\",\n rule: rule.name,\n reason: rule.reason ?? null\n };\n return {\n action: \"none\",\n rule: null,\n reason: \"no file rule matched\"\n };\n}\n\n//#endregion\n//#region src/lib/settings.ts\nconst SETTINGS_PATH = \".claude/hooks/bst-hook.settings.json\";\nconst BstHookSettingsSchema = object({ protectedBranches: _default(array(string().check(_minLength(1))), []) });\nfunction isFileClean(cwd, relativePath) {\n try {\n execFileSync(\"git\", [\n \"ls-files\",\n \"--error-unmatch\",\n relativePath\n ], {\n cwd,\n stdio: \"ignore\",\n timeout: GIT_TIMEOUT_MS\n });\n execFileSync(\"git\", [\n \"diff\",\n \"--quiet\",\n \"HEAD\",\n \"--\",\n relativePath\n ], {\n cwd,\n stdio: \"ignore\",\n timeout: GIT_TIMEOUT_MS\n });\n return true;\n } catch {\n return false;\n }\n}\nfunction loadRepoSettings(cwd) {\n const root = findWorkspaceRoot(cwd) ?? cwd;\n const settingsFile = join(root, SETTINGS_PATH);\n const empty = { protectedBranches: [] };\n if (!existsSync(settingsFile)) return empty;\n if (!isFileClean(root, SETTINGS_PATH)) return empty;\n try {\n const raw = readFileSync(settingsFile, \"utf-8\");\n const parsed = JSON.parse(raw);\n const result = BstHookSettingsSchema.safeParse(parsed);\n if (!result.success) return empty;\n return { protectedBranches: result.data.protectedBranches };\n } catch {\n return empty;\n }\n}\n\n//#endregion\n//#region src/index.ts\nconst inputResult = await readHookInput();\nif (!inputResult.ok) {\n process.stderr.write(`[bst-hook] invalid hook input: ${inputResult.error}\\n`);\n audit({\n sessionId: \"unknown\",\n cwd: \"unknown\",\n command: `<invalid input: ${inputResult.error}>`,\n decision: \"none\",\n rule: null,\n reason: `validation failed: ${inputResult.error}`,\n permissionMode: void 0\n });\n process.exit(0);\n}\nconst input = inputResult.data;\nif (input.hook_event_name !== \"PreToolUse\") process.exit(0);\nif (!(/* @__PURE__ */ new Set([\n \"Bash\",\n \"Read\",\n \"Write\",\n \"Edit\",\n \"Glob\",\n \"Grep\"\n])).has(input.tool_name)) process.exit(0);\nconst repoSettings = loadRepoSettings(input.cwd);\nlet decision;\nlet auditCommand;\nif (input.tool_name === \"Bash\") {\n const bashInput = parseBashInput(input.tool_input);\n if (!bashInput) {\n audit({\n sessionId: input.session_id,\n cwd: input.cwd,\n toolName: input.tool_name,\n command: `<invalid Bash input: ${JSON.stringify(input.tool_input).slice(0, 200)}>`,\n decision: \"none\",\n rule: null,\n reason: \"Bash tool_input missing or invalid 'command' field\",\n permissionMode: input.permission_mode\n });\n process.exit(0);\n }\n decision = evaluateCommand(bashInput.command, {\n cwd: input.cwd,\n depth: 0,\n repoProtectedBranches: repoSettings.protectedBranches\n });\n auditCommand = bashInput.command;\n} else {\n const projectDir = findWorkspaceRoot(input.cwd) ?? process.env[\"CLAUDE_PROJECT_DIR\"] ?? input.cwd;\n decision = evaluateToolUse(input.tool_name, input.tool_input, {\n cwd: projectDir,\n sessionId: input.session_id\n });\n const filePath = String(input.tool_input[\"file_path\"] ?? input.tool_input[\"path\"] ?? \"\");\n auditCommand = `${input.tool_name} ${filePath}`;\n}\naudit({\n sessionId: input.session_id,\n cwd: input.cwd,\n toolName: input.tool_name,\n command: auditCommand,\n decision: decision.action,\n rule: decision.rule,\n reason: decision.reason,\n permissionMode: input.permission_mode\n});\nif (decision.action === \"deny\") writeDeny(decision.reason);\nelse if (decision.advice) writeAdvice(decision.advice);\n\n//#endregion\nexport { };",
|
|
8419
8476
|
guide: "# bst-hook Permission Guide\n\nThis project uses a PreToolUse hook (`bst-hook`) that checks each Bash command\nand file tool call before it runs. The hook only ever blocks a command; it does\nnot approve anything on its own. A command that matches a rule below is denied,\nwith a message that says what to do instead.\n\n## Denied commands\n\nThese are blocked. The hook tells you what to do instead.\n\n### Bash\n\n- **sudo**: Don't use sudo. If you need elevated permissions, ask me to do it manually.\n- **git-force-push**: Use `git push --force-with-lease` instead of `--force`.\n- **git-protected-branch-write**: This would move a protected branch (main, master, the remote's default branch, or one listed in .claude/hooks/bst-hook.settings.json#protectedBranches). Work on a feature branch in a worktree: `git worktree add ../<repo>-<topic> -b feat/<topic>`, then commit and push there. To bring `main` up to date, use `git pull --ff-only`.\n- **chmod-777**: Don't use 777 permissions. Use more restrictive permissions.\n- **node-modules-write**: Don't write into node_modules/. Its files are hardlinks into pnpm's global store, so overwriting one in place (plain `cp`, `>` redirect, `sed -i`, ...) writes through the link and corrupts that package for every project on this machine -- silently, since the directory you wrote into ends up fine. To test a local build as an installed dependency, run it directly (`node packages/<pkg>/dist/bin.mjs <cmd>`), or install into a scratch tree with an isolated store (`pnpm add --store-dir <scratch> <pkg>`). If you must replace a file in place, `cp --remove-destination` is safe -- it unlinks the target first. Run `pnpm store status` to check whether the store is already damaged.\n- **env-file-read**: Don't read .env files. These contain secrets that should not appear in the conversation.\n- **env-file-write**: Don't write to .env files. These contain secrets.\n- **health-baseline-write**: A health ceiling raise travels alone: open a ceiling-only pull request -- only health-baseline.json and .tooling.json changed -- for a human to merge, or split the module. `health:check` refuses the raise beside code either way. To restore the default branch's copy run `git show origin/<default>:health-baseline.json > health-baseline.json`, and to adopt lower counts run `pnpm exec bst health:check --update-baseline`.\n- **health-baseline-reset**: A health ceiling raise travels alone: open a ceiling-only pull request -- only health-baseline.json and .tooling.json changed -- for a human to merge, or split the module. `health:check` refuses the raise beside code either way. To restore the default branch's copy run `git show origin/<default>:health-baseline.json > health-baseline.json`, and to adopt lower counts run `pnpm exec bst health:check --update-baseline`.\n- **npx-redirect**: This project uses pnpm. Use `pnpm dlx <pkg>` or `pnpm exec <cmd>` instead of npx.\n- **mise-self-update**: Don't self-update mise. The version is Renovate-managed; ask a human to bump the pin.\n\n### File tools (Read, Write, Edit, Glob, Grep)\n\n- **env-file-read**: Don't read .env files. These contain secrets that should not appear in the conversation.\n- **env-file-write**: Don't write to .env files. These contain secrets.\n- **hooks-folder-write**: Don't write to .claude/hooks/. These files are managed by the project and should not be modified directly.\n- **git-dir-write**: Don't write into .git/ directly. Its internals (config, hooks, refs) are not plain project files -- a config edit can redirect remotes or push destinations that other rules trust, and a hooks/ script runs automatically on the next commit/push. Use git subcommands instead, or ask the user.\n- **node-modules-write**: Don't write into node_modules/. Its files are hardlinks into pnpm's global store -- editing one in place corrupts that package for every project on this machine. Edit the source package and rebuild, or install into a scratch tree with an isolated store (`pnpm add --store-dir <scratch> <pkg>`).\n- **claude-settings-write**: Don't write to .claude/settings.json. This file is managed by the project and should not be modified directly.\n- **health-baseline-write**: A health ceiling raise travels alone: open a ceiling-only pull request -- only health-baseline.json and .tooling.json changed -- for a human to merge, or split the module. `health:check` refuses the raise beside code either way. To restore the default branch's copy run `git show origin/<default>:health-baseline.json > health-baseline.json`, and to adopt lower counts run `pnpm exec bst health:check --update-baseline`.\n\n## How commands are evaluated\n\n- **Pipelines and chains**: every segment is checked; a deny on any segment blocks the whole command.\n- **pnpm scripts**: `pnpm <script>` resolves against `package.json` (including pre/post hooks and `--filter` workspaces) and evaluates the resolved command recursively.\n- **cd propagation**: `cd dir && cmd` evaluates `cmd` relative to the new directory.\n- **Branch switch propagation**: `git switch <branch> && cmd` (or `git checkout <branch>`) evaluates a git write in `cmd` against the branch it switched to, not the one checked out before. This holds only across `&&` and only in the same directory.\n- **Audit log**: all decisions are logged to `.claude/hooks/bst-hook.audit.local.jsonl`.\n\n## Everything else\n\nA command that matches none of the rules above is judged by Claude Code's own\nauto mode, not by this hook.\n"
|
|
8420
8477
|
}) {
|
|
8421
8478
|
const results = [{
|
|
@@ -8522,7 +8579,7 @@ function serializeSettings$1(settings) {
|
|
|
8522
8579
|
}
|
|
8523
8580
|
function writeOrMergeSettings(ctx, filePath, generated) {
|
|
8524
8581
|
const existing = ctx.read(filePath);
|
|
8525
|
-
if (existing) {
|
|
8582
|
+
if (existing !== void 0 && existing.trim() !== "") {
|
|
8526
8583
|
const parsed = parseClaudeSettings(existing);
|
|
8527
8584
|
if (!parsed) return {
|
|
8528
8585
|
filePath,
|
|
@@ -8851,16 +8908,10 @@ async function generateLefthook(ctx) {
|
|
|
8851
8908
|
}
|
|
8852
8909
|
//#endregion
|
|
8853
8910
|
//#region src/generators/vscode-settings.ts
|
|
8854
|
-
const SCHEMA_NPM_PATH = "@bensandee/config/schemas/forgejo-workflow.schema.json";
|
|
8855
8911
|
const SCHEMA_LOCAL_PATH = ".vscode/forgejo-workflow.schema.json";
|
|
8856
8912
|
const SETTINGS_PATH = ".vscode/settings.json";
|
|
8857
8913
|
const SCHEMA_GLOB = ".forgejo/workflows/*.{yml,yaml}";
|
|
8858
8914
|
const VscodeSettingsSchema = z.looseObject({ "yaml.schemas": z.record(z.string(), z.unknown()).default({}) });
|
|
8859
|
-
function readSchemaFromNodeModules(targetDir) {
|
|
8860
|
-
const candidate = path.join(targetDir, "node_modules", SCHEMA_NPM_PATH);
|
|
8861
|
-
if (!existsSync(candidate)) return void 0;
|
|
8862
|
-
return readFileSync(candidate, "utf-8");
|
|
8863
|
-
}
|
|
8864
8915
|
function serializeSettings(settings) {
|
|
8865
8916
|
return JSON.stringify(settings, null, 2) + "\n";
|
|
8866
8917
|
}
|
|
@@ -8927,15 +8978,7 @@ async function generateVscodeSettings(ctx) {
|
|
|
8927
8978
|
});
|
|
8928
8979
|
return results;
|
|
8929
8980
|
}
|
|
8930
|
-
const schemaContent = readSchemaFromNodeModules(ctx.targetDir);
|
|
8931
|
-
if (!schemaContent) {
|
|
8932
|
-
results.push({
|
|
8933
|
-
filePath: SCHEMA_LOCAL_PATH,
|
|
8934
|
-
action: "skipped",
|
|
8935
|
-
description: "Could not find @bensandee/config schema in node_modules"
|
|
8936
|
-
});
|
|
8937
|
-
return results;
|
|
8938
|
-
}
|
|
8981
|
+
const schemaContent = "{\n \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n \"$comment\": \"GitHub Actions workflow schema extended with Forgejo-specific properties\",\n \"additionalProperties\": false,\n \"definitions\": {\n \"architecture\": {\n \"type\": \"string\",\n \"enum\": [\"ARM32\", \"x64\", \"x86\"]\n },\n \"branch\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#onpushpull_requestbranchestags\",\n \"$ref\": \"#/definitions/globs\",\n \"description\": \"When using the push and pull_request events, you can configure a workflow to run on specific branches or tags. If you only define only tags or only branches, the workflow won't run for events affecting the undefined Git ref.\\nThe branches, branches-ignore, tags, and tags-ignore keywords accept glob patterns that use the * and ** wildcard characters to match more than one branch or tag name. For more information, see https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet.\\nThe patterns defined in branches and tags are evaluated against the Git ref's name. For example, defining the pattern mona/octocat in branches will match the refs/heads/mona/octocat Git ref. The pattern releases/** will match the refs/heads/releases/10 Git ref.\\nYou can use two types of filters to prevent a workflow from running on pushes and pull requests to tags and branches:\\n- branches or branches-ignore - You cannot use both the branches and branches-ignore filters for the same event in a workflow. Use the branches filter when you need to filter branches for positive matches and exclude branches. Use the branches-ignore filter when you only need to exclude branch names.\\n- tags or tags-ignore - You cannot use both the tags and tags-ignore filters for the same event in a workflow. Use the tags filter when you need to filter tags for positive matches and exclude tags. Use the tags-ignore filter when you only need to exclude tag names.\\nYou can exclude tags and branches using the ! character. The order that you define patterns matters.\\n- A matching negative pattern (prefixed with !) after a positive match will exclude the Git ref.\\n- A matching positive pattern after a negative match will include the Git ref again.\"\n },\n \"concurrency\": {\n \"type\": \"object\",\n \"properties\": {\n \"group\": {\n \"$comment\": \"https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#example-using-concurrency-to-cancel-any-in-progress-job-or-run-1\",\n \"description\": \"When a concurrent job or workflow is queued, if another job or workflow using the same concurrency group in the repository is in progress, the queued job or workflow will be pending. Any previously pending job or workflow in the concurrency group will be canceled.\",\n \"type\": \"string\"\n },\n \"cancel-in-progress\": {\n \"$comment\": \"https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#example-using-concurrency-to-cancel-any-in-progress-job-or-run-1\",\n \"description\": \"To cancel any currently running job or workflow in the same concurrency group, specify cancel-in-progress: true.\",\n \"oneOf\": [\n {\n \"type\": \"boolean\"\n },\n {\n \"$ref\": \"#/definitions/expressionSyntax\"\n }\n ]\n }\n },\n \"required\": [\"group\"],\n \"additionalProperties\": false\n },\n \"configuration\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"number\"\n },\n {\n \"type\": \"boolean\"\n },\n {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/configuration\"\n }\n },\n {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/definitions/configuration\"\n }\n }\n ]\n },\n \"container\": {\n \"type\": \"object\",\n \"properties\": {\n \"image\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idcontainerimage\",\n \"description\": \"The Docker image to use as the container to run the action. The value can be the Docker Hub image name or a registry name.\",\n \"type\": \"string\"\n },\n \"credentials\": {\n \"$comment\": \"https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idcontainercredentials\",\n \"description\": \"If the image's container registry requires authentication to pull the image, you can use credentials to set a map of the username and password. The credentials are the same values that you would provide to the `docker login` command.\",\n \"type\": \"object\",\n \"properties\": {\n \"username\": {\n \"type\": \"string\"\n },\n \"password\": {\n \"type\": \"string\"\n }\n }\n },\n \"env\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idcontainerenv\",\n \"$ref\": \"#/definitions/env\",\n \"description\": \"Sets an array of environment variables in the container.\"\n },\n \"ports\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idcontainerports\",\n \"description\": \"Sets an array of ports to expose on the container.\",\n \"type\": \"array\",\n \"items\": {\n \"oneOf\": [\n {\n \"type\": \"number\"\n },\n {\n \"type\": \"string\"\n }\n ]\n },\n \"minItems\": 1\n },\n \"volumes\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idcontainervolumes\",\n \"description\": \"Sets an array of volumes for the container to use. You can use volumes to share data between services or other steps in a job. You can specify named Docker volumes, anonymous Docker volumes, or bind mounts on the host.\\nTo specify a volume, you specify the source and destination path: <source>:<destinationPath>\\nThe <source> is a volume name or an absolute path on the host machine, and <destinationPath> is an absolute path in the container.\",\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"minItems\": 1\n },\n \"options\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idcontaineroptions\",\n \"description\": \"Additional Docker container resource options. For a list of options, see https://docs.docker.com/engine/reference/commandline/create/#options.\",\n \"type\": \"string\"\n }\n },\n \"required\": [\"image\"],\n \"additionalProperties\": false\n },\n \"defaults\": {\n \"type\": \"object\",\n \"properties\": {\n \"run\": {\n \"type\": \"object\",\n \"properties\": {\n \"shell\": {\n \"$ref\": \"#/definitions/shell\"\n },\n \"working-directory\": {\n \"$ref\": \"#/definitions/working-directory\"\n }\n },\n \"minProperties\": 1,\n \"additionalProperties\": false\n }\n },\n \"minProperties\": 1,\n \"additionalProperties\": false\n },\n \"permissions\": {\n \"$comment\": \"https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#permissions\",\n \"description\": \"You can modify the default permissions granted to the GITHUB_TOKEN, adding or removing access as required, so that you only allow the minimum required access.\",\n \"oneOf\": [\n {\n \"type\": \"string\",\n \"enum\": [\"read-all\", \"write-all\"]\n },\n {\n \"$ref\": \"#/definitions/permissions-event\"\n }\n ]\n },\n \"permissions-event\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"properties\": {\n \"actions\": {\n \"$ref\": \"#/definitions/permissions-level\"\n },\n \"artifact-metadata\": {\n \"$ref\": \"#/definitions/permissions-level\"\n },\n \"attestations\": {\n \"$ref\": \"#/definitions/permissions-level\"\n },\n \"checks\": {\n \"$ref\": \"#/definitions/permissions-level\"\n },\n \"contents\": {\n \"$ref\": \"#/definitions/permissions-level\"\n },\n \"deployments\": {\n \"$ref\": \"#/definitions/permissions-level\"\n },\n \"discussions\": {\n \"$ref\": \"#/definitions/permissions-level\"\n },\n \"id-token\": {\n \"$ref\": \"#/definitions/permissions-level\"\n },\n \"issues\": {\n \"$ref\": \"#/definitions/permissions-level\"\n },\n \"models\": {\n \"type\": \"string\",\n \"enum\": [\"read\", \"none\"]\n },\n \"packages\": {\n \"$ref\": \"#/definitions/permissions-level\"\n },\n \"pages\": {\n \"$ref\": \"#/definitions/permissions-level\"\n },\n \"pull-requests\": {\n \"$ref\": \"#/definitions/permissions-level\"\n },\n \"repository-projects\": {\n \"$ref\": \"#/definitions/permissions-level\"\n },\n \"security-events\": {\n \"$ref\": \"#/definitions/permissions-level\"\n },\n \"statuses\": {\n \"$ref\": \"#/definitions/permissions-level\"\n }\n }\n },\n \"permissions-level\": {\n \"type\": \"string\",\n \"enum\": [\"read\", \"write\", \"none\"]\n },\n \"env\": {\n \"$comment\": \"https://docs.github.com/en/actions/learn-github-actions/environment-variables\",\n \"description\": \"To set custom environment variables, you need to specify the variables in the workflow file. You can define environment variables for a step, job, or entire workflow using the jobs.<job_id>.steps[*].env, jobs.<job_id>.env, and env keywords. For more information, see https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepsenv\",\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"number\"\n },\n {\n \"type\": \"boolean\"\n }\n ]\n }\n },\n {\n \"$ref\": \"#/definitions/stringContainingExpressionSyntax\"\n }\n ]\n },\n \"environment\": {\n \"$comment\": \"https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idenvironment\",\n \"description\": \"The environment that the job references\",\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\n \"$comment\": \"https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#example-using-a-single-environment-name\",\n \"description\": \"The name of the environment configured in the repo.\",\n \"type\": \"string\"\n },\n \"url\": {\n \"$comment\": \"https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#example-using-environment-name-and-url\",\n \"description\": \"A deployment URL\",\n \"type\": \"string\"\n }\n },\n \"required\": [\"name\"],\n \"additionalProperties\": false\n },\n \"event\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows\",\n \"type\": \"string\",\n \"enum\": [\n \"branch_protection_rule\",\n \"check_run\",\n \"check_suite\",\n \"create\",\n \"delete\",\n \"deployment\",\n \"deployment_status\",\n \"discussion\",\n \"discussion_comment\",\n \"fork\",\n \"gollum\",\n \"issue_comment\",\n \"issues\",\n \"label\",\n \"merge_group\",\n \"milestone\",\n \"page_build\",\n \"project\",\n \"project_card\",\n \"project_column\",\n \"public\",\n \"pull_request\",\n \"pull_request_review\",\n \"pull_request_review_comment\",\n \"pull_request_target\",\n \"push\",\n \"registry_package\",\n \"release\",\n \"status\",\n \"watch\",\n \"workflow_call\",\n \"workflow_dispatch\",\n \"workflow_run\",\n \"repository_dispatch\"\n ]\n },\n \"eventObject\": {\n \"oneOf\": [\n {\n \"type\": \"object\"\n },\n {\n \"type\": \"null\"\n }\n ],\n \"additionalProperties\": true\n },\n \"expressionSyntax\": {\n \"$comment\": \"escape `{` and `}` in pattern to be unicode compatible (#1360)\",\n \"type\": \"string\",\n \"pattern\": \"^\\\\$\\\\{\\\\{(.|[\\r\\n])*\\\\}\\\\}$\"\n },\n \"stringContainingExpressionSyntax\": {\n \"$comment\": \"escape `{` and `}` in pattern to be unicode compatible (#1360)\",\n \"type\": \"string\",\n \"pattern\": \"^.*\\\\$\\\\{\\\\{(.|[\\r\\n])*\\\\}\\\\}.*$\"\n },\n \"globs\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\",\n \"minLength\": 1\n },\n \"minItems\": 1\n },\n \"machine\": {\n \"type\": \"string\",\n \"enum\": [\"linux\", \"macos\", \"windows\"]\n },\n \"name\": {\n \"type\": \"string\",\n \"pattern\": \"^[_a-zA-Z][a-zA-Z0-9_-]*$\"\n },\n \"path\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#onpushpull_requestpaths\",\n \"$ref\": \"#/definitions/globs\",\n \"description\": \"When using the push and pull_request events, you can configure a workflow to run when at least one file does not match paths-ignore or at least one modified file matches the configured paths. Path filters are not evaluated for pushes to tags.\\nThe paths-ignore and paths keywords accept glob patterns that use the * and ** wildcard characters to match more than one path name. For more information, see https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet.\\nYou can exclude paths using two types of filters. You cannot use both of these filters for the same event in a workflow.\\n- paths-ignore - Use the paths-ignore filter when you only need to exclude path names.\\n- paths - Use the paths filter when you need to filter paths for positive matches and exclude paths.\"\n },\n \"shell\": {\n \"$comment\": \"https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsshell\",\n \"description\": \"You can override the default shell settings in the runner's operating system using the shell keyword. You can use built-in shell keywords, or you can define a custom set of shell options.\",\n \"anyOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"$comment\": \"https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#custom-shell\",\n \"type\": \"string\",\n \"enum\": [\"bash\", \"pwsh\", \"python\", \"sh\", \"cmd\", \"powershell\"]\n }\n ]\n },\n \"snapshot\": {\n \"$comment\": \"https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idsnapshot\",\n \"description\": \"You can use `jobs.<job_id>.snapshot` to generate a custom image.\\nAdd the snapshot keyword to the job, using either the string syntax or mapping syntax as shown in https://docs.github.com/en/actions/how-tos/manage-runners/larger-runners/use-custom-images#generating-a-custom-image.\\nEach job that includes the snapshot keyword creates a separate image. To generate only one image or image version, include all workflow steps in a single job. Each successful run of a job that includes the snapshot keyword creates a new version of that image.\\nFor more information, see https://docs.github.com/en/actions/how-tos/manage-runners/larger-runners/use-custom-images.\",\n \"oneOf\": [\n {\n \"$comment\": \"https://docs.github.com/en/actions/how-tos/manage-runners/larger-runners/use-custom-images#string-syntax\",\n \"type\": \"string\",\n \"description\": \"You can use the string syntax with `snapshot` to define the image name. This method creates a new image or adds a new version to an existing image with the same name. You cannot specify a version number using this syntax.\"\n },\n {\n \"$comment\": \"https://docs.github.com/en/actions/how-tos/manage-runners/larger-runners/use-custom-images#mapping-syntax\",\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"description\": \"You can use the mapping syntax with `snapshot` to define both the `image-name` and the optional `version`. When you specify a major version, the minor versioning automatically increments if that major version already exists. Patch versions are not supported.\",\n \"properties\": {\n \"image-name\": {\n \"type\": \"string\"\n },\n \"version\": {\n \"$comment\": \"https://docs.github.com/en/actions/how-tos/manage-runners/larger-runners/use-custom-images#specifying-a-version-in-your-workflow\",\n \"type\": \"string\",\n \"pattern\": \"^\\\\d+(\\\\.\\\\d+|\\\\*)?$\"\n }\n },\n \"required\": [\"image-name\"]\n }\n ]\n },\n \"step\": {\n \"type\": \"object\",\n \"additionalProperties\": false,\n \"dependencies\": {\n \"working-directory\": [\"run\"],\n \"shell\": [\"run\"]\n },\n \"oneOf\": [\n {\n \"required\": [\"uses\"]\n },\n {\n \"required\": [\"run\"]\n }\n ],\n \"properties\": {\n \"id\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepsid\",\n \"description\": \"A unique identifier for the step. You can use the id to reference the step in contexts. For more information, see https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions.\",\n \"type\": \"string\"\n },\n \"if\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepsif\",\n \"description\": \"You can use the if conditional to prevent a step from running unless a condition is met. You can use any supported context and expression to create a conditional.\\nExpressions in an if conditional do not require the ${{ }} syntax. For more information, see https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions.\",\n \"type\": [\"boolean\", \"number\", \"string\"]\n },\n \"name\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepsname\",\n \"description\": \"A name for your step to display on GitHub.\",\n \"type\": \"string\"\n },\n \"uses\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepsuses\",\n \"description\": \"Selects an action to run as part of a step in your job. An action is a reusable unit of code. You can use an action defined in the same repository as the workflow, a public repository, or in a published Docker container image (https://hub.docker.com/).\\nWe strongly recommend that you include the version of the action you are using by specifying a Git ref, SHA, or Docker tag number. If you don't specify a version, it could break your workflows or cause unexpected behavior when the action owner publishes an update.\\n- Using the commit SHA of a released action version is the safest for stability and security.\\n- Using the specific major action version allows you to receive critical fixes and security patches while still maintaining compatibility. It also assures that your workflow should still work.\\n- Using the master branch of an action may be convenient, but if someone releases a new major version with a breaking change, your workflow could break.\\nSome actions require inputs that you must set using the with keyword. Review the action's README file to determine the inputs required.\\nActions are either JavaScript files or Docker containers. If the action you're using is a Docker container you must run the job in a Linux virtual environment. For more details, see https://help.github.com/en/articles/virtual-environments-for-github-actions.\",\n \"type\": \"string\"\n },\n \"run\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepsrun\",\n \"description\": \"Runs command-line programs using the operating system's shell. If you do not provide a name, the step name will default to the text specified in the run command.\\nCommands run using non-login shells by default. You can choose a different shell and customize the shell used to run commands. For more information, see https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#using-a-specific-shell.\\nEach run keyword represents a new process and shell in the virtual environment. When you provide multi-line commands, each line runs in the same shell.\",\n \"type\": \"string\"\n },\n \"working-directory\": {\n \"$ref\": \"#/definitions/working-directory\"\n },\n \"shell\": {\n \"$ref\": \"#/definitions/shell\"\n },\n \"with\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepswith\",\n \"$ref\": \"#/definitions/env\",\n \"description\": \"A map of the input parameters defined by the action. Each input parameter is a key/value pair. Input parameters are set as environment variables. The variable is prefixed with INPUT_ and converted to upper case.\",\n \"properties\": {\n \"args\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepswithargs\",\n \"type\": \"string\"\n },\n \"entrypoint\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepswithentrypoint\",\n \"type\": \"string\"\n }\n }\n },\n \"env\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepsenv\",\n \"$ref\": \"#/definitions/env\",\n \"description\": \"Sets environment variables for steps to use in the virtual environment. You can also set environment variables for the entire workflow or a job.\"\n },\n \"continue-on-error\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepscontinue-on-error\",\n \"description\": \"Prevents a job from failing when a step fails. Set to true to allow a job to pass when this step fails.\",\n \"oneOf\": [\n {\n \"type\": \"boolean\"\n },\n {\n \"$ref\": \"#/definitions/expressionSyntax\"\n }\n ],\n \"default\": false\n },\n \"timeout-minutes\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepstimeout-minutes\",\n \"description\": \"The maximum number of minutes to run the step before killing the process.\",\n \"oneOf\": [\n {\n \"type\": \"number\"\n },\n {\n \"$ref\": \"#/definitions/expressionSyntax\"\n }\n ]\n }\n }\n },\n \"types\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#onevent_nametypes\",\n \"description\": \"Selects the types of activity that will trigger a workflow run. Most GitHub events are triggered by more than one type of activity. For example, the event for the release resource is triggered when a release is published, unpublished, created, edited, deleted, or prereleased. The types keyword enables you to narrow down activity that causes the workflow to run. When only one activity type triggers a webhook event, the types keyword is unnecessary.\\nYou can use an array of event types. For more information about each event and their activity types, see https://help.github.com/en/articles/events-that-trigger-workflows#webhook-events.\",\n \"oneOf\": [\n {\n \"type\": \"array\",\n \"minItems\": 1\n },\n {\n \"type\": \"string\"\n }\n ]\n },\n \"working-directory\": {\n \"$comment\": \"https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsrun\",\n \"description\": \"Using the working-directory keyword, you can specify the working directory of where to run the command.\",\n \"type\": \"string\"\n },\n \"jobNeeds\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idneeds\",\n \"description\": \"Identifies any jobs that must complete successfully before this job will run. It can be a string or array of strings. If a job fails, all jobs that need it are skipped unless the jobs use a conditional statement that causes the job to continue.\",\n \"oneOf\": [\n {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/definitions/name\"\n },\n \"minItems\": 1\n },\n {\n \"$ref\": \"#/definitions/name\"\n }\n ]\n },\n \"matrix\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix\",\n \"description\": \"A build matrix is a set of different configurations of the virtual environment. For example you might run a job against more than one supported version of a language, operating system, or tool. Each configuration is a copy of the job that runs and reports a status.\\nYou can specify a matrix by supplying an array for the configuration options. For example, if the GitHub virtual environment supports Node.js versions 6, 8, and 10 you could specify an array of those versions in the matrix.\\nWhen you define a matrix of operating systems, you must set the required runs-on keyword to the operating system of the current job, rather than hard-coding the operating system name. To access the operating system name, you can use the matrix.os context parameter to set runs-on. For more information, see https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions.\",\n \"oneOf\": [\n {\n \"type\": \"object\",\n \"patternProperties\": {\n \"^(in|ex)clude$\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#example-including-configurations-in-a-matrix-build\",\n \"oneOf\": [\n {\n \"$ref\": \"#/definitions/expressionSyntax\"\n },\n {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/configuration\"\n }\n },\n \"minItems\": 1\n }\n ]\n }\n },\n \"additionalProperties\": {\n \"oneOf\": [\n {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/definitions/configuration\"\n },\n \"minItems\": 1\n },\n {\n \"$ref\": \"#/definitions/expressionSyntax\"\n }\n ]\n },\n \"minProperties\": 1\n },\n {\n \"$ref\": \"#/definitions/expressionSyntax\"\n }\n ]\n },\n \"reusableWorkflowCallJob\": {\n \"$comment\": \"https://docs.github.com/en/actions/learn-github-actions/reusing-workflows#calling-a-reusable-workflow\",\n \"description\": \"Each job must have an id to associate with the job. The key job_id is a string and its value is a map of the job's configuration data. You must replace <job_id> with a string that is unique to the jobs object. The <job_id> must start with a letter or _ and contain only alphanumeric characters, -, or _.\",\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idname\",\n \"description\": \"The name of the job displayed on GitHub.\",\n \"type\": \"string\"\n },\n \"needs\": {\n \"$ref\": \"#/definitions/jobNeeds\"\n },\n \"permissions\": {\n \"$ref\": \"#/definitions/permissions\"\n },\n \"if\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idif\",\n \"description\": \"You can use the if conditional to prevent a job from running unless a condition is met. You can use any supported context and expression to create a conditional.\\nExpressions in an if conditional do not require the ${{ }} syntax. For more information, see https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions.\",\n \"type\": [\"boolean\", \"number\", \"string\"]\n },\n \"uses\": {\n \"$comment\": \"https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_iduses\",\n \"description\": \"The location and version of a reusable workflow file to run as a job, of the form './{path/to}/{localfile}.yml' or '{owner}/{repo}/{path}/{filename}@{ref}'. {ref} can be a SHA, a release tag, or a branch name. Using the commit SHA is the safest for stability and security.\",\n \"type\": \"string\",\n \"pattern\": \"^(.+\\\\/)+(.+)\\\\.(ya?ml)(@.+)?$\"\n },\n \"with\": {\n \"$comment\": \"https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idwith\",\n \"$ref\": \"#/definitions/env\",\n \"description\": \"A map of inputs that are passed to the called workflow. Any inputs that you pass must match the input specifications defined in the called workflow. Unlike 'jobs.<job_id>.steps[*].with', the inputs you pass with 'jobs.<job_id>.with' are not be available as environment variables in the called workflow. Instead, you can reference the inputs by using the inputs context.\"\n },\n \"secrets\": {\n \"$comment\": \"https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idsecrets\",\n \"description\": \"When a job is used to call a reusable workflow, you can use 'secrets' to provide a map of secrets that are passed to the called workflow. Any secrets that you pass must match the names defined in the called workflow.\",\n \"oneOf\": [\n {\n \"$ref\": \"#/definitions/env\"\n },\n {\n \"type\": \"string\",\n \"enum\": [\"inherit\"]\n }\n ]\n },\n \"strategy\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstrategy\",\n \"description\": \"A strategy creates a build matrix for your jobs. You can define different variations of an environment to run each job in.\",\n \"type\": \"object\",\n \"properties\": {\n \"matrix\": {\n \"$ref\": \"#/definitions/matrix\"\n },\n \"fail-fast\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstrategyfail-fast\",\n \"description\": \"When set to true, GitHub cancels all in-progress jobs if any matrix job fails. Default: true\",\n \"type\": [\"boolean\", \"string\"],\n \"default\": true\n },\n \"max-parallel\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstrategymax-parallel\",\n \"description\": \"The maximum number of jobs that can run simultaneously when using a matrix job strategy. By default, GitHub will maximize the number of jobs run in parallel depending on the available runners on GitHub-hosted virtual machines.\",\n \"type\": [\"number\", \"string\"]\n }\n },\n \"required\": [\"matrix\"],\n \"additionalProperties\": false\n },\n \"concurrency\": {\n \"$comment\": \"https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idconcurrency\",\n \"description\": \"Concurrency ensures that only a single job or workflow using the same concurrency group will run at a time. A concurrency group can be any string or expression. The expression can use any context except for the secrets context. \\nYou can also specify concurrency at the workflow level. \\nWhen a concurrent job or workflow is queued, if another job or workflow using the same concurrency group in the repository is in progress, the queued job or workflow will be pending. Any previously pending job or workflow in the concurrency group will be canceled. To also cancel any currently running job or workflow in the same concurrency group, specify cancel-in-progress: true.\",\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"$ref\": \"#/definitions/concurrency\"\n }\n ]\n }\n },\n \"required\": [\"uses\"],\n \"additionalProperties\": false\n },\n \"normalJob\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_id\",\n \"description\": \"Each job must have an id to associate with the job. The key job_id is a string and its value is a map of the job's configuration data. You must replace <job_id> with a string that is unique to the jobs object. The <job_id> must start with a letter or _ and contain only alphanumeric characters, -, or _.\",\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idname\",\n \"description\": \"The name of the job displayed on GitHub.\",\n \"type\": \"string\"\n },\n \"needs\": {\n \"$ref\": \"#/definitions/jobNeeds\"\n },\n \"snapshot\": {\n \"$ref\": \"#/definitions/snapshot\"\n },\n \"permissions\": {\n \"$ref\": \"#/definitions/permissions\"\n },\n \"runs-on\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idruns-on\",\n \"description\": \"The type of machine to run the job on. The machine can be either a GitHub-hosted runner, or a self-hosted runner.\",\n \"anyOf\": [\n {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#github-hosted-runners\",\n \"type\": \"string\"\n },\n {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#self-hosted-runners\",\n \"type\": \"array\",\n \"anyOf\": [\n {\n \"items\": [\n {\n \"type\": \"string\"\n }\n ],\n \"minItems\": 1,\n \"additionalItems\": {\n \"type\": \"string\"\n }\n }\n ]\n },\n {\n \"$comment\": \"https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#choosing-runners-in-a-group\",\n \"type\": \"object\",\n \"properties\": {\n \"group\": {\n \"type\": \"string\"\n },\n \"labels\": {\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n }\n }\n ]\n }\n }\n },\n {\n \"$ref\": \"#/definitions/stringContainingExpressionSyntax\"\n },\n {\n \"$ref\": \"#/definitions/expressionSyntax\"\n }\n ]\n },\n \"environment\": {\n \"$comment\": \"https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idenvironment\",\n \"description\": \"The environment that the job references.\",\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"$ref\": \"#/definitions/environment\"\n }\n ]\n },\n \"outputs\": {\n \"$comment\": \"https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idoutputs\",\n \"description\": \"A map of outputs for a job. Job outputs are available to all downstream jobs that depend on this job.\",\n \"type\": \"object\",\n \"additionalProperties\": {\n \"type\": \"string\"\n },\n \"minProperties\": 1\n },\n \"env\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idenv\",\n \"$ref\": \"#/definitions/env\",\n \"description\": \"A map of environment variables that are available to all steps in the job.\"\n },\n \"defaults\": {\n \"$comment\": \"https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_iddefaults\",\n \"$ref\": \"#/definitions/defaults\",\n \"description\": \"A map of default settings that will apply to all steps in the job.\"\n },\n \"if\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idif\",\n \"description\": \"You can use the if conditional to prevent a job from running unless a condition is met. You can use any supported context and expression to create a conditional.\\nExpressions in an if conditional do not require the ${{ }} syntax. For more information, see https://help.github.com/en/articles/contexts-and-expression-syntax-for-github-actions.\",\n \"type\": [\"boolean\", \"number\", \"string\"]\n },\n \"steps\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idsteps\",\n \"description\": \"A job contains a sequence of tasks called steps. Steps can run commands, run setup tasks, or run an action in your repository, a public repository, or an action published in a Docker registry. Not all steps run actions, but all actions run as a step. Each step runs in its own process in the virtual environment and has access to the workspace and filesystem. Because steps run in their own process, changes to environment variables are not preserved between steps. GitHub provides built-in steps to set up and complete a job.\\nMust contain either `uses` or `run`\\n\",\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/definitions/step\"\n },\n \"minItems\": 1\n },\n \"timeout-minutes\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idtimeout-minutes\",\n \"description\": \"The maximum number of minutes to let a workflow run before GitHub automatically cancels it. Default: 360\",\n \"oneOf\": [\n {\n \"type\": \"number\"\n },\n {\n \"$ref\": \"#/definitions/expressionSyntax\"\n }\n ],\n \"default\": 360\n },\n \"strategy\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstrategy\",\n \"description\": \"A strategy creates a build matrix for your jobs. You can define different variations of an environment to run each job in.\",\n \"type\": \"object\",\n \"properties\": {\n \"matrix\": {\n \"$ref\": \"#/definitions/matrix\"\n },\n \"fail-fast\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstrategyfail-fast\",\n \"description\": \"When set to true, GitHub cancels all in-progress jobs if any matrix job fails. Default: true\",\n \"type\": [\"boolean\", \"string\"],\n \"default\": true\n },\n \"max-parallel\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idstrategymax-parallel\",\n \"description\": \"The maximum number of jobs that can run simultaneously when using a matrix job strategy. By default, GitHub will maximize the number of jobs run in parallel depending on the available runners on GitHub-hosted virtual machines.\",\n \"type\": [\"number\", \"string\"]\n }\n },\n \"required\": [\"matrix\"],\n \"additionalProperties\": false\n },\n \"continue-on-error\": {\n \"$comment\": \"https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idcontinue-on-error\",\n \"description\": \"Prevents a workflow run from failing when a job fails. Set to true to allow a workflow run to pass when this job fails.\",\n \"oneOf\": [\n {\n \"type\": \"boolean\"\n },\n {\n \"$ref\": \"#/definitions/expressionSyntax\"\n }\n ]\n },\n \"container\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idcontainer\",\n \"description\": \"A container to run any steps in a job that don't already specify a container. If you have steps that use both script and container actions, the container actions will run as sibling containers on the same network with the same volume mounts.\\nIf you do not set a container, all steps will run directly on the host specified by runs-on unless a step refers to an action configured to run in a container.\",\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"$ref\": \"#/definitions/container\"\n }\n ]\n },\n \"services\": {\n \"$comment\": \"https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobsjob_idservices\",\n \"description\": \"Additional containers to host services for a job in a workflow. These are useful for creating databases or cache services like redis. The runner on the virtual machine will automatically create a network and manage the life cycle of the service containers.\\nWhen you use a service container for a job or your step uses container actions, you don't need to set port information to access the service. Docker automatically exposes all ports between containers on the same network.\\nWhen both the job and the action run in a container, you can directly reference the container by its hostname. The hostname is automatically mapped to the service name.\\nWhen a step does not use a container action, you must access the service using localhost and bind the ports.\",\n \"type\": \"object\",\n \"additionalProperties\": {\n \"$ref\": \"#/definitions/container\"\n }\n },\n \"concurrency\": {\n \"$comment\": \"https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idconcurrency\",\n \"description\": \"Concurrency ensures that only a single job or workflow using the same concurrency group will run at a time. A concurrency group can be any string or expression. The expression can use any context except for the secrets context. \\nYou can also specify concurrency at the workflow level. \\nWhen a concurrent job or workflow is queued, if another job or workflow using the same concurrency group in the repository is in progress, the queued job or workflow will be pending. Any previously pending job or workflow in the concurrency group will be canceled. To also cancel any currently running job or workflow in the same concurrency group, specify cancel-in-progress: true.\",\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"$ref\": \"#/definitions/concurrency\"\n }\n ]\n }\n },\n \"required\": [\"runs-on\"],\n \"additionalProperties\": false\n },\n \"workflowDispatchInput\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/metadata-syntax-for-github-actions#inputsinput_id\",\n \"description\": \"A string identifier to associate with the input. The value of <input_id> is a map of the input's metadata. The <input_id> must be a unique identifier within the inputs object. The <input_id> must start with a letter or _ and contain only alphanumeric characters, -, or _.\",\n \"type\": \"object\",\n \"properties\": {\n \"description\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/metadata-syntax-for-github-actions#inputsinput_iddescription\",\n \"description\": \"A string description of the input parameter.\",\n \"type\": \"string\"\n },\n \"deprecationMessage\": {\n \"description\": \"A string shown to users using the deprecated input.\",\n \"type\": \"string\"\n },\n \"required\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/metadata-syntax-for-github-actions#inputsinput_idrequired\",\n \"description\": \"A boolean to indicate whether the action requires the input parameter. Set to true when the parameter is required.\",\n \"type\": \"boolean\"\n },\n \"default\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/metadata-syntax-for-github-actions#inputsinput_iddefault\",\n \"description\": \"A string representing the default value. The default value is used when an input parameter isn't specified in a workflow file.\"\n },\n \"type\": {\n \"$comment\": \"https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onworkflow_dispatchinputsinput_idtype\",\n \"description\": \"A string representing the type of the input.\",\n \"type\": \"string\",\n \"enum\": [\"string\", \"choice\", \"boolean\", \"number\", \"environment\"]\n },\n \"options\": {\n \"$comment\": \"https://github.blog/changelog/2021-11-10-github-actions-input-types-for-manual-workflows\",\n \"description\": \"The options of the dropdown list, if the type is a choice.\",\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"minItems\": 1\n }\n },\n \"allOf\": [\n {\n \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": \"string\"\n }\n },\n \"required\": [\"type\"]\n },\n \"then\": {\n \"properties\": {\n \"default\": {\n \"type\": \"string\"\n }\n }\n }\n },\n {\n \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": \"boolean\"\n }\n },\n \"required\": [\"type\"]\n },\n \"then\": {\n \"properties\": {\n \"default\": {\n \"type\": \"boolean\"\n }\n }\n }\n },\n {\n \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": \"number\"\n }\n },\n \"required\": [\"type\"]\n },\n \"then\": {\n \"properties\": {\n \"default\": {\n \"type\": \"number\"\n }\n }\n }\n },\n {\n \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": \"environment\"\n }\n },\n \"required\": [\"type\"]\n },\n \"then\": {\n \"properties\": {\n \"default\": {\n \"type\": \"string\"\n }\n }\n }\n },\n {\n \"if\": {\n \"properties\": {\n \"type\": {\n \"const\": \"choice\"\n }\n },\n \"required\": [\"type\"]\n },\n \"then\": {\n \"required\": [\"options\"]\n }\n }\n ],\n \"additionalProperties\": false\n }\n },\n \"properties\": {\n \"name\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#name\",\n \"description\": \"The name of your workflow. GitHub displays the names of your workflows on your repository's actions page. If you omit this field, GitHub sets the name to the workflow's filename.\",\n \"type\": \"string\"\n },\n \"on\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#on\",\n \"description\": \"The name of the GitHub event that triggers the workflow. You can provide a single event string, array of events, array of event types, or an event configuration map that schedules a workflow or restricts the execution of a workflow to specific files, tags, or branch changes. For a list of available events, see https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows.\",\n \"oneOf\": [\n {\n \"$ref\": \"#/definitions/event\"\n },\n {\n \"type\": \"array\",\n \"items\": {\n \"$ref\": \"#/definitions/event\"\n },\n \"minItems\": 1\n },\n {\n \"type\": \"object\",\n \"properties\": {\n \"branch_protection_rule\": {\n \"$comment\": \"https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#branch_protection_rule\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime the branch_protection_rule event occurs. More than one activity type triggers this event.\",\n \"properties\": {\n \"types\": {\n \"$ref\": \"#/definitions/types\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\"created\", \"edited\", \"deleted\"]\n },\n \"default\": [\"created\", \"edited\", \"deleted\"]\n }\n }\n },\n \"check_run\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#check-run-event-check_run\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime the check_run event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/checks/runs.\",\n \"properties\": {\n \"types\": {\n \"$ref\": \"#/definitions/types\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\"created\", \"rerequested\", \"completed\", \"requested_action\"]\n },\n \"default\": [\"created\", \"rerequested\", \"completed\", \"requested_action\"]\n }\n }\n },\n \"check_suite\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#check-suite-event-check_suite\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime the check_suite event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/checks/suites/.\",\n \"properties\": {\n \"types\": {\n \"$ref\": \"#/definitions/types\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\"completed\", \"requested\", \"rerequested\"]\n },\n \"default\": [\"completed\", \"requested\", \"rerequested\"]\n }\n }\n },\n \"create\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#create-event-create\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime someone creates a branch or tag, which triggers the create event. For information about the REST API, see https://developer.github.com/v3/git/refs/#create-a-reference.\"\n },\n \"delete\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#delete-event-delete\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime someone deletes a branch or tag, which triggers the delete event. For information about the REST API, see https://developer.github.com/v3/git/refs/#delete-a-reference.\"\n },\n \"deployment\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#deployment-event-deployment\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime someone creates a deployment, which triggers the deployment event. Deployments created with a commit SHA may not have a Git ref. For information about the REST API, see https://developer.github.com/v3/repos/deployments/.\"\n },\n \"deployment_status\": {\n \"$comment\": \"https://docs.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime a third party provides a deployment status, which triggers the deployment_status event. Deployments created with a commit SHA may not have a Git ref. For information about the REST API, see https://developer.github.com/v3/repos/deployments/#create-a-deployment-status.\"\n },\n \"discussion\": {\n \"$comment\": \"https://docs.github.com/en/actions/reference/events-that-trigger-workflows#discussion\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime the discussion event occurs. More than one activity type triggers this event. For information about the GraphQL API, see https://docs.github.com/en/graphql/guides/using-the-graphql-api-for-discussions\",\n \"properties\": {\n \"types\": {\n \"$ref\": \"#/definitions/types\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"created\",\n \"edited\",\n \"deleted\",\n \"transferred\",\n \"pinned\",\n \"unpinned\",\n \"labeled\",\n \"unlabeled\",\n \"locked\",\n \"unlocked\",\n \"category_changed\",\n \"answered\",\n \"unanswered\"\n ]\n },\n \"default\": [\n \"created\",\n \"edited\",\n \"deleted\",\n \"transferred\",\n \"pinned\",\n \"unpinned\",\n \"labeled\",\n \"unlabeled\",\n \"locked\",\n \"unlocked\",\n \"category_changed\",\n \"answered\",\n \"unanswered\"\n ]\n }\n }\n },\n \"discussion_comment\": {\n \"$comment\": \"https://docs.github.com/en/actions/reference/events-that-trigger-workflows#discussion_comment\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime the discussion_comment event occurs. More than one activity type triggers this event. For information about the GraphQL API, see https://docs.github.com/en/graphql/guides/using-the-graphql-api-for-discussions\",\n \"properties\": {\n \"types\": {\n \"$ref\": \"#/definitions/types\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\"created\", \"edited\", \"deleted\"]\n },\n \"default\": [\"created\", \"edited\", \"deleted\"]\n }\n }\n },\n \"fork\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#fork-event-fork\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime when someone forks a repository, which triggers the fork event. For information about the REST API, see https://developer.github.com/v3/repos/forks/#create-a-fork.\"\n },\n \"gollum\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#gollum-event-gollum\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow when someone creates or updates a Wiki page, which triggers the gollum event.\"\n },\n \"issue_comment\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#issue-comment-event-issue_comment\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime the issue_comment event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/issues/comments/.\",\n \"properties\": {\n \"types\": {\n \"$ref\": \"#/definitions/types\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\"created\", \"edited\", \"deleted\"]\n },\n \"default\": [\"created\", \"edited\", \"deleted\"]\n }\n }\n },\n \"issues\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#issues-event-issues\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime the issues event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/issues.\",\n \"properties\": {\n \"types\": {\n \"$ref\": \"#/definitions/types\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"opened\",\n \"edited\",\n \"deleted\",\n \"transferred\",\n \"pinned\",\n \"unpinned\",\n \"closed\",\n \"reopened\",\n \"assigned\",\n \"unassigned\",\n \"labeled\",\n \"unlabeled\",\n \"locked\",\n \"unlocked\",\n \"milestoned\",\n \"demilestoned\"\n ]\n },\n \"default\": [\n \"opened\",\n \"edited\",\n \"deleted\",\n \"transferred\",\n \"pinned\",\n \"unpinned\",\n \"closed\",\n \"reopened\",\n \"assigned\",\n \"unassigned\",\n \"labeled\",\n \"unlabeled\",\n \"locked\",\n \"unlocked\",\n \"milestoned\",\n \"demilestoned\"\n ]\n }\n }\n },\n \"label\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#label-event-label\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime the label event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/issues/labels/.\",\n \"properties\": {\n \"types\": {\n \"$ref\": \"#/definitions/types\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\"created\", \"edited\", \"deleted\"]\n },\n \"default\": [\"created\", \"edited\", \"deleted\"]\n }\n }\n },\n \"merge_group\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#merge_group\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow when a pull request is added to a merge queue, which adds the pull request to a merge group. For information about the merge queue, see https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/merging-a-pull-request-with-a-merge-queue .\",\n \"properties\": {\n \"types\": {\n \"$ref\": \"#/definitions/types\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\"checks_requested\"]\n },\n \"default\": [\"checks_requested\"]\n }\n }\n },\n \"milestone\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#milestone-event-milestone\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime the milestone event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/issues/milestones/.\",\n \"properties\": {\n \"types\": {\n \"$ref\": \"#/definitions/types\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\"created\", \"closed\", \"opened\", \"edited\", \"deleted\"]\n },\n \"default\": [\"created\", \"closed\", \"opened\", \"edited\", \"deleted\"]\n }\n }\n },\n \"page_build\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#page-build-event-page_build\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime someone pushes to a GitHub Pages-enabled branch, which triggers the page_build event. For information about the REST API, see https://developer.github.com/v3/repos/pages/.\"\n },\n \"project\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#project-event-project\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime the project event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/projects/.\",\n \"properties\": {\n \"types\": {\n \"$ref\": \"#/definitions/types\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\"created\", \"updated\", \"closed\", \"reopened\", \"edited\", \"deleted\"]\n },\n \"default\": [\"created\", \"updated\", \"closed\", \"reopened\", \"edited\", \"deleted\"]\n }\n }\n },\n \"project_card\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#project-card-event-project_card\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime the project_card event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/projects/cards.\",\n \"properties\": {\n \"types\": {\n \"$ref\": \"#/definitions/types\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\"created\", \"moved\", \"converted\", \"edited\", \"deleted\"]\n },\n \"default\": [\"created\", \"moved\", \"converted\", \"edited\", \"deleted\"]\n }\n }\n },\n \"project_column\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#project-column-event-project_column\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime the project_column event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/projects/columns.\",\n \"properties\": {\n \"types\": {\n \"$ref\": \"#/definitions/types\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\"created\", \"updated\", \"moved\", \"deleted\"]\n },\n \"default\": [\"created\", \"updated\", \"moved\", \"deleted\"]\n }\n }\n },\n \"public\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#public-event-public\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime someone makes a private repository public, which triggers the public event. For information about the REST API, see https://developer.github.com/v3/repos/#edit.\"\n },\n \"pull_request\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#pull-request-event-pull_request\",\n \"description\": \"Runs your workflow anytime the pull_request event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/pulls.\\nNote: Workflows do not run on private base repositories when you open a pull request from a forked repository.\\nWhen you create a pull request from a forked repository to the base repository, GitHub sends the pull_request event to the base repository and no pull request events occur on the forked repository.\\nWorkflows don't run on forked repositories by default. You must enable GitHub Actions in the Actions tab of the forked repository.\\nThe permissions for the GITHUB_TOKEN in forked repositories is read-only. For more information about the GITHUB_TOKEN, see https://help.github.com/en/articles/virtual-environments-for-github-actions.\",\n \"oneOf\": [\n {\n \"type\": \"null\"\n },\n {\n \"allOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"types\": {\n \"$ref\": \"#/definitions/types\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"assigned\",\n \"unassigned\",\n \"labeled\",\n \"unlabeled\",\n \"opened\",\n \"edited\",\n \"closed\",\n \"reopened\",\n \"synchronize\",\n \"converted_to_draft\",\n \"ready_for_review\",\n \"locked\",\n \"unlocked\",\n \"milestoned\",\n \"demilestoned\",\n \"review_requested\",\n \"review_request_removed\",\n \"auto_merge_enabled\",\n \"auto_merge_disabled\",\n \"enqueued\",\n \"dequeued\"\n ]\n },\n \"default\": [\"opened\", \"synchronize\", \"reopened\"]\n },\n \"branches\": {\n \"$ref\": \"#/definitions/branch\"\n },\n \"branches-ignore\": {\n \"$ref\": \"#/definitions/branch\"\n },\n \"tags\": {\n \"$ref\": \"#/definitions/branch\"\n },\n \"tags-ignore\": {\n \"$ref\": \"#/definitions/branch\"\n },\n \"paths\": {\n \"$ref\": \"#/definitions/path\"\n },\n \"paths-ignore\": {\n \"$ref\": \"#/definitions/path\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"not\": {\n \"required\": [\"branches\", \"branches-ignore\"]\n }\n },\n {\n \"not\": {\n \"required\": [\"tags\", \"tags-ignore\"]\n }\n },\n {\n \"not\": {\n \"required\": [\"paths\", \"paths-ignore\"]\n }\n }\n ]\n }\n ]\n },\n \"pull_request_review\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#pull-request-review-event-pull_request_review\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime the pull_request_review event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/pulls/reviews.\\nNote: Workflows do not run on private base repositories when you open a pull request from a forked repository.\\nWhen you create a pull request from a forked repository to the base repository, GitHub sends the pull_request event to the base repository and no pull request events occur on the forked repository.\\nWorkflows don't run on forked repositories by default. You must enable GitHub Actions in the Actions tab of the forked repository.\\nThe permissions for the GITHUB_TOKEN in forked repositories is read-only. For more information about the GITHUB_TOKEN, see https://help.github.com/en/articles/virtual-environments-for-github-actions.\",\n \"properties\": {\n \"types\": {\n \"$ref\": \"#/definitions/types\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\"submitted\", \"edited\", \"dismissed\"]\n },\n \"default\": [\"submitted\", \"edited\", \"dismissed\"]\n }\n }\n },\n \"pull_request_review_comment\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#pull-request-review-comment-event-pull_request_review_comment\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime a comment on a pull request's unified diff is modified, which triggers the pull_request_review_comment event. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/pulls/comments.\\nNote: Workflows do not run on private base repositories when you open a pull request from a forked repository.\\nWhen you create a pull request from a forked repository to the base repository, GitHub sends the pull_request event to the base repository and no pull request events occur on the forked repository.\\nWorkflows don't run on forked repositories by default. You must enable GitHub Actions in the Actions tab of the forked repository.\\nThe permissions for the GITHUB_TOKEN in forked repositories is read-only. For more information about the GITHUB_TOKEN, see https://help.github.com/en/articles/virtual-environments-for-github-actions.\",\n \"properties\": {\n \"types\": {\n \"$ref\": \"#/definitions/types\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\"created\", \"edited\", \"deleted\"]\n },\n \"default\": [\"created\", \"edited\", \"deleted\"]\n }\n }\n },\n \"pull_request_target\": {\n \"$comment\": \"https://docs.github.com/en/actions/reference/events-that-trigger-workflows#pull_request_target\",\n \"description\": \"This event is similar to pull_request, except that it runs in the context of the base repository of the pull request, rather than in the merge commit. This means that you can more safely make your secrets available to the workflows triggered by the pull request, because only workflows defined in the commit on the base repository are run. For example, this event allows you to create workflows that label and comment on pull requests, based on the contents of the event payload.\",\n \"oneOf\": [\n {\n \"type\": \"null\"\n },\n {\n \"allOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"types\": {\n \"$ref\": \"#/definitions/types\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"assigned\",\n \"unassigned\",\n \"labeled\",\n \"unlabeled\",\n \"opened\",\n \"edited\",\n \"closed\",\n \"reopened\",\n \"synchronize\",\n \"converted_to_draft\",\n \"ready_for_review\",\n \"locked\",\n \"unlocked\",\n \"review_requested\",\n \"review_request_removed\",\n \"auto_merge_enabled\",\n \"auto_merge_disabled\"\n ]\n },\n \"default\": [\"opened\", \"synchronize\", \"reopened\"]\n },\n \"branches\": {\n \"$ref\": \"#/definitions/branch\"\n },\n \"branches-ignore\": {\n \"$ref\": \"#/definitions/branch\"\n },\n \"tags\": {\n \"$ref\": \"#/definitions/branch\"\n },\n \"tags-ignore\": {\n \"$ref\": \"#/definitions/branch\"\n },\n \"paths\": {\n \"$ref\": \"#/definitions/path\"\n },\n \"paths-ignore\": {\n \"$ref\": \"#/definitions/path\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"not\": {\n \"required\": [\"branches\", \"branches-ignore\"]\n }\n },\n {\n \"not\": {\n \"required\": [\"tags\", \"tags-ignore\"]\n }\n },\n {\n \"not\": {\n \"required\": [\"paths\", \"paths-ignore\"]\n }\n }\n ]\n }\n ]\n },\n \"push\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#push-event-push\",\n \"description\": \"Runs your workflow when someone pushes to a repository branch, which triggers the push event.\\nNote: The webhook payload available to GitHub Actions does not include the added, removed, and modified attributes in the commit object. You can retrieve the full commit object using the REST API. For more information, see https://developer.github.com/v3/repos/commits/#get-a-single-commit.\",\n \"oneOf\": [\n {\n \"type\": \"null\"\n },\n {\n \"allOf\": [\n {\n \"type\": \"object\",\n \"properties\": {\n \"branches\": {\n \"$ref\": \"#/definitions/branch\"\n },\n \"branches-ignore\": {\n \"$ref\": \"#/definitions/branch\"\n },\n \"tags\": {\n \"$ref\": \"#/definitions/branch\"\n },\n \"tags-ignore\": {\n \"$ref\": \"#/definitions/branch\"\n },\n \"paths\": {\n \"$ref\": \"#/definitions/path\"\n },\n \"paths-ignore\": {\n \"$ref\": \"#/definitions/path\"\n }\n },\n \"additionalProperties\": false\n },\n {\n \"not\": {\n \"required\": [\"branches\", \"branches-ignore\"]\n }\n },\n {\n \"not\": {\n \"required\": [\"tags\", \"tags-ignore\"]\n }\n },\n {\n \"not\": {\n \"required\": [\"paths\", \"paths-ignore\"]\n }\n }\n ]\n }\n ]\n },\n \"registry_package\": {\n \"$comment\": \"https://help.github.com/en/actions/reference/events-that-trigger-workflows#registry-package-event-registry_package\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime a package is published or updated. For more information, see https://help.github.com/en/github/managing-packages-with-github-packages.\",\n \"properties\": {\n \"types\": {\n \"$ref\": \"#/definitions/types\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\"published\", \"updated\"]\n },\n \"default\": [\"published\", \"updated\"]\n }\n }\n },\n \"release\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#release-event-release\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime the release event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/repos/releases/ in the GitHub Developer documentation.\",\n \"properties\": {\n \"types\": {\n \"$ref\": \"#/definitions/types\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\n \"published\",\n \"unpublished\",\n \"created\",\n \"edited\",\n \"deleted\",\n \"prereleased\",\n \"released\"\n ]\n },\n \"default\": [\n \"published\",\n \"unpublished\",\n \"created\",\n \"edited\",\n \"deleted\",\n \"prereleased\",\n \"released\"\n ]\n }\n }\n },\n \"status\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#status-event-status\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime the status of a Git commit changes, which triggers the status event. For information about the REST API, see https://developer.github.com/v3/repos/statuses/.\"\n },\n \"watch\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#watch-event-watch\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"Runs your workflow anytime the watch event occurs. More than one activity type triggers this event. For information about the REST API, see https://developer.github.com/v3/activity/starring/.\"\n },\n \"workflow_call\": {\n \"$comment\": \"https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#workflow_call\",\n \"description\": \"Allows workflows to be reused by other workflows.\",\n \"properties\": {\n \"inputs\": {\n \"$comment\": \"https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#onworkflow_callinputs\",\n \"description\": \"When using the workflow_call keyword, you can optionally specify inputs that are passed to the called workflow from the caller workflow.\",\n \"type\": \"object\",\n \"patternProperties\": {\n \"^[_a-zA-Z][a-zA-Z0-9_-]*$\": {\n \"$comment\": \"https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#inputsinput_id\",\n \"description\": \"A string identifier to associate with the input. The value of <input_id> is a map of the input's metadata. The <input_id> must be a unique identifier within the inputs object. The <input_id> must start with a letter or _ and contain only alphanumeric characters, -, or _.\",\n \"type\": \"object\",\n \"properties\": {\n \"description\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/metadata-syntax-for-github-actions#inputsinput_iddescription\",\n \"description\": \"A string description of the input parameter.\",\n \"type\": \"string\"\n },\n \"required\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/metadata-syntax-for-github-actions#inputsinput_idrequired\",\n \"description\": \"A boolean to indicate whether the action requires the input parameter. Set to true when the parameter is required.\",\n \"type\": \"boolean\"\n },\n \"type\": {\n \"$comment\": \"https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#onworkflow_callinput_idtype\",\n \"description\": \"Required if input is defined for the on.workflow_call keyword. The value of this parameter is a string specifying the data type of the input. This must be one of: boolean, number, or string.\",\n \"type\": \"string\",\n \"enum\": [\"boolean\", \"number\", \"string\"]\n },\n \"default\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/metadata-syntax-for-github-actions#inputsinput_iddefault\",\n \"description\": \"The default value is used when an input parameter isn't specified in a workflow file.\",\n \"type\": [\"boolean\", \"number\", \"string\"]\n }\n },\n \"required\": [\"type\"],\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"outputs\": {\n \"$comment\": \"https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#onworkflow_calloutputs\",\n \"description\": \"When using the workflow_call keyword, you can optionally specify inputs that are passed to the called workflow from the caller workflow.\",\n \"type\": \"object\",\n \"patternProperties\": {\n \"^[_a-zA-Z][a-zA-Z0-9_-]*$\": {\n \"$comment\": \"https://docs.github.com/en/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions#outputsoutput_id\",\n \"description\": \"A string identifier to associate with the output. The value of <output_id> is a map of the output's metadata. The <output_id> must be a unique identifier within the outputs object. The <output_id> must start with a letter or _ and contain only alphanumeric characters, -, or _.\",\n \"type\": \"object\",\n \"properties\": {\n \"description\": {\n \"$comment\": \"https://docs.github.com/en/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions#outputsoutput_iddescription\",\n \"description\": \"A string description of the output parameter.\",\n \"type\": \"string\"\n },\n \"value\": {\n \"$comment\": \"https://docs.github.com/en/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions#outputsoutput_idvalue\",\n \"description\": \"The value that the output parameter will be mapped to. You can set this to a string or an expression with context. For example, you can use the steps context to set the value of an output to the output value of a step.\",\n \"type\": \"string\"\n }\n },\n \"required\": [\"value\"],\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"secrets\": {\n \"$comment\": \"https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#onworkflow_callsecrets\",\n \"description\": \"A map of the secrets that can be used in the called workflow. Within the called workflow, you can use the secrets context to refer to a secret.\",\n \"patternProperties\": {\n \"^[_a-zA-Z][a-zA-Z0-9_-]*$\": {\n \"$comment\": \"https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#onworkflow_callsecretssecret_id\",\n \"description\": \"A string identifier to associate with the secret.\",\n \"properties\": {\n \"description\": {\n \"description\": \"A string description of the secret parameter.\",\n \"type\": \"string\"\n },\n \"required\": {\n \"$comment\": \"https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#onworkflow_callsecretssecret_idrequired\",\n \"description\": \"A boolean specifying whether the secret must be supplied.\",\n \"type\": \"boolean\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n }\n }\n },\n \"workflow_dispatch\": {\n \"$comment\": \"https://github.blog/changelog/2020-07-06-github-actions-manual-triggers-with-workflow_dispatch/\",\n \"description\": \"You can now create workflows that are manually triggered with the new workflow_dispatch event. You will then see a 'Run workflow' button on the Actions tab, enabling you to easily trigger a run.\",\n \"properties\": {\n \"inputs\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/metadata-syntax-for-github-actions#inputs\",\n \"description\": \"Input parameters allow you to specify data that the action expects to use during runtime. GitHub stores input parameters as environment variables. Input ids with uppercase letters are converted to lowercase during runtime. We recommended using lowercase input ids.\",\n \"type\": \"object\",\n \"patternProperties\": {\n \"^[_a-zA-Z][a-zA-Z0-9_-]*$\": {\n \"$ref\": \"#/definitions/workflowDispatchInput\"\n }\n },\n \"additionalProperties\": false\n }\n },\n \"additionalProperties\": false\n },\n \"workflow_run\": {\n \"$comment\": \"https://docs.github.com/en/actions/reference/events-that-trigger-workflows#workflow_run\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"This event occurs when a workflow run is requested or completed, and allows you to execute a workflow based on the finished result of another workflow. For example, if your pull_request workflow generates build artifacts, you can create a new workflow that uses workflow_run to analyze the results and add a comment to the original pull request.\",\n \"properties\": {\n \"types\": {\n \"$ref\": \"#/definitions/types\",\n \"items\": {\n \"type\": \"string\",\n \"enum\": [\"requested\", \"completed\", \"in_progress\"]\n },\n \"default\": [\"requested\", \"completed\"]\n },\n \"workflows\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"string\"\n },\n \"minItems\": 1\n }\n },\n \"patternProperties\": {\n \"^branches(-ignore)?$\": {}\n }\n },\n \"repository_dispatch\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#external-events-repository_dispatch\",\n \"$ref\": \"#/definitions/eventObject\",\n \"description\": \"You can use the GitHub API to trigger a webhook event called repository_dispatch when you want to trigger a workflow for activity that happens outside of GitHub. For more information, see https://developer.github.com/v3/repos/#create-a-repository-dispatch-event.\\nTo trigger the custom repository_dispatch webhook event, you must send a POST request to a GitHub API endpoint and provide an event_type name to describe the activity type. To trigger a workflow run, you must also configure your workflow to use the repository_dispatch event.\"\n },\n \"schedule\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows#scheduled-events-schedule\",\n \"description\": \"You can schedule a workflow to run at specific UTC times using POSIX cron syntax (https://pubs.opengroup.org/onlinepubs/9699919799/utilities/crontab.html#tag_20_25_07). Scheduled workflows run on the latest commit on the default or base branch. The shortest interval you can run scheduled workflows is once every 5 minutes.\\nNote: GitHub Actions does not support the non-standard syntax @yearly, @monthly, @weekly, @daily, @hourly, and @reboot.\\nYou can use crontab guru (https://crontab.guru/). to help generate your cron syntax and confirm what time it will run. To help you get started, there is also a list of crontab guru examples (https://crontab.guru/examples.html).\",\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"cron\": {\n \"type\": \"string\"\n }\n },\n \"additionalProperties\": false\n },\n \"minItems\": 1\n }\n },\n \"additionalProperties\": false\n }\n ]\n },\n \"env\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#env\",\n \"$ref\": \"#/definitions/env\",\n \"description\": \"A map of environment variables that are available to all jobs and steps in the workflow.\"\n },\n \"defaults\": {\n \"$comment\": \"https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#defaults\",\n \"$ref\": \"#/definitions/defaults\",\n \"description\": \"A map of default settings that will apply to all jobs in the workflow.\"\n },\n \"concurrency\": {\n \"$comment\": \"https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#concurrency\",\n \"description\": \"Concurrency ensures that only a single job or workflow using the same concurrency group will run at a time. A concurrency group can be any string or expression. The expression can use any context except for the secrets context. \\nYou can also specify concurrency at the workflow level. \\nWhen a concurrent job or workflow is queued, if another job or workflow using the same concurrency group in the repository is in progress, the queued job or workflow will be pending. Any previously pending job or workflow in the concurrency group will be canceled. To also cancel any currently running job or workflow in the same concurrency group, specify cancel-in-progress: true.\",\n \"oneOf\": [\n {\n \"type\": \"string\"\n },\n {\n \"$ref\": \"#/definitions/concurrency\"\n }\n ]\n },\n \"jobs\": {\n \"$comment\": \"https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#jobs\",\n \"description\": \"A workflow run is made up of one or more jobs. Jobs run in parallel by default. To run jobs sequentially, you can define dependencies on other jobs using the jobs.<job_id>.needs keyword.\\nEach job runs in a fresh instance of the virtual environment specified by runs-on.\\nYou can run an unlimited number of jobs as long as you are within the workflow usage limits. For more information, see https://help.github.com/en/github/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#usage-limits.\",\n \"type\": \"object\",\n \"patternProperties\": {\n \"^[_a-zA-Z][a-zA-Z0-9_-]*$\": {\n \"oneOf\": [\n {\n \"$ref\": \"#/definitions/normalJob\"\n },\n {\n \"$ref\": \"#/definitions/reusableWorkflowCallJob\"\n }\n ]\n }\n },\n \"minProperties\": 1,\n \"additionalProperties\": false\n },\n \"run-name\": {\n \"$comment\": \"https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#run-name\",\n \"description\": \"The name for workflow runs generated from the workflow. GitHub displays the workflow run name in the list of workflow runs on your repository's 'Actions' tab.\",\n \"type\": \"string\"\n },\n \"permissions\": {\n \"$ref\": \"#/definitions/permissions\"\n },\n \"enable-email-notifications\": {\n \"type\": \"boolean\",\n \"description\": \"Send an email notification when a workflow run fails or recovers from a failure. (Forgejo-specific)\"\n }\n },\n \"required\": [\"on\", \"jobs\"],\n \"type\": \"object\"\n}\n";
|
|
8939
8982
|
const existingSchema = ctx.read(SCHEMA_LOCAL_PATH);
|
|
8940
8983
|
if (existingSchema !== void 0 && contentEqual(SCHEMA_LOCAL_PATH, existingSchema, schemaContent)) results.push({
|
|
8941
8984
|
filePath: SCHEMA_LOCAL_PATH,
|
|
@@ -8947,7 +8990,7 @@ async function generateVscodeSettings(ctx) {
|
|
|
8947
8990
|
results.push({
|
|
8948
8991
|
filePath: SCHEMA_LOCAL_PATH,
|
|
8949
8992
|
action: existingSchema ? "updated" : "created",
|
|
8950
|
-
description: "
|
|
8993
|
+
description: "Wrote the Forgejo workflow schema"
|
|
8951
8994
|
});
|
|
8952
8995
|
}
|
|
8953
8996
|
results.push(writeSchemaToSettings(ctx));
|
|
@@ -9291,7 +9334,15 @@ const AGENT_RULES_MID = [
|
|
|
9291
9334
|
"- Write text in any language; letters, accents and other scripts are fine. Keep out what `chars:check` denies: LLM-tell punctuation (curly quotes, ellipsis, en and em dashes, arrows, box-drawing, status glyphs) and invisible or deceptive characters (bidi controls, zero-width and tag characters, odd spaces). Write `...`, `-`/`--`, straight quotes, `->` and `-`/`|`/`+` instead. Where a denied character must survive to output, escape it for its context: `\\uXXXX` in a JS/TS/Kotlin/Java string, char or regex; `&#xXXXX;` in JSX or XML; a spliced `${'\\uXXXX'}` in a Kotlin raw string. `pnpm exec bst chars:check --fix` applies the safe rewrites (`--fix-ambiguous` for em dashes and status glyphs) and flags the rest.",
|
|
9292
9335
|
"- Write in plain language, per ISO 24495-1 -- every word you produce, code comments and commit bodies included."
|
|
9293
9336
|
];
|
|
9294
|
-
const
|
|
9337
|
+
const TS_IMPORT_RULE_BUNDLER = "- Prefer extensionless imports; if an extension is required, use `.ts` over `.js`.";
|
|
9338
|
+
const TS_IMPORT_RULE_ALWAYS_TS = "- Relative imports always carry the `.ts` extension.";
|
|
9339
|
+
const TS_IMPORT_RULE_CONDITIONAL = "- Relative imports carry `.ts` with no build step or `nodenext` resolution; otherwise prefer extensionless.";
|
|
9340
|
+
function importRule(config) {
|
|
9341
|
+
if (config.structure === "monorepo") return TS_IMPORT_RULE_CONDITIONAL;
|
|
9342
|
+
if (config.builder === "none" || config.projectType === "node") return TS_IMPORT_RULE_ALWAYS_TS;
|
|
9343
|
+
if (config.builder === "custom") return TS_IMPORT_RULE_CONDITIONAL;
|
|
9344
|
+
return TS_IMPORT_RULE_BUNDLER;
|
|
9345
|
+
}
|
|
9295
9346
|
const CHANGESETS_RULE = "- A user-facing change owes a changeset in `.changeset/`, written with the change: `patch` for a fix, `minor` for a feature, naming every package it affects. One file per smallest unit of work.";
|
|
9296
9347
|
const WORK_STATUS_RULE = "- Before starting or resuming work, run `pnpm exec bst work:status`.";
|
|
9297
9348
|
const WORK_START_RULE = "- Before starting or resuming work, run `pnpm exec bst work:start <n>`.";
|
|
@@ -9433,11 +9484,11 @@ const PLANNING_GUIDE_BODY = [
|
|
|
9433
9484
|
"",
|
|
9434
9485
|
"A plan may be written by you, by another session, or by a headless agent, and the run that executes it may be any of these. That run never asks which plan is meant: it finds the plan from the branch alone, so the name and the git history must say it.",
|
|
9435
9486
|
"",
|
|
9436
|
-
"- A branch
|
|
9487
|
+
"- A plan this branch **added** is found without a marker, counted at its new path when the branch moved it there with `git mv`. A plan the branch did not add is found by a marker in its path -- the marker of either half of the pair the run works, `issue-<n>` or `pr-<n>` -- or, when the branch added no plan at all, by being the one plan it modified that still has a task open. A plan with none of these is invisible to the run that should execute it, however clearly its text names the work.",
|
|
9437
9488
|
"- Name a new plan `docs/plans/<YYYY-MM-DD>-<topic>.md`, and when the work has an issue or pull request, put its marker in the name: `docs/plans/<YYYY-MM-DD>-issue-<n>-<topic>.md`. The marker is what keeps the plan findable when a second plan joins the branch.",
|
|
9438
9489
|
"- To refine a plan this branch already added, edit it in place and keep its name, so the branch never carries two plans for one piece of work.",
|
|
9439
|
-
"-
|
|
9440
|
-
"- After committing the plan, check that it is findable: `
|
|
9490
|
+
"- To work a plan the branch inherited from the default branch -- a deferred plan, or one that landed in an earlier pull request -- rename it with `git mv` to a name that carries the marker, in the first commit that touches it, then work it under that name. Use the marker of the object the run works: when an agent executes the plan on a pull request, that is the pull request's `pr-<n>`.",
|
|
9491
|
+
"- After committing the plan, check that it is findable: `pnpm exec bst docs:check` reports R9 for a plan on the branch that the lookup above could not find.",
|
|
9441
9492
|
"",
|
|
9442
9493
|
"### Writing the plan",
|
|
9443
9494
|
"",
|
|
@@ -9445,10 +9496,12 @@ const PLANNING_GUIDE_BODY = [
|
|
|
9445
9496
|
"- Label each task `Task <n>:` at the start of its heading or checkbox, numbered from 1, so `plan:brief` can find it. To split a task after the plan is written, give the parts one lowercase letter (`Task 18a:`, `Task 18b:`) rather than renumbering, so later tasks and every cross-reference keep their numbers. A checkbox's first sentence is the task's name on the control panel: end it with a period inside one short line, and put the files, commands, and checks in the sentences after it.",
|
|
9446
9497
|
`- Give each task what makes it unambiguous and nothing more: a test's name and its assertions with the spec's exact values and why that test fails before the change, a code change's exact signature and file, a command with the output that means it passed. Write out a body only for an algorithm the signature and tests do not determine. Work that uses another task's result names that task's interfaces, never repeats its code. A task with no test to write says \`${TEST_EVIDENCE_PREFIXES.noTest} <reason>\` in the plan, so the executor inherits the call instead of making it cold.`,
|
|
9447
9498
|
"- Keep tasks small. Each one runs its own test cycle and ends in a result that can be checked on its own: write the failing test, watch it fail, make it pass, commit. A run cut off partway then loses one task, not the plan. A large plan is fine; a large task is not.",
|
|
9448
|
-
"- A plan that builds on earlier work opens with a task that checks the prerequisite exists, and records a `**Blocked:**` note if it does not, instead of rebuilding it.",
|
|
9499
|
+
"- A plan that builds on earlier work opens with a task that checks the prerequisite exists, and records a `**Blocked:**` note if it does not, instead of rebuilding it. It covers work a later change can supply and a run can retry once it lands; work only a person can do goes in a human phase, the next bullet.",
|
|
9500
|
+
"- Split the plan into phases, each a heading above its tasks (a `##` phase heading over `###` task headings), and mark each phase in its heading as autonomous or as needing a person; for a human phase, name what it needs: hardware, data not in the repository, access, a decision. A phase label is a heading, never a checkbox, because a checkbox reads as a task and gets claimed as work. Task numbers run across the whole plan: a phase never restarts them. The bullet above is for work a later change can supply; this one is for work only a person can do.",
|
|
9501
|
+
"- Put autonomous phases first: a human phase goes after every autonomous phase and before the retire task, and no autonomous task depends on a human phase, so the executor commits everything it can do alone before it needs a person. Open each human phase with what the person does, where they commit the result, and how the executor checks that the result is there; the check reads only what is committed, because an executor may not have the device, the data, or the access the person has, and the phase's boxes get ticked once that check passes on the committed result. Data too large to commit gets a committed stand-in the plan names, such as a sample, a summary, or recorded results; when no stand-in can do the job, the phase is hand work from start to finish and the person ticks its boxes.",
|
|
9449
9502
|
"- State a judgement that recurs across many places once, as a rule in the plan, so every task decides it the same way.",
|
|
9450
9503
|
"- Where the property that matters is out of the executor's reach (a container, a live process, a real network), say so and name the test that stands in for watching it.",
|
|
9451
|
-
"- The last task retires the plan (see the plan-lifecycle guide). Its checkbox text starts with the literal `Retire this plan`, not `Task <n>:`, so tooling can recognize it, and that first sentence ends there: `Retire this plan and its spec.`, then the command.",
|
|
9504
|
+
"- The last task retires the plan (see the plan-lifecycle guide). Its checkbox text starts with the literal `Retire this plan`, not `Task <n>:`, so tooling can recognize it, and that first sentence ends there: `Retire this plan and its spec.`, then the command. Put that box under its own heading after the last phase rather than straight under the last task's boxes, so it reads as its own unit and not as another step of that task.",
|
|
9452
9505
|
"- Reread the finished plan against the spec. A plan several times longer than its spec has written the program instead of the decisions: replace bodies with signatures, test names, and assertions. A line that decides nothing (\"TBD\", \"handle edge cases\", a type no task defines) is the opposite failure. Fix both.",
|
|
9453
9506
|
"",
|
|
9454
9507
|
"## Working through a plan",
|
|
@@ -9505,18 +9558,18 @@ function webUiGuideBody() {
|
|
|
9505
9558
|
const mwg = `pnpm dlx modern-web-guidance@${{
|
|
9506
9559
|
"@changesets/cli": "3.0.3",
|
|
9507
9560
|
"@release-it/bumper": "8.0.1",
|
|
9508
|
-
"@types/node": "24.19.
|
|
9561
|
+
"@types/node": "24.19.2",
|
|
9509
9562
|
"@vitest/coverage-v8": "5.0.3",
|
|
9510
|
-
"commit-and-tag-version": "13.
|
|
9563
|
+
"commit-and-tag-version": "13.3.0",
|
|
9511
9564
|
"knip": "6.41.0",
|
|
9512
|
-
"lefthook": "2.2.
|
|
9513
|
-
"modern-web-guidance": "0.0.
|
|
9514
|
-
"oxfmt": "0.
|
|
9565
|
+
"lefthook": "2.2.2",
|
|
9566
|
+
"modern-web-guidance": "0.0.194",
|
|
9567
|
+
"oxfmt": "0.73.0",
|
|
9515
9568
|
"oxlint": "1.87.0",
|
|
9516
|
-
"oxlint-tsgolint": "7.0.
|
|
9517
|
-
"prettier": "3.9.
|
|
9569
|
+
"oxlint-tsgolint": "7.0.2004",
|
|
9570
|
+
"prettier": "3.9.10",
|
|
9518
9571
|
"release-it": "21.1.1",
|
|
9519
|
-
"tsdown": "0.23.
|
|
9572
|
+
"tsdown": "0.23.1",
|
|
9520
9573
|
"typescript": "6.0.3",
|
|
9521
9574
|
"vitest": "5.0.3",
|
|
9522
9575
|
"pnpm": "11.28.5"
|
|
@@ -9772,7 +9825,7 @@ function agentRulesBody(config, guides, packageJson, agentWorked) {
|
|
|
9772
9825
|
const lines = [...AGENT_RULES_HEAD];
|
|
9773
9826
|
if (config.stack === "typescript") lines.push(TS_TYPECAST_RULE);
|
|
9774
9827
|
lines.push(...AGENT_RULES_MID);
|
|
9775
|
-
if (config.stack === "typescript") lines.push(
|
|
9828
|
+
if (config.stack === "typescript") lines.push(importRule(config));
|
|
9776
9829
|
if (config.releaseStrategy === "changesets") lines.push(CHANGESETS_RULE);
|
|
9777
9830
|
const tail = agentRulesTail(agentWorked).map((line) => line === FIND_COMMAND_RULE ? findCommandRule(packageJson) : line);
|
|
9778
9831
|
lines.push(...tail, "", guideIndex(guides));
|
|
@@ -10079,7 +10132,7 @@ function reportFailures(failed, command) {
|
|
|
10079
10132
|
function generateMigratePrompt(results, config, detected) {
|
|
10080
10133
|
const sections = [];
|
|
10081
10134
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
10082
|
-
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.
|
|
10135
|
+
sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.94.0 repo:sync\` on ${timestamp}_\n`);
|
|
10083
10136
|
const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
|
|
10084
10137
|
if (changed.length > 0) {
|
|
10085
10138
|
const items = changed.map((r) => {
|
|
@@ -10976,7 +11029,7 @@ function createRealHealthExecutor(rootDir) {
|
|
|
10976
11029
|
};
|
|
10977
11030
|
return {
|
|
10978
11031
|
stdout: "",
|
|
10979
|
-
stderr:
|
|
11032
|
+
stderr: describeError$1(err),
|
|
10980
11033
|
exitCode: 1
|
|
10981
11034
|
};
|
|
10982
11035
|
}
|
|
@@ -11932,6 +11985,26 @@ function logCoverageChoice(vc, coverage, useCoverageScript, requested) {
|
|
|
11932
11985
|
if (coverage.enabled && !useCoverageScript && coverage.source === "config") log$1.warn("coverage.gateOnPullRequests is set but test:coverage script is not defined; falling back to test");
|
|
11933
11986
|
debug(vc, `Coverage: ${requested ?? "auto"} (source: ${coverage.source}) -> ${useCoverageScript ? "test:coverage" : "test"}`);
|
|
11934
11987
|
}
|
|
11988
|
+
function withLegacyCharsScript(definedScripts) {
|
|
11989
|
+
const scripts = new Set(definedScripts);
|
|
11990
|
+
if (!scripts.has(CHARS_CHECK.name) && definedScripts.has(LEGACY_CHARS_CHECK_SCRIPT)) scripts.add(CHARS_CHECK.name);
|
|
11991
|
+
return scripts;
|
|
11992
|
+
}
|
|
11993
|
+
function withAddedChecks(builtinChecks, add) {
|
|
11994
|
+
const builtinNames = new Set(builtinChecks.map((check) => check.name));
|
|
11995
|
+
function isBuiltInName(name) {
|
|
11996
|
+
if (builtinNames.has(name)) return true;
|
|
11997
|
+
const alias = legacyCharsAlias(name);
|
|
11998
|
+
return alias !== void 0 && builtinNames.has(alias);
|
|
11999
|
+
}
|
|
12000
|
+
return [...builtinChecks, ...add.filter((name) => !isBuiltInName(name)).map((name) => ({ name }))];
|
|
12001
|
+
}
|
|
12002
|
+
function scriptNameFor(checkName, definedScripts, useCoverageScript) {
|
|
12003
|
+
if (checkName === "test" && useCoverageScript) return "test:coverage";
|
|
12004
|
+
if (checkName === "lint" && definedScripts.has("lint:report")) return "lint:report";
|
|
12005
|
+
if (checkName === CHARS_CHECK.name && !definedScripts.has(CHARS_CHECK.name) && definedScripts.has(LEGACY_CHARS_CHECK_SCRIPT)) return LEGACY_CHARS_CHECK_SCRIPT;
|
|
12006
|
+
return checkName;
|
|
12007
|
+
}
|
|
11935
12008
|
async function runRunChecks(targetDir, options = {}) {
|
|
11936
12009
|
const exec = options.execCommand ?? execStep;
|
|
11937
12010
|
const getScripts = options.getScripts ?? defaultGetScripts;
|
|
@@ -11946,17 +12019,9 @@ async function runRunChecks(targetDir, options = {}) {
|
|
|
11946
12019
|
const reporter = isCI ? ciReporter : localReporter;
|
|
11947
12020
|
const vc = { verbose: options.verbose ?? false };
|
|
11948
12021
|
const definedScripts = getScripts(targetDir);
|
|
11949
|
-
const definedCheckScripts =
|
|
11950
|
-
if (!definedCheckScripts.has(CHARS_CHECK.name) && definedScripts.has(LEGACY_CHARS_CHECK_SCRIPT)) definedCheckScripts.add(CHARS_CHECK.name);
|
|
12022
|
+
const definedCheckScripts = withLegacyCharsScript(definedScripts);
|
|
11951
12023
|
const addedNames = new Set(add);
|
|
11952
|
-
const
|
|
11953
|
-
const builtinNames = new Set(builtinChecks.map((check) => check.name));
|
|
11954
|
-
function isBuiltInName(name) {
|
|
11955
|
-
if (builtinNames.has(name)) return true;
|
|
11956
|
-
const alias = legacyCharsAlias(name);
|
|
11957
|
-
return alias !== void 0 && builtinNames.has(alias);
|
|
11958
|
-
}
|
|
11959
|
-
const allChecks = [...builtinChecks, ...add.filter((name) => !isBuiltInName(name)).map((name) => ({ name }))];
|
|
12024
|
+
const allChecks = withAddedChecks(resolveRunBuiltins(targetDir, toolingConfig, options.referenceEnablesHealth), add);
|
|
11960
12025
|
const failures = [];
|
|
11961
12026
|
const warnedSteps = [];
|
|
11962
12027
|
const notDefined = [];
|
|
@@ -11980,10 +12045,7 @@ async function runRunChecks(targetDir, options = {}) {
|
|
|
11980
12045
|
} else notDefined.push(check.name);
|
|
11981
12046
|
continue;
|
|
11982
12047
|
}
|
|
11983
|
-
|
|
11984
|
-
if (check.name === "test" && useCoverageScript) scriptName = "test:coverage";
|
|
11985
|
-
if (check.name === "lint" && definedScripts.has("lint:report")) scriptName = "lint:report";
|
|
11986
|
-
if (check.name === CHARS_CHECK.name && !definedScripts.has(CHARS_CHECK.name) && definedScripts.has(LEGACY_CHARS_CHECK_SCRIPT)) scriptName = LEGACY_CHARS_CHECK_SCRIPT;
|
|
12048
|
+
const scriptName = scriptNameFor(check.name, definedScripts, useCoverageScript);
|
|
11987
12049
|
const cmd = check.args ? `pnpm run ${scriptName} ${check.args}` : `pnpm run ${scriptName}`;
|
|
11988
12050
|
debug(vc, `Running: ${cmd} (in ${targetDir})`);
|
|
11989
12051
|
reporter.groupStart(check.name);
|
|
@@ -13118,7 +13180,9 @@ async function runVersionMode(executor, config) {
|
|
|
13118
13180
|
debug(config, `Changed packages: ${changedDirs.length > 0 ? changedDirs.join(", ") : "(none)"}`);
|
|
13119
13181
|
stampChangelogDates(executor, config.cwd, changedDirs, true);
|
|
13120
13182
|
} else stampChangelogDates(executor, config.cwd, [], false);
|
|
13121
|
-
|
|
13183
|
+
const installResult = executor.exec("pnpm", ["install", "--no-frozen-lockfile"], { cwd: config.cwd });
|
|
13184
|
+
debugExec(config, "pnpm install --no-frozen-lockfile", installResult);
|
|
13185
|
+
if (installResult.exitCode !== 0) throw new FatalError(`pnpm install --no-frozen-lockfile failed (exit code ${String(installResult.exitCode)}):\n${childOutput(installResult)}`);
|
|
13122
13186
|
const { title, body } = buildPrContent(executor, config.cwd, packagesBefore);
|
|
13123
13187
|
debug(config, `PR title: ${title}`);
|
|
13124
13188
|
const addResult = executor.exec("git", ["add", "-A"], { cwd: config.cwd });
|
|
@@ -14742,14 +14806,19 @@ async function writeClaim(executor, cwd, target, number) {
|
|
|
14742
14806
|
//#endregion
|
|
14743
14807
|
//#region src/work-status/consent.ts
|
|
14744
14808
|
async function readAgentConsent(git, defaultBranch) {
|
|
14745
|
-
const
|
|
14809
|
+
const base = await git.resolveBase(defaultBranch);
|
|
14810
|
+
if (base !== `origin/${defaultBranch}`) return {
|
|
14811
|
+
enabled: agentEnabledIn(readWorkingTree(git.topLevel)),
|
|
14812
|
+
source: "working-tree"
|
|
14813
|
+
};
|
|
14814
|
+
const onBranch = await git.show(base, ".tooling.json");
|
|
14746
14815
|
if (onBranch !== null) return {
|
|
14747
14816
|
enabled: agentEnabledIn(onBranch),
|
|
14748
14817
|
source: "default-branch"
|
|
14749
14818
|
};
|
|
14750
14819
|
return {
|
|
14751
|
-
enabled:
|
|
14752
|
-
source: "
|
|
14820
|
+
enabled: false,
|
|
14821
|
+
source: "default-branch"
|
|
14753
14822
|
};
|
|
14754
14823
|
}
|
|
14755
14824
|
function readWorkingTree(topLevel) {
|
|
@@ -15148,6 +15217,17 @@ function githubPrHost(executor, cwd, signal) {
|
|
|
15148
15217
|
}
|
|
15149
15218
|
};
|
|
15150
15219
|
}
|
|
15220
|
+
function parseGhJson(stdout, command) {
|
|
15221
|
+
let payload;
|
|
15222
|
+
try {
|
|
15223
|
+
payload = JSON.parse(stdout);
|
|
15224
|
+
} catch {
|
|
15225
|
+
throw new UnexpectedError(`'${command}' printed something that is not JSON: ${stdout.trim().slice(0, 200) || "no output"}`);
|
|
15226
|
+
}
|
|
15227
|
+
const value = z.json().safeParse(payload);
|
|
15228
|
+
if (!value.success) throw new UnexpectedError(`Unexpected '${command}' response: ${value.error.message} (${stdout.trim().slice(0, 200) || "no output"})`);
|
|
15229
|
+
return value.data;
|
|
15230
|
+
}
|
|
15151
15231
|
const GitHubPrContentSchema = z.object({
|
|
15152
15232
|
title: z.string(),
|
|
15153
15233
|
body: z.string().nullish()
|
|
@@ -15165,7 +15245,7 @@ async function readGitHubPrContent(executor, cwd, prNumber, signal) {
|
|
|
15165
15245
|
signal
|
|
15166
15246
|
});
|
|
15167
15247
|
if (result.exitCode !== 0) throw new FatalError(`Failed to read PR #${String(prNumber)}: ${result.stderr || result.stdout}`);
|
|
15168
|
-
const parsed = GitHubPrContentSchema.safeParse(
|
|
15248
|
+
const parsed = GitHubPrContentSchema.safeParse(parseGhJson(result.stdout, "gh pr view"));
|
|
15169
15249
|
if (!parsed.success) throw new UnexpectedError(`Unexpected 'gh pr view' response: ${parsed.error.message}`);
|
|
15170
15250
|
return {
|
|
15171
15251
|
title: parsed.data.title,
|
|
@@ -15194,7 +15274,7 @@ async function findOpenGitHubPr(executor, cwd, branch, signal) {
|
|
|
15194
15274
|
if (result.exitCode !== 0) throw new TransientError(`'gh pr list' exited ${String(result.exitCode)}: ${result.stderr.trim() || "no output"} -- cannot tell whether ${branch} has an open PR`);
|
|
15195
15275
|
const out = result.stdout.trim();
|
|
15196
15276
|
if (out === "") return null;
|
|
15197
|
-
const parsed = GitHubPrListSchema.safeParse(
|
|
15277
|
+
const parsed = GitHubPrListSchema.safeParse(parseGhJson(out, "gh pr list"));
|
|
15198
15278
|
if (!parsed.success) throw new UnexpectedError(`Unexpected 'gh pr list' response: ${parsed.error.message}`);
|
|
15199
15279
|
const first = parsed.data[0];
|
|
15200
15280
|
return first === void 0 ? null : {
|
|
@@ -15290,7 +15370,7 @@ function githubPrEditHost(executor, cwd, repository) {
|
|
|
15290
15370
|
"state,isDraft,title,headRefName,baseRefName,mergeable,url"
|
|
15291
15371
|
]);
|
|
15292
15372
|
if (result.exitCode !== 0) throw new FatalError(`Failed to read PR #${String(number)}: ${result.stderr || result.stdout}`);
|
|
15293
|
-
const parsed = GitHubPrEditStateSchema.safeParse(
|
|
15373
|
+
const parsed = GitHubPrEditStateSchema.safeParse(parseGhJson(result.stdout, "gh pr view"));
|
|
15294
15374
|
if (!parsed.success) throw new UnexpectedError(`Unexpected 'gh pr view' response: ${parsed.error.message}`);
|
|
15295
15375
|
const data = parsed.data;
|
|
15296
15376
|
return {
|
|
@@ -16088,6 +16168,27 @@ const OUTPUT_GLYPH_OFFENDERS = [
|
|
|
16088
16168
|
char: "▶",
|
|
16089
16169
|
replacement: ">"
|
|
16090
16170
|
},
|
|
16171
|
+
{
|
|
16172
|
+
codepoint: "U+25BA",
|
|
16173
|
+
name: "BLACK RIGHT-POINTING POINTER",
|
|
16174
|
+
tier: 2,
|
|
16175
|
+
char: "►",
|
|
16176
|
+
replacement: ">"
|
|
16177
|
+
},
|
|
16178
|
+
{
|
|
16179
|
+
codepoint: "U+25C4",
|
|
16180
|
+
name: "BLACK LEFT-POINTING POINTER",
|
|
16181
|
+
tier: 2,
|
|
16182
|
+
char: "◄",
|
|
16183
|
+
replacement: "<"
|
|
16184
|
+
},
|
|
16185
|
+
{
|
|
16186
|
+
codepoint: "U+25C0",
|
|
16187
|
+
name: "BLACK LEFT-POINTING TRIANGLE",
|
|
16188
|
+
tier: 2,
|
|
16189
|
+
char: "◀",
|
|
16190
|
+
replacement: "<"
|
|
16191
|
+
},
|
|
16091
16192
|
{
|
|
16092
16193
|
codepoint: "U+0394",
|
|
16093
16194
|
name: "GREEK CAPITAL LETTER DELTA",
|
|
@@ -17205,7 +17306,9 @@ var MarkupLexer = class {
|
|
|
17205
17306
|
matchesIslandClose() {
|
|
17206
17307
|
const start = this.i + 2;
|
|
17207
17308
|
const end = start + this.islandCloseTag.length;
|
|
17208
|
-
|
|
17309
|
+
if (this.text.slice(start, end).toLowerCase() !== this.islandCloseTag) return false;
|
|
17310
|
+
const next = this.text[end] ?? "";
|
|
17311
|
+
return next === ">" || next === "/" || /\s/.test(next);
|
|
17209
17312
|
}
|
|
17210
17313
|
closeIsland() {
|
|
17211
17314
|
this.flushTo(this.i, "code");
|
|
@@ -18234,6 +18337,9 @@ function parsePlanBoxes(planText) {
|
|
|
18234
18337
|
};
|
|
18235
18338
|
});
|
|
18236
18339
|
}
|
|
18340
|
+
function countOutstandingBoxes(planText, options) {
|
|
18341
|
+
return parsePlanBoxes(planText).filter((box) => isOutstanding(box, options)).length;
|
|
18342
|
+
}
|
|
18237
18343
|
//#endregion
|
|
18238
18344
|
//#region src/plan-workspace/status.ts
|
|
18239
18345
|
const RETIRED_SEGMENT = /(^|\/)retired\//;
|
|
@@ -18592,7 +18698,7 @@ function liftedFactsOf(resolved, forgeDown) {
|
|
|
18592
18698
|
return {
|
|
18593
18699
|
runLive: complete || read.runLive.length > 0 ? read.runLive : null,
|
|
18594
18700
|
heldByHand: complete || read.heldByHand ? read.heldByHand : null,
|
|
18595
|
-
owner: complete || ownership.owner
|
|
18701
|
+
owner: complete || ownership.owner === "agent" ? ownership.owner : null
|
|
18596
18702
|
};
|
|
18597
18703
|
}
|
|
18598
18704
|
async function docsSection({ git, docsExtraDirs, resolved }) {
|
|
@@ -18737,6 +18843,26 @@ function objectRows(value) {
|
|
|
18737
18843
|
if (value.issueUnavailable !== void 0) rows.push(` issue unavailable -- ${value.issueUnavailable}`);
|
|
18738
18844
|
return rows;
|
|
18739
18845
|
}
|
|
18846
|
+
function docsRows(value) {
|
|
18847
|
+
const rows = [` base ${value.base}`];
|
|
18848
|
+
for (const spec of value.specs) rows.push(` spec ${spec.path}${spec.retired ? " (retired)" : ""}`);
|
|
18849
|
+
for (const plan of value.plans) rows.push(` plan ${plan.path}: ${String(plan.status.checked)} of ${String(plan.status.total)} checked, ${String(plan.status.outstanding)} outstanding${plan.status.declined > 0 ? `, ${String(plan.status.declined)} declined` : ""}`);
|
|
18850
|
+
if (value.specs.length === 0 && value.plans.length === 0) rows.push(" no spec or plan changed on this branch");
|
|
18851
|
+
return rows;
|
|
18852
|
+
}
|
|
18853
|
+
function labelRows(value) {
|
|
18854
|
+
const rows = [];
|
|
18855
|
+
for (const [where, groups] of [["pull request", value.pr], ["issue", value.issue]]) {
|
|
18856
|
+
if (groups.length === 0) continue;
|
|
18857
|
+
rows.push(` ${where}:`);
|
|
18858
|
+
for (const group of groups) for (const label of group.labels) {
|
|
18859
|
+
const described = label.description === null ? "" : ` -- ${label.description}`;
|
|
18860
|
+
rows.push(` ${label.name}${described} [${group.scope}]`);
|
|
18861
|
+
}
|
|
18862
|
+
}
|
|
18863
|
+
if (value.descriptionsUnavailable !== void 0) rows.push(` descriptions unavailable -- ${value.descriptionsUnavailable}`);
|
|
18864
|
+
return rows;
|
|
18865
|
+
}
|
|
18740
18866
|
function nothingInFlight(report) {
|
|
18741
18867
|
const { object, docs, commits } = report;
|
|
18742
18868
|
if (object.state !== "absent" || docs.state !== "ok" || commits.state !== "ok") return null;
|
|
@@ -18767,26 +18893,8 @@ function renderWorkStatus(report, options) {
|
|
|
18767
18893
|
if (number !== null) lines.push(`UNCLAIMED: no agent label and no hand claim -- run \`bst work:start ${String(number)}\` before you work this.`);
|
|
18768
18894
|
}
|
|
18769
18895
|
lines.push(...block("object", report.object, objectRows));
|
|
18770
|
-
lines.push(...block("docs", report.docs,
|
|
18771
|
-
|
|
18772
|
-
for (const spec of value.specs) rows.push(` spec ${spec.path}${spec.retired ? " (retired)" : ""}`);
|
|
18773
|
-
for (const plan of value.plans) rows.push(` plan ${plan.path}: ${String(plan.status.checked)} of ${String(plan.status.total)} checked, ${String(plan.status.outstanding)} outstanding${plan.status.declined > 0 ? `, ${String(plan.status.declined)} declined` : ""}`);
|
|
18774
|
-
if (value.specs.length === 0 && value.plans.length === 0) rows.push(" no spec or plan changed on this branch");
|
|
18775
|
-
return rows;
|
|
18776
|
-
}));
|
|
18777
|
-
lines.push(...block("labels", report.labels, (value) => {
|
|
18778
|
-
const rows = [];
|
|
18779
|
-
for (const [where, groups] of [["pull request", value.pr], ["issue", value.issue]]) {
|
|
18780
|
-
if (groups.length === 0) continue;
|
|
18781
|
-
rows.push(` ${where}:`);
|
|
18782
|
-
for (const group of groups) for (const label of group.labels) {
|
|
18783
|
-
const described = label.description === null ? "" : ` -- ${label.description}`;
|
|
18784
|
-
rows.push(` ${label.name}${described} [${group.scope}]`);
|
|
18785
|
-
}
|
|
18786
|
-
}
|
|
18787
|
-
if (value.descriptionsUnavailable !== void 0) rows.push(` descriptions unavailable -- ${value.descriptionsUnavailable}`);
|
|
18788
|
-
return rows;
|
|
18789
|
-
}));
|
|
18896
|
+
lines.push(...block("docs", report.docs, docsRows));
|
|
18897
|
+
lines.push(...block("labels", report.labels, labelRows));
|
|
18790
18898
|
lines.push(...block("feedback", report.feedback, (value) => {
|
|
18791
18899
|
const from = value.cutoffSource === "agent-comment" ? "last agent comment" : "head commit";
|
|
18792
18900
|
const rows = [` since ${value.cutoff} (the ${from}), ${String(value.agentComments)} agent comment(s) held back`];
|
|
@@ -18933,6 +19041,7 @@ function collectInputArgs(rawArgs) {
|
|
|
18933
19041
|
for (let i = 0; i < rawArgs.length; i += 1) {
|
|
18934
19042
|
const arg = rawArgs[i];
|
|
18935
19043
|
if (arg === void 0) continue;
|
|
19044
|
+
if (arg === "--") break;
|
|
18936
19045
|
if (arg === INPUT_FLAG) {
|
|
18937
19046
|
const next = rawArgs[i + 1];
|
|
18938
19047
|
if (next === void 0) throw new FatalError("Missing value for --input: expected `--input key=value`");
|
|
@@ -21077,7 +21186,7 @@ function composePs(executor, config) {
|
|
|
21077
21186
|
//#region src/docker-check/health.ts
|
|
21078
21187
|
const DOCKER_RUN_START_FAILURE_EXIT_CODE = 125;
|
|
21079
21188
|
const PROBE_TIMEOUT_SECONDS = "5";
|
|
21080
|
-
const PROBE_IMAGE = "curlimages/curl:8.
|
|
21189
|
+
const PROBE_IMAGE = "curlimages/curl:8.23.0@sha256:a134550be58ab783810e613ae96fd253c38f7bdd16d9bf9be350700c7a29ac06";
|
|
21081
21190
|
function getContainerHealth(containers, serviceName) {
|
|
21082
21191
|
return containers.find((c) => c.Service === serviceName)?.Health ?? "unknown";
|
|
21083
21192
|
}
|
|
@@ -21540,6 +21649,11 @@ function countByMode(entries) {
|
|
|
21540
21649
|
for (const entry of entries) if (entry.permissionMode !== void 0) counts[entry.permissionMode] = (counts[entry.permissionMode] ?? 0) + 1;
|
|
21541
21650
|
return counts;
|
|
21542
21651
|
}
|
|
21652
|
+
function parseTailCount(raw) {
|
|
21653
|
+
const value = Number(raw);
|
|
21654
|
+
if (!Number.isInteger(value) || value <= 0) throw new FatalError(`--tail must be a positive whole number of entries, got "${raw}"`);
|
|
21655
|
+
return value;
|
|
21656
|
+
}
|
|
21543
21657
|
function parseAuditLog(logPath) {
|
|
21544
21658
|
if (!existsSync(logPath)) return [];
|
|
21545
21659
|
const lines = readFileSync(logPath, "utf-8").split("\n").filter(Boolean);
|
|
@@ -21601,7 +21715,9 @@ const hooksAuditCommand = defineCommand({
|
|
|
21601
21715
|
async run({ args }) {
|
|
21602
21716
|
const out = createCliOutput(args.json === true);
|
|
21603
21717
|
const targetDir = args.dir ? path.resolve(args.dir) : findProjectRoot(process.cwd());
|
|
21604
|
-
|
|
21718
|
+
const logPath = path.join(targetDir, ".claude/hooks/bst-hook.audit.local.jsonl");
|
|
21719
|
+
const tailCount = args.tail === void 0 ? null : parseTailCount(args.tail);
|
|
21720
|
+
let entries = parseAuditLog(logPath);
|
|
21605
21721
|
if (entries.length === 0) {
|
|
21606
21722
|
out.json({ entries: [] });
|
|
21607
21723
|
out.text("No audit log entries found.");
|
|
@@ -21610,17 +21726,15 @@ const hooksAuditCommand = defineCommand({
|
|
|
21610
21726
|
const decisionFilter = args.decision === "passthrough" ? "none" : args.decision;
|
|
21611
21727
|
if (decisionFilter) entries = entries.filter((e) => e.decision === decisionFilter);
|
|
21612
21728
|
if (args.json === true) {
|
|
21613
|
-
const tail = args.tail ? Number.parseInt(args.tail, 10) : entries.length;
|
|
21614
21729
|
const byMode = countByMode(entries);
|
|
21615
21730
|
out.json({
|
|
21616
|
-
entries: entries.slice(-
|
|
21731
|
+
entries: tailCount === null ? entries : entries.slice(-tailCount),
|
|
21617
21732
|
counts: countDecisions(entries),
|
|
21618
21733
|
...Object.keys(byMode).length > 0 ? { byMode } : {}
|
|
21619
21734
|
});
|
|
21620
21735
|
return;
|
|
21621
21736
|
}
|
|
21622
|
-
if (
|
|
21623
|
-
const tailCount = Number.parseInt(args.tail, 10);
|
|
21737
|
+
if (tailCount !== null) {
|
|
21624
21738
|
for (const entry of entries.slice(-tailCount)) {
|
|
21625
21739
|
const ts = new Date(entry.timestamp).toLocaleString();
|
|
21626
21740
|
const rule = entry.rule ? ` [${entry.rule}]` : "";
|
|
@@ -24968,6 +25082,143 @@ const branchPruneCommand = defineCommand({
|
|
|
24968
25082
|
}
|
|
24969
25083
|
});
|
|
24970
25084
|
//#endregion
|
|
25085
|
+
//#region src/docs-check/plan-locate.ts
|
|
25086
|
+
const SCORED_STATUS = /^[A-Z][0-9]+$/;
|
|
25087
|
+
const RENAME_STATUS = /^R[0-9]*$/;
|
|
25088
|
+
const COPY_STATUS = /^C[0-9]*$/;
|
|
25089
|
+
function pathCountFor(token) {
|
|
25090
|
+
if (RENAME_STATUS.test(token) || COPY_STATUS.test(token)) return 2;
|
|
25091
|
+
if (SCORED_STATUS.test(token)) return 2;
|
|
25092
|
+
return 1;
|
|
25093
|
+
}
|
|
25094
|
+
function singlePathStatus(token) {
|
|
25095
|
+
if (token === "A" || token === "M" || token === "D" || token === "T") return token;
|
|
25096
|
+
return null;
|
|
25097
|
+
}
|
|
25098
|
+
function normalizeRecord(token, paths) {
|
|
25099
|
+
const [source, destination] = paths;
|
|
25100
|
+
if (RENAME_STATUS.test(token)) {
|
|
25101
|
+
if (source === void 0 || destination === void 0) return [];
|
|
25102
|
+
return [{
|
|
25103
|
+
status: "D",
|
|
25104
|
+
path: source
|
|
25105
|
+
}, {
|
|
25106
|
+
status: "A",
|
|
25107
|
+
path: destination
|
|
25108
|
+
}];
|
|
25109
|
+
}
|
|
25110
|
+
if (COPY_STATUS.test(token)) {
|
|
25111
|
+
if (destination === void 0) return [];
|
|
25112
|
+
return [{
|
|
25113
|
+
status: "A",
|
|
25114
|
+
path: destination
|
|
25115
|
+
}];
|
|
25116
|
+
}
|
|
25117
|
+
const status = singlePathStatus(token);
|
|
25118
|
+
if (status === null || source === void 0) return [];
|
|
25119
|
+
return [{
|
|
25120
|
+
status,
|
|
25121
|
+
path: source
|
|
25122
|
+
}];
|
|
25123
|
+
}
|
|
25124
|
+
function parseNameStatus(stdout) {
|
|
25125
|
+
const fields = stdout.split("\0");
|
|
25126
|
+
while (fields.length > 0 && fields.at(-1) === "") fields.pop();
|
|
25127
|
+
const entries = [];
|
|
25128
|
+
let index = 0;
|
|
25129
|
+
while (index < fields.length) {
|
|
25130
|
+
const token = fields[index] ?? "";
|
|
25131
|
+
const paths = fields.slice(index + 1, index + 1 + pathCountFor(token));
|
|
25132
|
+
index += 1 + paths.length;
|
|
25133
|
+
entries.push(...normalizeRecord(token, paths));
|
|
25134
|
+
}
|
|
25135
|
+
return entries;
|
|
25136
|
+
}
|
|
25137
|
+
function planDiffArgs(defaultRef) {
|
|
25138
|
+
return [
|
|
25139
|
+
"diff",
|
|
25140
|
+
"--name-status",
|
|
25141
|
+
"-z",
|
|
25142
|
+
`${defaultRef}...HEAD`
|
|
25143
|
+
];
|
|
25144
|
+
}
|
|
25145
|
+
const ANY_MARKER = /(?<![a-zA-Z0-9_])(?:issue|pr)-[1-9][0-9]*(?![0-9])/;
|
|
25146
|
+
function markerFor(ref) {
|
|
25147
|
+
return new RegExp(`(?<![a-zA-Z0-9_])${ref.kind}-${String(ref.number)}(?![0-9])`);
|
|
25148
|
+
}
|
|
25149
|
+
function matchMarkerPaths(paths, refs) {
|
|
25150
|
+
if (refs === null) return paths.filter((path) => path.startsWith("docs/") && ANY_MARKER.test(path));
|
|
25151
|
+
const markers = refs.map(markerFor);
|
|
25152
|
+
return paths.filter((path) => path.startsWith("docs/") && markers.some((m) => m.test(path)));
|
|
25153
|
+
}
|
|
25154
|
+
function isPlanPath(path) {
|
|
25155
|
+
const doc = classifyConventionalDoc(path);
|
|
25156
|
+
return doc !== null && doc.kind !== "spec";
|
|
25157
|
+
}
|
|
25158
|
+
function livePlanPaths(paths) {
|
|
25159
|
+
return paths.filter((path) => isPlanPath(path) && !classifyConventionalDoc(path)?.retired);
|
|
25160
|
+
}
|
|
25161
|
+
function modifiedLivePlans(entries) {
|
|
25162
|
+
const modified = [];
|
|
25163
|
+
for (const item of entries) {
|
|
25164
|
+
if (item.status !== "M") continue;
|
|
25165
|
+
if (isPlanPath(item.path) && !classifyConventionalDoc(item.path)?.retired) modified.push(item.path);
|
|
25166
|
+
}
|
|
25167
|
+
return modified;
|
|
25168
|
+
}
|
|
25169
|
+
function addedPlans(entries) {
|
|
25170
|
+
const live = [];
|
|
25171
|
+
const retired = [];
|
|
25172
|
+
for (const item of entries) {
|
|
25173
|
+
if (item.status !== "A") continue;
|
|
25174
|
+
const doc = classifyConventionalDoc(item.path);
|
|
25175
|
+
if (doc === null || doc.kind === "spec") continue;
|
|
25176
|
+
if (doc.retired) retired.push(item.path);
|
|
25177
|
+
else live.push(item.path);
|
|
25178
|
+
}
|
|
25179
|
+
return {
|
|
25180
|
+
live,
|
|
25181
|
+
retired
|
|
25182
|
+
};
|
|
25183
|
+
}
|
|
25184
|
+
function touchedLivePlans(entries) {
|
|
25185
|
+
const touched = [];
|
|
25186
|
+
for (const item of entries) {
|
|
25187
|
+
if (item.status === "D") continue;
|
|
25188
|
+
if (isPlanPath(item.path) && !classifyConventionalDoc(item.path)?.retired) touched.push(item.path);
|
|
25189
|
+
}
|
|
25190
|
+
return touched;
|
|
25191
|
+
}
|
|
25192
|
+
function locatePlan(input) {
|
|
25193
|
+
const { entries, trackedPaths, refs, isOpen } = input;
|
|
25194
|
+
const [marked] = matchMarkerPaths(refs === null ? touchedLivePlans(entries) : livePlanPaths(trackedPaths), refs);
|
|
25195
|
+
if (marked !== void 0) return {
|
|
25196
|
+
kind: "found",
|
|
25197
|
+
path: marked
|
|
25198
|
+
};
|
|
25199
|
+
const added = addedPlans(entries).live;
|
|
25200
|
+
const [firstAdded, secondAdded] = added;
|
|
25201
|
+
if (firstAdded !== void 0 && secondAdded === void 0) return {
|
|
25202
|
+
kind: "found",
|
|
25203
|
+
path: firstAdded
|
|
25204
|
+
};
|
|
25205
|
+
if (firstAdded !== void 0 && secondAdded !== void 0) return {
|
|
25206
|
+
kind: "ambiguous",
|
|
25207
|
+
candidates: added
|
|
25208
|
+
};
|
|
25209
|
+
const modified = modifiedLivePlans(entries).filter(isOpen);
|
|
25210
|
+
const [firstModified, secondModified] = modified;
|
|
25211
|
+
if (firstModified !== void 0 && secondModified === void 0) return {
|
|
25212
|
+
kind: "found",
|
|
25213
|
+
path: firstModified
|
|
25214
|
+
};
|
|
25215
|
+
if (firstModified !== void 0 && secondModified !== void 0) return {
|
|
25216
|
+
kind: "ambiguous",
|
|
25217
|
+
candidates: modified
|
|
25218
|
+
};
|
|
25219
|
+
return { kind: "none" };
|
|
25220
|
+
}
|
|
25221
|
+
//#endregion
|
|
24971
25222
|
//#region src/docs-check/rules.ts
|
|
24972
25223
|
function claimsPartialDelivery(body) {
|
|
24973
25224
|
const status = statusOf(body);
|
|
@@ -25070,7 +25321,7 @@ function ageDays(exec, file, now) {
|
|
|
25070
25321
|
return Math.floor((now.getTime() - then) / MS_PER_DAY$1);
|
|
25071
25322
|
}
|
|
25072
25323
|
function severityOf(violation, enforced, staleEnforced) {
|
|
25073
|
-
if (violation.rule.startsWith("R7")) return "advisory";
|
|
25324
|
+
if (violation.rule.startsWith("R7") || violation.rule.startsWith("R9")) return "advisory";
|
|
25074
25325
|
if (!enforced) return "advisory";
|
|
25075
25326
|
if (violation.rule.startsWith("R5") && !staleEnforced) return "advisory";
|
|
25076
25327
|
return "fatal";
|
|
@@ -25165,6 +25416,45 @@ function collectSpecRetirementViolations(exec, pairs, config, opts) {
|
|
|
25165
25416
|
}
|
|
25166
25417
|
return violations;
|
|
25167
25418
|
}
|
|
25419
|
+
const R9_FIX = "If this branch works this plan, rename it with `git mv` to a name carrying `issue-<n>` or `pr-<n>`. If it only edits a plan another branch works, ignore this.";
|
|
25420
|
+
function collectPlanLocateViolations(exec) {
|
|
25421
|
+
const entries = exec.branchNameStatus();
|
|
25422
|
+
if (entries === void 0) return [];
|
|
25423
|
+
const touched = touchedLivePlans(entries);
|
|
25424
|
+
if (touched.length === 0) return [];
|
|
25425
|
+
const located = locatePlan({
|
|
25426
|
+
entries,
|
|
25427
|
+
trackedPaths: [],
|
|
25428
|
+
refs: null,
|
|
25429
|
+
isOpen: (file) => {
|
|
25430
|
+
const text = exec.readFile(file);
|
|
25431
|
+
return text !== void 0 && countOutstandingBoxes(text) > 0;
|
|
25432
|
+
}
|
|
25433
|
+
});
|
|
25434
|
+
const reports = [];
|
|
25435
|
+
if (located.kind === "none") reports.push(...touched.map((file) => ({
|
|
25436
|
+
file,
|
|
25437
|
+
message: `The execute lookup finds no plan for this branch: ${file} was touched but not added, its name carries no issue-/pr- marker, and it has no outstanding task for the tier that reads a modified plan.`
|
|
25438
|
+
})));
|
|
25439
|
+
else if (located.kind === "ambiguous") reports.push(...located.candidates.map((file) => ({
|
|
25440
|
+
file,
|
|
25441
|
+
message: `The execute lookup cannot choose a plan for this branch: ${String(located.candidates.length)} live plans carry no issue-/pr- marker.`
|
|
25442
|
+
})));
|
|
25443
|
+
const violations = [];
|
|
25444
|
+
for (const report of reports) {
|
|
25445
|
+
const raw = {
|
|
25446
|
+
file: report.file,
|
|
25447
|
+
rule: "R9 plan-unlocatable",
|
|
25448
|
+
message: report.message,
|
|
25449
|
+
fix: R9_FIX
|
|
25450
|
+
};
|
|
25451
|
+
violations.push({
|
|
25452
|
+
...raw,
|
|
25453
|
+
severity: severityOf(raw, true, true)
|
|
25454
|
+
});
|
|
25455
|
+
}
|
|
25456
|
+
return violations;
|
|
25457
|
+
}
|
|
25168
25458
|
function collectViolations(exec, config, opts) {
|
|
25169
25459
|
const pairs = discoverPairs(exec, config);
|
|
25170
25460
|
const violations = [];
|
|
@@ -25177,6 +25467,7 @@ function collectViolations(exec, config, opts) {
|
|
|
25177
25467
|
retiredCount += result.retired;
|
|
25178
25468
|
}
|
|
25179
25469
|
violations.push(...collectSpecRetirementViolations(exec, pairs, config, opts));
|
|
25470
|
+
violations.push(...collectPlanLocateViolations(exec));
|
|
25180
25471
|
const activeDirs = matchableActiveDirs(exec, config);
|
|
25181
25472
|
const unmatchedEnforced = Array.isArray(config.enforced) ? config.enforced.filter((entry) => !activeDirs.includes(entry)) : [];
|
|
25182
25473
|
return {
|
|
@@ -25191,6 +25482,56 @@ function collectViolations(exec, config, opts) {
|
|
|
25191
25482
|
}
|
|
25192
25483
|
//#endregion
|
|
25193
25484
|
//#region src/docs-check/executor.ts
|
|
25485
|
+
function gitOut$1(rootDir, args) {
|
|
25486
|
+
try {
|
|
25487
|
+
return execFileSync("git", args, {
|
|
25488
|
+
cwd: rootDir,
|
|
25489
|
+
encoding: "utf-8",
|
|
25490
|
+
stdio: [
|
|
25491
|
+
"ignore",
|
|
25492
|
+
"pipe",
|
|
25493
|
+
"ignore"
|
|
25494
|
+
]
|
|
25495
|
+
});
|
|
25496
|
+
} catch (_error) {
|
|
25497
|
+
return;
|
|
25498
|
+
}
|
|
25499
|
+
}
|
|
25500
|
+
function resolveCommit$1(rootDir, ref) {
|
|
25501
|
+
const commit = gitOut$1(rootDir, [
|
|
25502
|
+
"rev-parse",
|
|
25503
|
+
"--verify",
|
|
25504
|
+
"-q",
|
|
25505
|
+
`${ref}^{commit}`
|
|
25506
|
+
])?.trim();
|
|
25507
|
+
return commit === void 0 || commit === "" ? void 0 : commit;
|
|
25508
|
+
}
|
|
25509
|
+
function branchNameStatus(rootDir) {
|
|
25510
|
+
let defaultBranch;
|
|
25511
|
+
try {
|
|
25512
|
+
defaultBranch = readConfiguredDefaultBranch(rootDir);
|
|
25513
|
+
} catch (_error) {
|
|
25514
|
+
return;
|
|
25515
|
+
}
|
|
25516
|
+
const ref = [`origin/${defaultBranch}`, defaultBranch].find((candidate) => resolveCommit$1(rootDir, candidate) !== void 0);
|
|
25517
|
+
if (ref === void 0) return void 0;
|
|
25518
|
+
const head = gitOut$1(rootDir, ["rev-parse", "HEAD"])?.trim();
|
|
25519
|
+
if (gitOut$1(rootDir, [
|
|
25520
|
+
"symbolic-ref",
|
|
25521
|
+
"-q",
|
|
25522
|
+
"--short",
|
|
25523
|
+
"HEAD"
|
|
25524
|
+
])?.trim() === defaultBranch) return void 0;
|
|
25525
|
+
if (head === void 0 || head === "") return void 0;
|
|
25526
|
+
if (head === resolveCommit$1(rootDir, ref)) return void 0;
|
|
25527
|
+
if (gitOut$1(rootDir, [
|
|
25528
|
+
"merge-base",
|
|
25529
|
+
ref,
|
|
25530
|
+
"HEAD"
|
|
25531
|
+
]) === void 0) return void 0;
|
|
25532
|
+
const stdout = gitOut$1(rootDir, planDiffArgs(ref));
|
|
25533
|
+
return stdout === void 0 ? void 0 : parseNameStatus(stdout);
|
|
25534
|
+
}
|
|
25194
25535
|
function createRealDocsExecutor(rootDir) {
|
|
25195
25536
|
const full = (rel) => path.join(rootDir, ...rel.split("/"));
|
|
25196
25537
|
return {
|
|
@@ -25235,7 +25576,8 @@ function createRealDocsExecutor(rootDir) {
|
|
|
25235
25576
|
}
|
|
25236
25577
|
const iso = stdout.trim();
|
|
25237
25578
|
return iso === "" ? void 0 : iso;
|
|
25238
|
-
}
|
|
25579
|
+
},
|
|
25580
|
+
branchNameStatus: () => branchNameStatus(rootDir)
|
|
25239
25581
|
};
|
|
25240
25582
|
}
|
|
25241
25583
|
//#endregion
|
|
@@ -25283,7 +25625,8 @@ function runDocsCheck(exec, config, opts) {
|
|
|
25283
25625
|
return {
|
|
25284
25626
|
payload: { enabled: false },
|
|
25285
25627
|
text: "",
|
|
25286
|
-
exitCode: 0
|
|
25628
|
+
exitCode: 0,
|
|
25629
|
+
warnCount: 0
|
|
25287
25630
|
};
|
|
25288
25631
|
}
|
|
25289
25632
|
const result = collectViolations(exec, config, {
|
|
@@ -25291,21 +25634,24 @@ function runDocsCheck(exec, config, opts) {
|
|
|
25291
25634
|
strict: opts.strict
|
|
25292
25635
|
});
|
|
25293
25636
|
debug(vc, `docs: ${String(result.pairCount)} pair(s), ${String(result.violations.length)} violation(s)`);
|
|
25637
|
+
const warnCount = result.violations.filter((v) => v.rule.startsWith("R9")).length;
|
|
25294
25638
|
if (result.pairCount === 0) return {
|
|
25295
25639
|
payload: result,
|
|
25296
25640
|
text: "",
|
|
25297
|
-
exitCode: 0
|
|
25641
|
+
exitCode: 0,
|
|
25642
|
+
warnCount
|
|
25298
25643
|
};
|
|
25299
25644
|
return {
|
|
25300
25645
|
payload: result,
|
|
25301
25646
|
text: formatReport(result),
|
|
25302
|
-
exitCode: result.pass ? 0 : 1
|
|
25647
|
+
exitCode: result.pass ? 0 : 1,
|
|
25648
|
+
warnCount
|
|
25303
25649
|
};
|
|
25304
25650
|
}
|
|
25305
25651
|
const docsCheckCommand = defineCommand({
|
|
25306
25652
|
meta: {
|
|
25307
25653
|
name: "docs:check",
|
|
25308
|
-
description: "Enforce the plan/spec lifecycle: a doc's directory is the source of truth for whether it is done. Checks that statuses agree with directories, retirements are dated and say where the truth went, relative links resolve, and active docs aren't stale. Findings are advisory (exit 0) unless the directory is in .tooling.json#docs.enforced (a list, or true for all) or --strict is passed; staleness stays advisory unless .tooling.json#docs.staleEnforced is set, and a link out of docs/ in a retired doc is always advisory. A retired doc should also carry the historical banner and have no links out of docs/; bst docs:delink fixes both. It CANNOT tell whether the code a plan describes actually shipped -- it verifies that a doc's metadata is self-consistent, not that it is true. Deciding a plan is done still means reading the source it names."
|
|
25654
|
+
description: "Enforce the plan/spec lifecycle: a doc's directory is the source of truth for whether it is done. Checks that statuses agree with directories, retirements are dated and say where the truth went, relative links resolve, and active docs aren't stale. On a branch, it also warns (rule R9) when the branch works a plan that the execute lookup cannot find; that finding is always advisory. Findings are advisory (exit 0) unless the directory is in .tooling.json#docs.enforced (a list, or true for all) or --strict is passed; staleness stays advisory unless .tooling.json#docs.staleEnforced is set, and a link out of docs/ in a retired doc is always advisory. A retired doc should also carry the historical banner and have no links out of docs/; bst docs:delink fixes both. It CANNOT tell whether the code a plan describes actually shipped -- it verifies that a doc's metadata is self-consistent, not that it is true. Deciding a plan is done still means reading the source it names."
|
|
25309
25655
|
},
|
|
25310
25656
|
args: {
|
|
25311
25657
|
dir: {
|
|
@@ -25316,7 +25662,7 @@ const docsCheckCommand = defineCommand({
|
|
|
25316
25662
|
},
|
|
25317
25663
|
strict: {
|
|
25318
25664
|
type: "boolean",
|
|
25319
|
-
description: "Treat every discovered pair as enforced, ignoring .tooling.json#docs.enforced (a retired doc's link out of docs/, rule R7,
|
|
25665
|
+
description: "Treat every discovered pair as enforced, ignoring .tooling.json#docs.enforced (a retired doc's link out of docs/, rule R7, and an unlocatable plan, rule R9, stay advisory)"
|
|
25320
25666
|
},
|
|
25321
25667
|
json: {
|
|
25322
25668
|
type: "boolean",
|
|
@@ -25330,13 +25676,14 @@ const docsCheckCommand = defineCommand({
|
|
|
25330
25676
|
run({ args }) {
|
|
25331
25677
|
const targetDir = path.resolve(args.dir ?? ".");
|
|
25332
25678
|
const out = createCliOutput(args.json === true);
|
|
25333
|
-
const { payload, text, exitCode } = runDocsCheck(createRealDocsExecutor(targetDir), resolveDocsConfig(targetDir), {
|
|
25679
|
+
const { payload, text, exitCode, warnCount } = runDocsCheck(createRealDocsExecutor(targetDir), resolveDocsConfig(targetDir), {
|
|
25334
25680
|
now: /* @__PURE__ */ new Date(),
|
|
25335
25681
|
strict: args.strict === true,
|
|
25336
25682
|
verbose: args.verbose === true || isEnvVerbose()
|
|
25337
25683
|
});
|
|
25338
25684
|
out.json(payload);
|
|
25339
25685
|
if (text !== "") out.text(text);
|
|
25686
|
+
if (warnCount > 0) out.text(formatWarnMarker(warnCount));
|
|
25340
25687
|
process.exitCode = exitCode;
|
|
25341
25688
|
}
|
|
25342
25689
|
});
|
|
@@ -26754,7 +27101,10 @@ async function runIssueLabel(executor, options) {
|
|
|
26754
27101
|
...options.repo === void 0 ? {} : { repo: options.repo }
|
|
26755
27102
|
});
|
|
26756
27103
|
} catch (error) {
|
|
26757
|
-
if (error instanceof IssueNumberRequiredError)
|
|
27104
|
+
if (error instanceof IssueNumberRequiredError) {
|
|
27105
|
+
const command = options.action === "add" ? "issue:label" : "issue:unlabel";
|
|
27106
|
+
throw new FatalError(describeIssueNumberRequired(`bst ${command} 273 agent/implement`));
|
|
27107
|
+
}
|
|
26758
27108
|
throw error;
|
|
26759
27109
|
}
|
|
26760
27110
|
}
|
|
@@ -27870,7 +28220,7 @@ async function runCli(config) {
|
|
|
27870
28220
|
//#region src/cli-main.ts
|
|
27871
28221
|
setLogLevel(resolveLogLevel({}));
|
|
27872
28222
|
setForgejoLogger(log$1);
|
|
27873
|
-
const displayVersion = "0.
|
|
28223
|
+
const displayVersion = "0.94.0";
|
|
27874
28224
|
const bstSubCommands = {
|
|
27875
28225
|
"repo:sync": syncCommand,
|
|
27876
28226
|
"checks:run": runChecksCommand,
|