@bensandee/tooling 0.77.0 → 0.78.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.
Files changed (3) hide show
  1. package/README.md +20 -0
  2. package/dist/bin.mjs +1344 -199
  3. package/package.json +3 -2
package/dist/bin.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { appendFileSync, copyFileSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, renameSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
2
+ import { accessSync, appendFileSync, constants, copyFileSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
3
3
  import * as path$1 from "node:path";
4
4
  import path from "node:path";
5
5
  import { parseArgs, parseEnv } from "node:util";
@@ -13,6 +13,7 @@ import { applyEdits, modify, parse } from "jsonc-parser";
13
13
  import { z } from "zod";
14
14
  import spawn$1 from "cross-spawn";
15
15
  import { isMap, isScalar, isSeq, parse as parse$1, parseAllDocuments, parseDocument, stringify, visit } from "yaml";
16
+ import { parse as parse$2 } from "smol-toml";
16
17
  import picomatch from "picomatch";
17
18
  import { StringDecoder } from "node:string_decoder";
18
19
  import { tmpdir } from "node:os";
@@ -396,7 +397,7 @@ function loadEnvFileForBin(rawArgs, io = processIo) {
396
397
  if (resolveJsonMode(rawArgs)) {
397
398
  const document = buildErrorDocument({
398
399
  command: resolveCommandName(rawArgs),
399
- version: "0.77.0",
400
+ version: "0.78.0",
400
401
  kind: "usage",
401
402
  message
402
403
  });
@@ -4175,13 +4176,24 @@ function extractPinnedActions(existing) {
4175
4176
  * same action name, substitute the existing ref (including any inline comment like `# v6`)
4176
4177
  * into the generated output. This keeps SHA pins and their version annotations stable when
4177
4178
  * unrelated workflow changes force a rewrite.
4179
+ *
4180
+ * `owned` names actions whose ref the generator itself pins (the shared setup-mise action,
4181
+ * pinned in `mise-pin.ts`): for those the generated ref wins, so a new pin reaches a repo on its
4182
+ * next sync, and the ref gets the entry's comment (the tag it was taken from), which the
4183
+ * generator's step objects cannot carry. Preserving them would leave every repo on the commit it
4184
+ * first received.
4178
4185
  */
4179
- function preservePinnedActions(generated, existing) {
4180
- if (!existing) return generated;
4181
- const pins = extractPinnedActions(existing);
4182
- if (pins.size === 0) return generated;
4186
+ function preservePinnedActions(generated, existing, owned = []) {
4183
4187
  let result = generated;
4188
+ for (const { name, comment } of owned) {
4189
+ const pattern = new RegExp(String.raw`^(\s*(?:-\s+)?uses:\s*)${escapeRegex(name)}@(\S+)(?:[ \t]+#[^\n]*)?$`, "gm");
4190
+ result = result.replace(pattern, `$1${name}@$2 # ${comment}`);
4191
+ }
4192
+ if (!existing) return result;
4193
+ const pins = extractPinnedActions(existing);
4194
+ if (pins.size === 0) return result;
4184
4195
  for (const [name, tail] of pins) {
4196
+ if (owned.some((action) => action.name === name)) continue;
4185
4197
  const pattern = new RegExp(String.raw`^(\s*(?:-\s+)?uses:\s*)${escapeRegex(name)}@\S+(?:[ \t]+#[^\n]*)?$`, "gm");
4186
4198
  result = result.replace(pattern, `$1${name}@${tail}`);
4187
4199
  }
@@ -4295,6 +4307,30 @@ function declaredPublishRegistries(targetDir, structure, rootPackageJson) {
4295
4307
  return registries;
4296
4308
  }
4297
4309
  //#endregion
4310
+ //#region src/generators/mise-pin.ts
4311
+ /**
4312
+ * The mise version and release-asset digest the fleet's CI and the agent runner base image agree
4313
+ * to run. This is the one place that agreement is recorded; `packages/agent/test/runner-base-image-dockerfile.test.ts`
4314
+ * (task 8 of the mise-toolchain-provisioning plan) reads both constants back through
4315
+ * `readRepoFile` and asserts the base image's own `ARG MISE_VERSION` and `mise.sha256` manifest
4316
+ * agree with them, so the two packages cannot drift without a failing test on either side.
4317
+ *
4318
+ * Each constant sits under its own `// renovate:` marker comment, in the three-line grammar
4319
+ * `packages/agent/docker/agent-goose-image/goose.sha256` uses for its own digest line, so the same
4320
+ * shape of custom regex manager in the root `renovate.json` can rewrite both this version and this
4321
+ * digest (`customManagers` entries scoped to this file).
4322
+ */
4323
+ const MISE_VERSION = "2026.9.12";
4324
+ const MISE_LINUX_X64_SHA256 = "e79ae57945034903aee8aa2ea66b4c7ca9cd4f4edd5a8a78a589cbae6d0f428a";
4325
+ /** Where generated Forgejo workflows fetch the shared mise setup action: a composite action in
4326
+ * `bensandee/common`, on the forge every fleet repo lives on. GitHub cannot reach it; the
4327
+ * generators keep inline steps there. */
4328
+ const SETUP_MISE_ACTION_URL = "https://code.orangebikelabs.com/bensandee/common/actions/setup-mise";
4329
+ /** The tag in `bensandee/common` that marks the current release of the action. Generated `uses:`
4330
+ * lines carry it as their comment, and Renovate moves the SHA below to wherever it points. */
4331
+ const SETUP_MISE_ACTION_TAG = "setup-mise@latest";
4332
+ const SETUP_MISE_ACTION_SHA = "13f60e0f19ad57b5616f94c9bc18bf214bbe91fe";
4333
+ //#endregion
4298
4334
  //#region src/generators/ci-utils.ts
4299
4335
  /** Build a GitHub Actions expression like `${{ expr }}` without triggering no-template-curly-in-string. */
4300
4336
  function actionsExpr(expr) {
@@ -4389,13 +4425,99 @@ function npmAuthWiring(ctx, publishes) {
4389
4425
  tokenEnv: { NODE_AUTH_TOKEN: actionsExpr("secrets.NPM_TOKEN") }
4390
4426
  };
4391
4427
  }
4392
- function hasEnginesNode(ctx) {
4393
- const raw = ctx.read("package.json");
4394
- if (!raw) return false;
4395
- return typeof parsePackageJson(raw)?.engines?.["node"] === "string";
4428
+ const MISE_RELEASE_URL = `https://github.com/jdx/mise/releases/download/v${MISE_VERSION}/mise-v${MISE_VERSION}-linux-x64`;
4429
+ /**
4430
+ * The node script `inlineMiseSteps` pipes `mise env --json` into, run under the mise it just
4431
+ * installed. Reads the env map off stdin (mise exec passes it straight through) and exports every
4432
+ * key except `PATH` into `$GITHUB_ENV` in heredoc form -- the form that survives any value,
4433
+ * including one containing `$` or quotes -- then appends mise's shim directory and the
4434
+ * `~/.local/bin` install location to `$GITHUB_PATH` so every later step sees node, pnpm, and any
4435
+ * other declared tool on PATH with no prefix.
4436
+ */
4437
+ const MISE_ENV_EXPORT_SCRIPT = [
4438
+ "const fs = require(\"fs\");",
4439
+ "const env = JSON.parse(fs.readFileSync(0, \"utf8\"));",
4440
+ "let out = \"\";",
4441
+ "for (const [key, value] of Object.entries(env)) {",
4442
+ " if (key === \"PATH\") continue;",
4443
+ " out += key + \"<<BST_MISE_ENV_EOF\\n\" + value + \"\\nBST_MISE_ENV_EOF\\n\";",
4444
+ "}",
4445
+ "fs.appendFileSync(process.env.GITHUB_ENV, out);",
4446
+ "fs.appendFileSync(",
4447
+ " process.env.GITHUB_PATH,",
4448
+ " process.env.HOME + \"/.local/share/mise/shims\\n\" + process.env.HOME + \"/.local/bin\\n\",",
4449
+ ");"
4450
+ ].join("\n");
4451
+ /** A fresh object per call: one shared object in two jobs would serialize as a YAML alias. */
4452
+ function pnpmStoreCacheStep() {
4453
+ return { step: {
4454
+ name: "Cache pnpm store",
4455
+ uses: "actions/cache@v4",
4456
+ with: {
4457
+ path: "~/.local/share/pnpm/store",
4458
+ key: `pnpm-${actionsExpr("runner.os")}-${actionsExpr("hashFiles('pnpm-lock.yaml')")}`
4459
+ }
4460
+ } };
4396
4461
  }
4397
- function computeNodeVersionYaml(ctx) {
4398
- return hasEnginesNode(ctx) ? "node-version-file: package.json" : "node-version: \"24\"";
4462
+ /** Actions whose ref the generator pins itself, so `preservePinnedActions` must not keep a
4463
+ * repo's older ref for them. */
4464
+ const GENERATOR_OWNED_ACTIONS = [{
4465
+ name: SETUP_MISE_ACTION_URL,
4466
+ comment: SETUP_MISE_ACTION_TAG
4467
+ }];
4468
+ /**
4469
+ * The steps every workflow generator uses in place of `pnpm/action-setup` + `actions/setup-node`:
4470
+ * install the pinned `mise`, `mise install` the repo's `mise.toml` toolchain, export its env into
4471
+ * the rest of the job, and cache the pnpm store. `MISE_VERSION`/`MISE_LINUX_X64_SHA256`
4472
+ * (`mise-pin.ts`) are the same pin the agent runner's base image installs, checked by
4473
+ * `runner-base-image-dockerfile.test.ts`.
4474
+ *
4475
+ * On Forgejo the mise half is the shared composite action in `bensandee/common`, pinned by commit.
4476
+ * GitHub resolves actions only from github.com, so it keeps the same steps inline.
4477
+ */
4478
+ function miseSetupSteps(ci) {
4479
+ if (ci !== "forgejo") return inlineMiseSteps();
4480
+ return [{ step: {
4481
+ name: "Set up mise",
4482
+ uses: `${SETUP_MISE_ACTION_URL}@${SETUP_MISE_ACTION_SHA}`,
4483
+ with: {
4484
+ version: MISE_VERSION,
4485
+ sha256: MISE_LINUX_X64_SHA256
4486
+ }
4487
+ } }, pnpmStoreCacheStep()];
4488
+ }
4489
+ /**
4490
+ * The inline form of the shared action, for GitHub. Uses `mise env --json` piped into the
4491
+ * just-installed mise's own `node -e`, not `mise env --shell bash`: the JSON form is
4492
+ * shell-independent and the interpreter reading it is guaranteed to exist (mise just installed
4493
+ * it), so this needs no assumption about which shell the runner's `run:` steps use.
4494
+ */
4495
+ function inlineMiseSteps() {
4496
+ const installScript = [
4497
+ "curl -fsSL -o \"$RUNNER_TEMP/mise\" " + MISE_RELEASE_URL,
4498
+ `echo "${MISE_LINUX_X64_SHA256} $RUNNER_TEMP/mise" | sha256sum -c`,
4499
+ "mkdir -p \"$HOME/.local/bin\" && install -m 0755 \"$RUNNER_TEMP/mise\" \"$HOME/.local/bin/mise\"",
4500
+ "export MISE_TRUSTED_CONFIG_PATHS=\"$GITHUB_WORKSPACE\"",
4501
+ "export MISE_YES=1",
4502
+ "\"$HOME/.local/bin/mise\" install",
4503
+ `"$HOME/.local/bin/mise" env --json | "$HOME/.local/bin/mise" exec -- node -e '${MISE_ENV_EXPORT_SCRIPT}'`
4504
+ ].join("\n");
4505
+ return [
4506
+ { step: {
4507
+ name: "Cache mise tools",
4508
+ uses: "actions/cache@v4",
4509
+ with: {
4510
+ path: "~/.local/share/mise",
4511
+ key: `mise-${actionsExpr("runner.os")}-${actionsExpr("hashFiles('mise.toml')")}`
4512
+ }
4513
+ } },
4514
+ pnpmStoreCacheStep(),
4515
+ { step: {
4516
+ name: "Install mise",
4517
+ env: { GITHUB_TOKEN: "" },
4518
+ run: installScript
4519
+ } }
4520
+ ];
4399
4521
  }
4400
4522
  /**
4401
4523
  * Build the docker publish env block.
@@ -4462,14 +4584,10 @@ function resolveGenTimeHost(ctx) {
4462
4584
  if (!parsed) return null;
4463
4585
  return registryHostFromHostname(parsed.hostname);
4464
4586
  }
4465
- function publishSteps(nodeVersionYaml, host) {
4587
+ function publishSteps(ci, host) {
4466
4588
  return [
4467
4589
  { step: { uses: "actions/checkout@v6" } },
4468
- { step: { uses: "pnpm/action-setup@v5" } },
4469
- { step: {
4470
- uses: "actions/setup-node@v6",
4471
- with: nodeVersionYaml.startsWith("node-version-file") ? { "node-version-file": "package.json" } : { "node-version": "24" }
4472
- } },
4590
+ ...miseSetupSteps(ci),
4473
4591
  { step: { run: "pnpm install --frozen-lockfile" } },
4474
4592
  { step: {
4475
4593
  name: "Publish Docker images",
@@ -4622,7 +4740,8 @@ async function generateDeployCi(ctx) {
4622
4740
  description: "Publish workflow not generated for simple release strategy"
4623
4741
  };
4624
4742
  }
4625
- const steps = publishSteps(computeNodeVersionYaml(ctx), resolveGenTimeHost(ctx));
4743
+ const host = resolveGenTimeHost(ctx);
4744
+ const steps = publishSteps(ctx.config.ci, host);
4626
4745
  const content = buildWorkflowYaml({
4627
4746
  ci: ctx.config.ci,
4628
4747
  name: "Publish",
@@ -4645,7 +4764,7 @@ async function generateDeployCi(ctx) {
4645
4764
  description: "Publish workflow already up to date"
4646
4765
  };
4647
4766
  }
4648
- const withPinnedActions = preservePinnedActions(withDevPath, existing);
4767
+ const withPinnedActions = preservePinnedActions(withDevPath, existing, GENERATOR_OWNED_ACTIONS);
4649
4768
  const final = existing ? insertCustomBlocks(withPinnedActions, extractCustomBlocks(existing)) : withPinnedActions;
4650
4769
  ctx.write(workflowPath, final);
4651
4770
  return {
@@ -4864,36 +4983,18 @@ function diagnoseCheckScriptForwardingAt(targetDir) {
4864
4983
  //#endregion
4865
4984
  //#region src/generators/package-json.ts
4866
4985
  /**
4867
- * Single source of truth for the supported Node floor. `engines.node` is the
4868
- * honest minimum (`>=<floor>`); `devEngines.runtime.version` is the caret form
4869
- * (`^<floor>` = `>=floor <nextmajor>`), which serves two masters at once:
4986
+ * Single source of truth for the supported Node floor: `engines.node` (`>=<floor>`) is the
4987
+ * honest minimum this package declares. `mise.toml`'s `tools.node` (`generators/mise.ts`) is the
4988
+ * exact pin every consumer -- the agent's containers, generated CI, a developer's shell -- now
4989
+ * installs and runs, seeded from this same floor on first generation.
4870
4990
  *
4871
- * - `actions/setup-node` reads `devEngines.runtime` (for `name: "node"`) before
4872
- * `engines.node`. The `<nextmajor>` upper bound keeps it on the pinned LTS
4873
- * major (resolving to the latest patch within it) instead of floating to the
4874
- * newest released major (e.g. Node 26) the way a bare `>=` range would.
4875
- * - pnpm resolves `devEngines.runtime.version` to its floor and checks it
4876
- * against `engines.node`. The caret's floor equals the engines floor, so the
4877
- * two never contradict -- a `<major>.x` floor of `<major>.0.0` sits below
4878
- * `>=<floor>` and makes `pnpm install` emit a spurious "Unsupported engine"
4879
- * warning. The floor MUST track `engines.node`, so it is derived from it
4880
- * rather than hard-coded.
4881
- *
4882
- * `onFail: "warn"` keeps a local Node mismatch non-fatal.
4991
+ * `devEngines.runtime` used to carry a caret form of this floor for `actions/setup-node` and
4992
+ * pnpm's runtime-download check to read; both are retired by the shared "Install mise" workflow
4993
+ * step and `mise install` respectively, so the field is removed wherever this generator finds it
4994
+ * (`applyMetadataDefaults`, `generateWorkspaceEngines`) rather than kept in sync.
4883
4995
  */
4884
4996
  const NODE_ENGINES_FLOOR = "24.13.0";
4885
4997
  const ENGINES_NODE = `>=${NODE_ENGINES_FLOOR}`;
4886
- /** The `^<floor>` runtime range whose floor satisfies a `>=<floor>` engines range. */
4887
- function caretRange(floor) {
4888
- return `^${floor}`;
4889
- }
4890
- function devEnginesRuntime() {
4891
- return { runtime: {
4892
- name: "node",
4893
- version: caretRange(NODE_ENGINES_FLOOR),
4894
- onFail: "warn"
4895
- } };
4896
- }
4897
4998
  function versionPart(s) {
4898
4999
  return s && s !== "x" && s !== "*" ? Number(s) : 0;
4899
5000
  }
@@ -4950,65 +5051,16 @@ function runtimeFloorSatisfies(range, enginesNode) {
4950
5051
  return compareTuples(dev, eng) >= 0;
4951
5052
  }
4952
5053
  /**
4953
- * Decide how to reconcile a package's `devEngines.runtime` (node entries) with
4954
- * its `engines.node` floor, without mutating anything. The caret form `^<floor>`
4955
- * keeps pnpm quiet (its floor equals the engines floor) while staying bounded to
4956
- * the same major for `setup-node`.
4957
- *
4958
- * - `noop` -- no node runtime declared, or every node runtime already satisfies
4959
- * the engines floor (a deliberate higher pin is left alone). Never adds a
4960
- * `devEngines.runtime` that isn't already there.
4961
- * - `warn` -- a node runtime is declared but `engines.node` has no determinable
4962
- * floor; leave it unchanged rather than guess.
4963
- * - `fix` -- `staleIndices` index the node runtimes (in array order, or `[0]`
4964
- * for the single-object form) whose `version` must become `desired`.
4965
- */
4966
- function planDevEnginesFix(pkg, enginesNode) {
4967
- const runtimeField = pkg["devEngines"]?.runtime;
4968
- if (!runtimeField) return { kind: "noop" };
4969
- const nodeEntries = (Array.isArray(runtimeField) ? runtimeField : [runtimeField]).map((runtime, index) => ({
4970
- runtime,
4971
- index
4972
- })).filter(({ runtime }) => runtime.name === "node" && typeof runtime.version === "string");
4973
- if (nodeEntries.length === 0) return { kind: "noop" };
4974
- const floor = minVersionTuple(enginesNode);
4975
- if (!floor) return {
4976
- kind: "warn",
4977
- message: `could not determine a floor from engines.node "${enginesNode}"`
4978
- };
4979
- const desired = caretRange(`${floor[0]}.${floor[1]}.${floor[2]}`);
4980
- const staleIndices = nodeEntries.filter(({ runtime }) => !runtimeFloorSatisfies(runtime.version ?? "", enginesNode)).map(({ index }) => index);
4981
- if (staleIndices.length === 0) return { kind: "noop" };
4982
- return {
4983
- kind: "fix",
4984
- desired,
4985
- staleIndices
4986
- };
4987
- }
4988
- /**
4989
- * Correct an existing `devEngines.runtime` (node) in place so its floor matches
4990
- * `engines.node` -- only the `version` changes; `name` and `onFail` are
4991
- * preserved. Never adds `devEngines.runtime` to a package that lacks it.
5054
+ * Remove `devEngines.runtime` from a package.json object in place, dropping `devEngines`
5055
+ * entirely once it is left empty. Returns `true` when something was removed, so callers can push
5056
+ * their own change description (the wording differs between the root generator and the
5057
+ * nested-workspace pass).
4992
5058
  */
4993
- function ensureDevEnginesConsistent(pkg, enginesNode, changes) {
4994
- const plan = planDevEnginesFix(pkg, enginesNode);
4995
- if (plan.kind === "warn") {
4996
- log$1.warn(`package.json: ${plan.message}; leaving devEngines.runtime unchanged`);
4997
- return;
4998
- }
4999
- if (plan.kind !== "fix") return;
5000
- const runtimeField = pkg["devEngines"]?.runtime;
5001
- let runtimes;
5002
- if (Array.isArray(runtimeField)) runtimes = runtimeField;
5003
- else if (runtimeField) runtimes = [runtimeField];
5004
- else runtimes = [];
5005
- for (const index of plan.staleIndices) {
5006
- const runtime = runtimes[index];
5007
- if (!runtime) continue;
5008
- const previous = runtime.version;
5009
- runtime.version = plan.desired;
5010
- changes.push(`bumped devEngines.runtime node ${previous} -> ${plan.desired}`);
5011
- }
5059
+ function removeDevEnginesRuntime(pkg) {
5060
+ if (!pkg["devEngines"]?.runtime) return false;
5061
+ delete pkg["devEngines"].runtime;
5062
+ if (Object.keys(pkg["devEngines"]).length === 0) delete pkg["devEngines"];
5063
+ return true;
5012
5064
  }
5013
5065
  /** Scripts every repo gets, whatever its language. */
5014
5066
  const HARNESS_SCRIPTS = {
@@ -5579,8 +5631,7 @@ function applyMetadataDefaults(pkg, ctx, changes) {
5579
5631
  pkg["engines"] = { node: ENGINES_NODE };
5580
5632
  changes.push(`set engines.node ${ENGINES_NODE}`);
5581
5633
  }
5582
- const enginesNode = pkg["engines"]?.["node"];
5583
- if (typeof enginesNode === "string") ensureDevEnginesConsistent(pkg, enginesNode, changes);
5634
+ if (removeDevEnginesRuntime(pkg)) changes.push("removed devEngines.runtime (mise.toml declares the runtime)");
5584
5635
  const pinnedPnpm = {
5585
5636
  "@changesets/cli": "3.0.3",
5586
5637
  "@release-it/bumper": "8.0.1",
@@ -5637,9 +5688,9 @@ function buildManagedDevDeps(ctx) {
5637
5688
  const isMonorepo = ctx.config.structure === "monorepo";
5638
5689
  const devDeps = { ...rootDevDeps(ctx.config) };
5639
5690
  if (!isMonorepo) Object.assign(devDeps, perPackageDevDeps(ctx.config));
5640
- if (ctx.config.stack !== "none" || ctx.config.ci === "forgejo") devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.14.4";
5641
- if (!isWorkspacePackage(ctx, "@bensandee/tooling")) devDeps["@bensandee/tooling"] = "0.77.0";
5642
- if (ctx.config.useEslintPlugin && ctx.config.stack !== "none") devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.15.1";
5691
+ if (ctx.config.stack !== "none" || ctx.config.ci === "forgejo") devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.14.5";
5692
+ if (!isWorkspacePackage(ctx, "@bensandee/tooling")) devDeps["@bensandee/tooling"] = "0.78.0";
5693
+ if (ctx.config.useEslintPlugin && ctx.config.stack !== "none") devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.15.2";
5643
5694
  if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
5644
5695
  "@changesets/cli": "3.0.3",
5645
5696
  "@release-it/bumper": "8.0.1",
@@ -5727,7 +5778,6 @@ async function generatePackageJson(ctx) {
5727
5778
  scripts: allScripts,
5728
5779
  devDependencies: Object.fromEntries(Object.entries(devDeps).filter(([name]) => !unmanaged.has(name))),
5729
5780
  engines: { node: ENGINES_NODE },
5730
- devEngines: devEnginesRuntime(),
5731
5781
  packageManager: `pnpm@${{
5732
5782
  "@changesets/cli": "3.0.3",
5733
5783
  "@release-it/bumper": "8.0.1",
@@ -5755,16 +5805,15 @@ async function generatePackageJson(ctx) {
5755
5805
  };
5756
5806
  }
5757
5807
  /**
5758
- * Reconcile `devEngines.runtime` against `engines.node` in nested workspace
5759
- * packages -- pnpm's "Unsupported engine" warning fires per-package, so a
5760
- * subpackage that declares both fields can warn even when the root is clean.
5808
+ * Remove `devEngines.runtime` from nested workspace packages -- the same removal
5809
+ * `applyMetadataDefaults` does for the root, extended to `packages/*` because `mise.toml` is a
5810
+ * root-only declaration and a subpackage that still carries the field would otherwise be the one
5811
+ * place in the repo still asking `actions/setup-node`/pnpm to resolve a runtime.
5761
5812
  *
5762
- * Unlike the root `package.json` (a fully managed file), a subpackage's
5763
- * `package.json` is otherwise owned by the user, so this edits *only* the stale
5764
- * `version` string via `jsonc-parser` rather than reserialising the whole file
5765
- * (which would reorder keys and produce a large surprising diff). It never adds
5766
- * `engines.node` or `devEngines.runtime` -- it touches a package only when both
5767
- * already exist and a node runtime's floor is below the engines floor.
5813
+ * Unlike the root `package.json` (a fully managed file), a subpackage's `package.json` is
5814
+ * otherwise owned by the user, so this edits *only* the `devEngines`/`devEngines.runtime` nodes
5815
+ * via `jsonc-parser` rather than reserialising the whole file (which would reorder keys and
5816
+ * produce a large surprising diff).
5768
5817
  *
5769
5818
  * Scans `packages/*` (the same convention as the rest of the tool); other
5770
5819
  * workspace layouts (`apps/*`, custom globs) are out of scope.
@@ -5773,42 +5822,25 @@ async function generateWorkspaceEngines(ctx) {
5773
5822
  if (ctx.config.structure !== "monorepo") return [];
5774
5823
  const results = [];
5775
5824
  for (const member of getMonorepoPackages(ctx.targetDir)) {
5776
- const result = reconcileWorkspaceDevEngines(ctx, path.join(path.relative(ctx.targetDir, member.dir), "package.json"));
5825
+ const result = removeWorkspaceDevEnginesRuntime(ctx, path.join(path.relative(ctx.targetDir, member.dir), "package.json"));
5777
5826
  if (result) results.push(result);
5778
5827
  }
5779
5828
  return results;
5780
5829
  }
5781
- /** Surgically fix a single nested package.json's devEngines, preserving its formatting. */
5782
- function reconcileWorkspaceDevEngines(ctx, relPath) {
5830
+ /** Surgically remove a single nested package.json's `devEngines.runtime`, preserving its
5831
+ * formatting. */
5832
+ function removeWorkspaceDevEnginesRuntime(ctx, relPath) {
5783
5833
  const raw = ctx.read(relPath);
5784
5834
  if (!raw) return void 0;
5785
5835
  const pkg = parsePackageJson(raw);
5786
- if (!pkg) return void 0;
5787
- const enginesNode = pkg["engines"]?.["node"];
5788
- if (typeof enginesNode !== "string") return void 0;
5789
- const plan = planDevEnginesFix(pkg, enginesNode);
5790
- if (plan.kind === "warn") {
5791
- log$1.warn(`${relPath}: ${plan.message}; leaving devEngines.runtime unchanged`);
5792
- return;
5793
- }
5794
- if (plan.kind !== "fix") return void 0;
5795
- const isArray = Array.isArray(pkg["devEngines"]?.runtime);
5796
- let text = raw;
5797
- for (const index of plan.staleIndices) text = applyEdits(text, modify(text, isArray ? [
5798
- "devEngines",
5799
- "runtime",
5800
- index,
5801
- "version"
5802
- ] : [
5803
- "devEngines",
5804
- "runtime",
5805
- "version"
5806
- ], plan.desired, {}));
5836
+ if (!pkg?.["devEngines"]?.runtime) return void 0;
5837
+ const jsonPath = Object.keys(pkg["devEngines"]).length === 1 ? ["devEngines"] : ["devEngines", "runtime"];
5838
+ const text = applyEdits(raw, modify(raw, jsonPath, void 0, {}));
5807
5839
  ctx.write(relPath, text);
5808
5840
  return {
5809
5841
  filePath: relPath,
5810
5842
  action: "updated",
5811
- description: `bumped devEngines.runtime node -> ${plan.desired}`
5843
+ description: "removed devEngines.runtime (mise.toml declares the runtime)"
5812
5844
  };
5813
5845
  }
5814
5846
  //#endregion
@@ -6939,6 +6971,183 @@ async function generateTsdown(ctx) {
6939
6971
  };
6940
6972
  }
6941
6973
  //#endregion
6974
+ //#region src/generators/mise.ts
6975
+ /**
6976
+ * `mise.toml` owns exactly two keys, `tools.node` and `tools.pnpm`, and carries every other byte
6977
+ * -- other tools, `[env]`, `[tasks.*]`, comments -- through untouched. `smol-toml`'s `parse` is
6978
+ * used only to validate the file and to inspect the owned keys' shapes; it preserves no layout, so
6979
+ * every mutation below is a textual edit against the raw source rather than a reserialization.
6980
+ * `generators/package-json.ts` documents the same "manage the keys, not the file" posture for
6981
+ * `devEngines.runtime`.
6982
+ */
6983
+ const TOOLS_HEADER_RE = /^\[tools\]\s*$/m;
6984
+ /** Any top-level table header (`[x]` or `[x.y]`) or array-of-tables header (`[[x]]`), used to find
6985
+ * where `[tools]`'s own range ends -- an array-of-tables entry is a fresh table just as much as a
6986
+ * `[x]` header is, so it must terminate the body range too, or an owned line appended past it
6987
+ * lands inside that entry instead of `[tools]`. */
6988
+ const TOP_LEVEL_HEADER_RE = /^\[{1,2}[^[].*\]{1,2}\s*$/m;
6989
+ const PNPM_LINE_RE = /^pnpm\s*=.*$/m;
6990
+ /** Tolerant shape of the file's `[tools]` table: string or table form for every key, so a
6991
+ * table-form `node`/`pnpm` is caught rather than mistaken for absent. */
6992
+ const ToolsTableSchema = z.record(z.string(), z.union([z.string(), z.record(z.string(), z.unknown())]));
6993
+ /** Tolerant shape of the whole parsed document -- only `tools` is read. */
6994
+ const ParsedTomlSchema = z.looseObject({ tools: z.unknown().optional() });
6995
+ /** The `[tools]` table's body range: right after its header line to the next top-level header or
6996
+ * EOF. `undefined` when there is no `[tools]` table at all. */
6997
+ function toolsRange(source) {
6998
+ const headerMatch = TOOLS_HEADER_RE.exec(source);
6999
+ if (!headerMatch) return void 0;
7000
+ const start = headerMatch.index + headerMatch[0].length;
7001
+ const rest = source.slice(start);
7002
+ const nextHeader = TOP_LEVEL_HEADER_RE.exec(rest);
7003
+ return {
7004
+ start,
7005
+ end: nextHeader ? start + nextHeader.index : source.length
7006
+ };
7007
+ }
7008
+ /** Strip the version out of `packageManager` (`pnpm@11.27.0` -> `11.27.0`). */
7009
+ function pnpmVersionFromPackageManager(packageManager) {
7010
+ return /^pnpm@(\S+)$/.exec(packageManager ?? "")?.[1];
7011
+ }
7012
+ /** The exact pin to seed `tools.node` with: the higher of `enginesNode`'s own floor (e.g. a repo
7013
+ * whose `package.json` already declares `>=24.14.0`) and {@link NODE_ENGINES_FLOOR}, so a loose
7014
+ * `engines.node` (`>=24`, `24.x`) can never seed a pin below the supported floor -- the same
7015
+ * derive-from-engines discipline the retired `devEngines.runtime` generator used. */
7016
+ function nodePinFor(enginesNode) {
7017
+ const tuple = enginesNode ? minVersionTuple(enginesNode) : void 0;
7018
+ const floorTuple = minVersionTuple(NODE_ENGINES_FLOOR);
7019
+ if (!tuple || !floorTuple) return NODE_ENGINES_FLOOR;
7020
+ return compareTuples(tuple, floorTuple) > 0 ? tuple.join(".") : NODE_ENGINES_FLOOR;
7021
+ }
7022
+ /** The two owned lines, in order, for a file created from scratch (or a `[tools]` table added to
7023
+ * one that had none). */
7024
+ function ownedLines(pnpmVersion, enginesNode) {
7025
+ const lines = [`[tools]`, `node = "${nodePinFor(enginesNode)}"`];
7026
+ if (pnpmVersion) lines.push(`pnpm = "${pnpmVersion}"`);
7027
+ return lines;
7028
+ }
7029
+ /** Append a `key = "value"` line to a `[tools]` body, respecting whatever trailing newline it
7030
+ * already has. */
7031
+ function appendLine(body, line) {
7032
+ return (body.endsWith("\n") || body.length === 0 ? body : body + "\n") + line + "\n";
7033
+ }
7034
+ /** `action: "failed"` naming an owned key present in table form. */
7035
+ function tableFormFailure(filePath, key) {
7036
+ return {
7037
+ filePath,
7038
+ action: "failed",
7039
+ description: `mise.toml: tools.${key} is a table; repo:sync owns it as a plain string pin`
7040
+ };
7041
+ }
7042
+ /** `true` when `pinned` is a version string whose floor sits below `enginesNode`'s -- both must
7043
+ * parse for the comparison to mean anything; an unparseable value is left for `runtimeFloorSatisfies`
7044
+ * (which itself defaults to "leave it alone") to decide. */
7045
+ function isBelowFloor(pinned, enginesNode) {
7046
+ if (minVersionTuple(pinned) === void 0 || minVersionTuple(enginesNode) === void 0) return false;
7047
+ return !runtimeFloorSatisfies(pinned, enginesNode);
7048
+ }
7049
+ /** Resolve the two facts `mise.toml` needs from `package.json`: the engines floor to validate an
7050
+ * existing `tools.node` pin against, and the pnpm version to keep `tools.pnpm` in step with. */
7051
+ function readPackageJsonFacts(ctx) {
7052
+ const pkgRaw = ctx.read("package.json");
7053
+ const pkg = pkgRaw ? parsePackageJson(pkgRaw) : void 0;
7054
+ return {
7055
+ enginesNode: pkg?.engines?.["node"],
7056
+ pnpmVersion: pnpmVersionFromPackageManager(pkg?.packageManager)
7057
+ };
7058
+ }
7059
+ /** Edit an existing `[tools]` body in place: add `node` only when absent, rewrite `pnpm` only on
7060
+ * drift. Returns the new body and the change descriptions, or `undefined` for the body when
7061
+ * nothing changed. */
7062
+ function editToolsBody(body, existingNode, existingPnpm, pnpmVersion, enginesNode) {
7063
+ let newBody = body;
7064
+ const changes = [];
7065
+ if (existingNode === void 0) {
7066
+ const pin = nodePinFor(enginesNode);
7067
+ newBody = appendLine(newBody, `node = "${pin}"`);
7068
+ changes.push(`added tools.node = "${pin}"`);
7069
+ }
7070
+ if (pnpmVersion && existingPnpm !== pnpmVersion) {
7071
+ if (existingPnpm === void 0) {
7072
+ newBody = appendLine(newBody, `pnpm = "${pnpmVersion}"`);
7073
+ changes.push(`added tools.pnpm = "${pnpmVersion}"`);
7074
+ } else {
7075
+ newBody = newBody.replace(PNPM_LINE_RE, (line) => {
7076
+ return `pnpm = "${pnpmVersion}"${/(\s*#.*)$/.exec(line)?.[1] ?? ""}`;
7077
+ });
7078
+ changes.push(`updated tools.pnpm ${existingPnpm} -> ${pnpmVersion}`);
7079
+ }
7080
+ }
7081
+ return {
7082
+ body: newBody,
7083
+ changes
7084
+ };
7085
+ }
7086
+ async function generateMise(ctx) {
7087
+ const filePath = "mise.toml";
7088
+ const { enginesNode, pnpmVersion } = readPackageJsonFacts(ctx);
7089
+ const existing = ctx.read(filePath);
7090
+ if (!existing) {
7091
+ ctx.write(filePath, ownedLines(pnpmVersion, enginesNode).join("\n") + "\n");
7092
+ return {
7093
+ filePath,
7094
+ action: "created",
7095
+ description: "Generated mise.toml"
7096
+ };
7097
+ }
7098
+ let parsed;
7099
+ try {
7100
+ parsed = parse$2(existing);
7101
+ } catch (error) {
7102
+ return {
7103
+ filePath,
7104
+ action: "failed",
7105
+ description: `Could not parse existing mise.toml -- ${error instanceof Error ? error.message : String(error)}`
7106
+ };
7107
+ }
7108
+ const document = ParsedTomlSchema.safeParse(parsed);
7109
+ const parsedTools = document.success ? document.data.tools ?? {} : {};
7110
+ const toolsResult = ToolsTableSchema.safeParse(parsedTools);
7111
+ const toolsTable = toolsResult.success ? toolsResult.data : {};
7112
+ if (typeof toolsTable["node"] === "object") return tableFormFailure(filePath, "node");
7113
+ if (typeof toolsTable["pnpm"] === "object") return tableFormFailure(filePath, "pnpm");
7114
+ const existingNode = toolsTable["node"];
7115
+ const existingPnpm = toolsTable["pnpm"];
7116
+ if (existingNode !== void 0 && enginesNode !== void 0 && isBelowFloor(existingNode, enginesNode)) return {
7117
+ filePath,
7118
+ action: "failed",
7119
+ description: `mise.toml: tools.node "${existingNode}" is below engines.node "${enginesNode}"`
7120
+ };
7121
+ const range = toolsRange(existing);
7122
+ let text;
7123
+ let changes;
7124
+ if (range === void 0) {
7125
+ if (Object.keys(toolsTable).length > 0) return {
7126
+ filePath,
7127
+ action: "failed",
7128
+ description: `mise.toml: tools.* is declared with dotted keys; repo:sync only manages a [tools] table`
7129
+ };
7130
+ text = ownedLines(pnpmVersion, enginesNode).join("\n") + "\n\n" + existing;
7131
+ changes = ["added [tools] table"];
7132
+ } else {
7133
+ const body = existing.slice(range.start, range.end);
7134
+ const edited = editToolsBody(body, existingNode, existingPnpm, pnpmVersion, enginesNode);
7135
+ changes = edited.changes;
7136
+ text = edited.body === body ? existing : existing.slice(0, range.start) + edited.body + existing.slice(range.end);
7137
+ }
7138
+ if (changes.length === 0) return {
7139
+ filePath,
7140
+ action: "skipped",
7141
+ description: "Already up to spec"
7142
+ };
7143
+ ctx.write(filePath, text);
7144
+ return {
7145
+ filePath,
7146
+ action: "updated",
7147
+ description: changes.join(", ")
7148
+ };
7149
+ }
7150
+ //#endregion
6942
7151
  //#region ../forgejo/src/fetcher.ts
6943
7152
  /**
6944
7153
  * A far larger cap for the two calls that legitimately transfer a whole file -- a release-asset
@@ -7716,7 +7925,7 @@ function postReleaseHookJob(opts) {
7716
7925
  needs: opts.needsJob,
7717
7926
  if: opts.publishCondition,
7718
7927
  "continue-on-error": true,
7719
- steps: [...commonSteps(opts.nodeVersionYaml, []), { step: {
7928
+ steps: [...commonSteps(opts.ci, []), { step: {
7720
7929
  name: "Post-release hook",
7721
7930
  env: {
7722
7931
  ...hookSecretEnv(opts.secrets),
@@ -7755,27 +7964,20 @@ function deployDispatchStep(opts) {
7755
7964
  } };
7756
7965
  }
7757
7966
  /** Common setup steps shared by release-it and simple strategies. */
7758
- function commonSteps(nodeVersionYaml, authSteps) {
7967
+ function commonSteps(ci, authSteps) {
7759
7968
  return [
7760
7969
  { step: {
7761
7970
  uses: "actions/checkout@v6",
7762
7971
  with: { "fetch-depth": 0 }
7763
7972
  } },
7764
- { step: { uses: "pnpm/action-setup@v5" } },
7765
- { step: {
7766
- uses: "actions/setup-node@v6",
7767
- with: {
7768
- ...nodeVersionYaml.startsWith("node-version-file") ? { "node-version-file": "package.json" } : { "node-version": "24" },
7769
- cache: "pnpm"
7770
- }
7771
- } },
7973
+ ...miseSetupSteps(ci),
7772
7974
  ...authSteps,
7773
7975
  { step: { run: "pnpm install --frozen-lockfile" } }
7774
7976
  ];
7775
7977
  }
7776
- function releaseItSteps(ci, nodeVersionYaml, npmAuth) {
7978
+ function releaseItSteps(ci, npmAuth) {
7777
7979
  const tokenEnv = ci === "github" ? { GITHUB_TOKEN: actionsExpr("secrets.RELEASE_TOKEN") } : { RELEASE_TOKEN: actionsExpr("secrets.RELEASE_TOKEN") };
7778
- return [...commonSteps(nodeVersionYaml, npmAuth.steps), { step: {
7980
+ return [...commonSteps(ci, npmAuth.steps), { step: {
7779
7981
  run: "pnpm release-it --ci",
7780
7982
  env: {
7781
7983
  ...tokenEnv,
@@ -7827,7 +8029,7 @@ function simpleReleaseEnv() {
7827
8029
  };
7828
8030
  }
7829
8031
  function simpleReleaseSteps(ctx) {
7830
- const { ci, nodeVersionYaml, npmAuth, hasDocker, hasAssets, host } = ctx;
8032
+ const { ci, npmAuth, hasDocker, hasAssets, host } = ctx;
7831
8033
  const releaseEnv = connectionEnv(ci);
7832
8034
  const pushedIf = "steps.release.outputs.pushed == 'true'";
7833
8035
  const releaseStep = { step: {
@@ -7853,7 +8055,7 @@ function simpleReleaseSteps(ctx) {
7853
8055
  run: "pnpm exec bst docker:publish"
7854
8056
  } };
7855
8057
  return [
7856
- ...commonSteps(nodeVersionYaml, npmAuth.steps),
8058
+ ...commonSteps(ci, npmAuth.steps),
7857
8059
  releaseStep,
7858
8060
  ...hasAssets ? [assetsStep] : [],
7859
8061
  ...hasDocker ? [dockerStep] : []
@@ -7861,7 +8063,7 @@ function simpleReleaseSteps(ctx) {
7861
8063
  }
7862
8064
  function buildSteps(strategy, ctx) {
7863
8065
  switch (strategy) {
7864
- case "release-it": return releaseItSteps(ctx.ci, ctx.nodeVersionYaml, ctx.npmAuth);
8066
+ case "release-it": return releaseItSteps(ctx.ci, ctx.npmAuth);
7865
8067
  case "simple": return simpleReleaseSteps(ctx);
7866
8068
  default: return null;
7867
8069
  }
@@ -7881,7 +8083,6 @@ async function generateReleaseCi(ctx) {
7881
8083
  };
7882
8084
  const isGitHub = ctx.config.ci === "github";
7883
8085
  const workflowPath = isGitHub ? ".github/workflows/release.yml" : ".forgejo/workflows/release.yml";
7884
- const nodeVersionYaml = computeNodeVersionYaml(ctx);
7885
8086
  const hasDocker = hasDockerPackages(ctx) && ctx.config.publishDocker;
7886
8087
  const hasAssets = hasReleaseAssets(ctx) || hasVersionStamp(ctx);
7887
8088
  const host = resolveGenTimeHost(ctx);
@@ -7889,7 +8090,6 @@ async function generateReleaseCi(ctx) {
7889
8090
  const hookSecrets = getPostReleaseHookSecrets(ctx);
7890
8091
  const steps = buildSteps(ctx.config.releaseStrategy, {
7891
8092
  ci: ctx.config.ci,
7892
- nodeVersionYaml,
7893
8093
  npmAuth,
7894
8094
  hasDocker,
7895
8095
  hasAssets,
@@ -7923,12 +8123,12 @@ async function generateReleaseCi(ctx) {
7923
8123
  tag: actionsExpr("steps.release.outputs.tag")
7924
8124
  };
7925
8125
  extraJobs = { "post-release-hook": postReleaseHookJob({
8126
+ ci: ctx.config.ci,
7926
8127
  needsJob: "release",
7927
8128
  publishCondition: "needs.release.outputs.pushed == 'true'",
7928
8129
  releaseTagExpr: actionsExpr("needs.release.outputs.tag"),
7929
8130
  command: getPostReleaseHookCommand(ctx),
7930
- secrets: hookSecrets,
7931
- nodeVersionYaml
8131
+ secrets: hookSecrets
7932
8132
  }) };
7933
8133
  }
7934
8134
  const on = ctx.config.releaseStrategy === "simple" ? simpleWorkflowDispatchTrigger() : { workflow_dispatch: null };
@@ -7958,7 +8158,7 @@ async function generateReleaseCi(ctx) {
7958
8158
  description: "Release workflow already up to date"
7959
8159
  };
7960
8160
  }
7961
- const withPinnedActions = preservePinnedActions(withDevPath, existing);
8161
+ const withPinnedActions = preservePinnedActions(withDevPath, existing, GENERATOR_OWNED_ACTIONS);
7962
8162
  const final = existing ? insertCustomBlocks(withPinnedActions, extractCustomBlocks(existing)) : withPinnedActions;
7963
8163
  ctx.write(workflowPath, final);
7964
8164
  return {
@@ -8035,22 +8235,14 @@ const CI_CONCURRENCY = {
8035
8235
  group: `ci-${actionsExpr("github.ref")}`,
8036
8236
  "cancel-in-progress": actionsExpr(IS_NOT_MAIN_BRANCH)
8037
8237
  };
8038
- function checkSteps(nodeVersionYaml, authSteps, coverageEnabled, emitDockerSweepStep) {
8039
- const isNodeVersionFile = nodeVersionYaml.startsWith("node-version-file");
8238
+ function checkSteps(ci, authSteps, coverageEnabled, emitDockerSweepStep) {
8040
8239
  const checkCommand = coverageEnabled ? "pnpm ci:check" : "pnpm ci:check --no-coverage";
8041
8240
  return [
8042
8241
  { step: {
8043
8242
  uses: "actions/checkout@v6",
8044
8243
  with: { "fetch-depth": 0 }
8045
8244
  } },
8046
- { step: { uses: "pnpm/action-setup@v5" } },
8047
- { step: {
8048
- uses: "actions/setup-node@v6",
8049
- with: {
8050
- ...isNodeVersionFile ? { "node-version-file": "package.json" } : { "node-version": "24" },
8051
- cache: "pnpm"
8052
- }
8053
- } },
8245
+ ...miseSetupSteps(ci),
8054
8246
  ...authSteps,
8055
8247
  { step: { run: "pnpm install --frozen-lockfile" } },
8056
8248
  ...emitDockerSweepStep ? [{ step: {
@@ -8076,7 +8268,6 @@ async function generateCi(ctx) {
8076
8268
  const isGitHub = ctx.config.ci === "github";
8077
8269
  const isForgejo = !isGitHub;
8078
8270
  const isChangesets = ctx.config.releaseStrategy === "changesets";
8079
- const nodeVersionYaml = computeNodeVersionYaml(ctx);
8080
8271
  const publishesNpm = ctx.config.publishNpm === true;
8081
8272
  const hookCommand = getPostReleaseHookCommand(ctx);
8082
8273
  const hookSecrets = getPostReleaseHookSecrets(ctx);
@@ -8105,7 +8296,7 @@ async function generateCi(ctx) {
8105
8296
  }
8106
8297
  const npmAuth = npmAuthWiring(ctx, isChangesets && publishesNpm && !isGitHub);
8107
8298
  const steps = [
8108
- ...checkSteps(nodeVersionYaml, npmAuth.steps, coverage.enabled, hasDockerPackages(ctx)),
8299
+ ...checkSteps(ctx.config.ci, npmAuth.steps, coverage.enabled, hasDockerPackages(ctx)),
8109
8300
  ...emitCoverageSteps ? coverageHistorySteps() : [],
8110
8301
  ...healthSteps(emitHealthHistory),
8111
8302
  ...isChangesets ? [changesetsReleaseStep(ctx.config.ci, publishesNpm, npmAuth.tokenEnv)] : [],
@@ -8138,12 +8329,12 @@ async function generateCi(ctx) {
8138
8329
  tag: changesetsTagOutputExpr
8139
8330
  };
8140
8331
  extraJobs = { "post-release-hook": postReleaseHookJob({
8332
+ ci: ctx.config.ci,
8141
8333
  needsJob: "check",
8142
8334
  publishCondition: "needs.check.outputs.published == 'true'",
8143
8335
  releaseTagExpr: actionsExpr("needs.check.outputs.tag"),
8144
8336
  command: hookCommand,
8145
- secrets: hookSecrets,
8146
- nodeVersionYaml
8337
+ secrets: hookSecrets
8147
8338
  }) };
8148
8339
  }
8149
8340
  const checkJobEnv = {
@@ -8185,7 +8376,7 @@ async function generateCi(ctx) {
8185
8376
  description: "CI workflow already up to date"
8186
8377
  };
8187
8378
  }
8188
- const withPinnedActions = preservePinnedActions(withDevPath, existing);
8379
+ const withPinnedActions = preservePinnedActions(withDevPath, existing, GENERATOR_OWNED_ACTIONS);
8189
8380
  const final = existing ? insertCustomBlocks(withPinnedActions, extractCustomBlocks(existing)) : withPinnedActions;
8190
8381
  ctx.write(filePath, final);
8191
8382
  return {
@@ -8712,7 +8903,7 @@ function writeIfChanged(ctx, filePath, content, label) {
8712
8903
  };
8713
8904
  }
8714
8905
  async function generateClaudeHooks(ctx) {
8715
- return [writeIfChanged(ctx, HOOK_PATH, "/**\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\";\nimport * as os from \"node:os\";\n\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/util.js\nfunction getEnumValues(entries) {\n const numericValues = Object.values(entries).filter((v) => typeof v === \"number\");\n return Object.entries(entries).filter(([k, _]) => numericValues.indexOf(+k) === -1).map(([_, v]) => v);\n}\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}\n/**\n* Whichever object a def's `shape` currently answers from: the one the caller passed until the first read, the frozen copy after it.\n*\n* Its keys and descriptors read without invoking anything, which is what lets a discriminated union check its discriminator, and the cycle walk read a shape, without resolving a getter that references the schema being constructed. A def that answers `shape` from an accessor of its own has none.\n*/\nfunction rawShape(def) {\n const desc = Object.getOwnPropertyDescriptor(def, \"shape\");\n return desc?.get ? desc.get.raw : desc?.value;\n}\nfunction sourceShape(schema) {\n return rawShape(schema._zod.def) ?? schema._zod.def.shape;\n}\nfunction deferProp(target, key, getter) {\n Object.defineProperty(target, key, {\n get() {\n const value = getter();\n assignProp(this, key, value);\n return value;\n },\n enumerable: true,\n configurable: true\n });\n}\nfunction putProp(target, key, value) {\n if (key in target) assignProp(target, key, value);\n else target[key] = value;\n}\n/**\n* Copies `keys` of `source`'s shape onto `target`, each value passed through `wrap`.\n*\n* A key the source has resolved is copied through now, so the derived shape states it outright and nothing has to resolve it to learn what it holds. A key the source still defers stays deferred, and reads back through the source's own `shape`, so it resolves once and both shapes get that one schema.\n*/\nfunction mirrorShape(target, source, keys, wrap) {\n const raw = sourceShape(source);\n for (const key of keys) {\n const desc = Object.getOwnPropertyDescriptor(raw, key);\n if (!desc.enumerable) continue;\n if (desc.get) deferProp(target, key, () => {\n const value = source._zod.def.shape[key];\n return wrap ? wrap(value, key) : value;\n });\n else putProp(target, key, wrap ? wrap(desc.value, key) : desc.value);\n }\n}\nfunction mirrorProps(target, source) {\n for (const key of Reflect.ownKeys(source)) {\n const desc = Object.getOwnPropertyDescriptor(source, key);\n if (!desc.enumerable) continue;\n if (desc.get) deferProp(target, key, () => source[key]);\n else putProp(target, key, desc.value);\n }\n}\nfunction mergeDefs(...defs) {\n const mergedDescriptors = {};\n for (const def of defs) {\n const descriptors = Object.getOwnPropertyDescriptors(def);\n Object.assign(mergedDescriptors, descriptors);\n }\n return Object.defineProperties({}, mergedDescriptors);\n}\nfunction esc(str) {\n return JSON.stringify(str);\n}\nfunction slugify(input) {\n return input.toLowerCase().trim().replace(/[^\\w\\s-]/g, \"\").replace(/[\\s_-]+/g, \"-\").replace(/^-+|-+$/g, \"\");\n}\nconst captureStackTrace = \"captureStackTrace\" in Error ? Error.captureStackTrace : (..._args) => {};\nfunction isObject(data) {\n return typeof data === \"object\" && data !== null && !Array.isArray(data);\n}\nconst allowsEval = /* @__PURE__*/ cached(() => {\n if (globalConfig.jitless) return false;\n if (typeof navigator !== \"undefined\" && navigator?.userAgent?.includes(\"Cloudflare\")) return false;\n try {\n new Function(\"\");\n return true;\n } catch (_) {\n return false;\n }\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}\nconst propertyKeyTypes = /* @__PURE__*/ new Set([\n \"string\",\n \"number\",\n \"symbol\"\n]);\nfunction escapeRegex(str) {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\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}\nconst NUMBER_FORMAT_RANGES = /*@__PURE__*/ (() => ({\n safeint: [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],\n int32: [-2147483648, 2147483647],\n uint32: [0, 4294967295],\n float32: [-34028234663852886e22, 34028234663852886e22],\n float64: [-Number.MAX_VALUE, Number.MAX_VALUE]\n}))();\nconst BIGINT_FORMAT_RANGES = {\n int64: [/* @__PURE__*/ BigInt(\"-9223372036854775808\"), /* @__PURE__*/ BigInt(\"9223372036854775807\")],\n uint64: [/* @__PURE__*/ BigInt(0), /* @__PURE__*/ BigInt(\"18446744073709551615\")]\n};\nfunction pick(schema, mask) {\n const currDef = schema._zod.def;\n const checks = currDef.checks;\n if (checks && checks.length > 0) throw new Error(\".pick() cannot be used on object schemas containing refinements\");\n const newShape = {};\n mirrorShape(newShape, schema, maskedKeys(schema, mask));\n return clone(schema, mergeDefs(currDef, {\n shape: newShape,\n checks: []\n }));\n}\nfunction maskedKeys(schema, mask) {\n const raw = sourceShape(schema);\n const keys = [];\n for (const key of Reflect.ownKeys(mask)) {\n if (!Object.getOwnPropertyDescriptor(raw, key)?.enumerable) throw new Error(`Unrecognized key: \"${String(key)}\"`);\n if (mask[key]) keys.push(key);\n }\n return keys;\n}\nfunction omit(schema, mask) {\n const currDef = schema._zod.def;\n const checks = currDef.checks;\n if (checks && checks.length > 0) throw new Error(\".omit() cannot be used on object schemas containing refinements\");\n const omitted = new Set(maskedKeys(schema, mask));\n const newShape = {};\n mirrorShape(newShape, schema, Reflect.ownKeys(sourceShape(schema)).filter((key) => !omitted.has(key)));\n return clone(schema, mergeDefs(currDef, {\n shape: newShape,\n checks: []\n }));\n}\nfunction extend(schema, shape) {\n if (!isPlainObject(shape)) throw new Error(\"Invalid input to extend: expected a plain object\");\n const checks = schema._zod.def.checks;\n if (checks && checks.length > 0) {\n const existingShape = sourceShape(schema);\n for (const key of Reflect.ownKeys(shape)) if (Object.getOwnPropertyDescriptor(existingShape, key) !== void 0) throw new Error(\"Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.\");\n }\n return clone(schema, mergeDefs(schema._zod.def, { shape: extended(schema, shape) }));\n}\nfunction extended(schema, shape) {\n const newShape = {};\n mirrorShape(newShape, schema, Reflect.ownKeys(sourceShape(schema)));\n mirrorProps(newShape, shape);\n return newShape;\n}\nfunction safeExtend(schema, shape) {\n if (!isPlainObject(shape)) throw new Error(\"Invalid input to safeExtend: expected a plain object\");\n return clone(schema, mergeDefs(schema._zod.def, { shape: extended(schema, shape) }));\n}\nfunction merge(a, b) {\n if (!b?._zod?.def) throw new Error(\"Invalid input to merge: expected an object schema. To merge a plain shape, use `.extend()`.\");\n if (a._zod.def.checks?.length) throw new Error(\".merge() cannot be used on object schemas containing refinements. Use .safeExtend() instead.\");\n const newShape = {};\n mirrorShape(newShape, a, Reflect.ownKeys(sourceShape(a)));\n mirrorShape(newShape, b, Reflect.ownKeys(sourceShape(b)));\n return clone(a, mergeDefs(a._zod.def, {\n shape: newShape,\n get catchall() {\n return b._zod.def.catchall;\n },\n checks: b._zod.def.checks ?? []\n }));\n}\nfunction partial(Class, schema, mask, name = \"partial\") {\n const checks = schema._zod.def.checks;\n if (checks && checks.length > 0) throw new Error(`.${name}() cannot be used on object schemas containing refinements`);\n const selected = mask ? new Set(maskedKeys(schema, mask)) : void 0;\n const newShape = {};\n mirrorShape(newShape, schema, Reflect.ownKeys(sourceShape(schema)), Class && ((value, key) => selected && !selected.has(key) ? value : new Class({\n type: \"optional\",\n innerType: value\n })));\n return clone(schema, mergeDefs(schema._zod.def, {\n shape: newShape,\n checks: []\n }));\n}\nfunction required(Class, schema, mask) {\n const selected = mask ? new Set(maskedKeys(schema, mask)) : void 0;\n const newShape = {};\n mirrorShape(newShape, schema, Reflect.ownKeys(sourceShape(schema)), (value, key) => selected && !selected.has(key) ? value : new Class({\n type: \"nonoptional\",\n innerType: value\n }));\n return clone(schema, mergeDefs(schema._zod.def, { shape: newShape }));\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 issue(...args) {\n const [iss, input, inst] = args;\n if (typeof iss === \"string\") return {\n message: iss,\n code: \"custom\",\n input,\n inst\n };\n return { ...iss };\n}\n/**\n* Installs a trait's members on its prototype. Each value builds that member for the instance on first read; the built value shadows the accessor as an own property, so a detached `const { parse } = schema` keeps working.\n*\n* Call this from a `proto` initializer, which runs once per prototype — never per instance.\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}\n/** Shadows a prototype member with an own value, so a getter that builds from the instance runs once. */\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}\n/** Like {@link own}, for a member that was never an own data property and has to stay out of `Object.keys`. */\nfunction hide(inst, key, value) {\n return own(inst, key, value, false);\n}\n/** Adds members a table derives from the instance: each builds on first read and shadows as own data, and assignment shadows the same way, as when these were own properties. */\nfunction derived(computes, table) {\n for (const key in computes) {\n const compute = computes[key];\n Object.defineProperty(table, key, {\n configurable: true,\n enumerable: true,\n get() {\n return own(this, key, compute(this));\n },\n set(value) {\n own(this, key, value);\n }\n });\n }\n return table;\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}\n/** Returns the prototype to install on, or `undefined` if this group is already installed on it. */\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};\n/**\n* Installs a lazily-derived internal on the `_zod` prototype of `inst`'s\n* constructor, computed from the internals object itself and cached there on\n* first read. One accessor per constructor rather than one per instance.\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}\n/**\n* Installs `key` on `inst`'s prototype, computed by `make` on first read and cached there as an own\n* data property. One accessor per constructor rather than one per instance, because an own accessor\n* puts every instance after the first into v8 dictionary mode. The key doubles as the sentinel.\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/** Marks the thunk `_catch` synthesises for a constant catch value. `Function.length` cannot tell that thunk from a user callback — rest and defaulted parameters both report arity 0 — and a user callback reads `ctx.error`, whose issues only finalize correctly against the caller's per-parse error map. Provenance can say what arity cannot. A plain string key rather than `Symbol.for`, whose call at module scope no bundler can prove pure — the same shape that anchored `urlCanParse` into every build. */\nconst CONSTANT_CATCH = \"~constantCatch\";\n/** Wraps a constant catch value in a thunk tagged with {@link CONSTANT_CATCH}. */\nfunction constantCatch(value) {\n const fn = () => value;\n fn[CONSTANT_CATCH] = true;\n return fn;\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/core.js\nvar _a$1;\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};\nvar $ZodEncodeError = class extends Error {\n constructor(name) {\n super(`Encountered unidirectional transform during encode: ${name}`);\n this.name = \"ZodEncodeError\";\n }\n};\n(_a$1 = globalThis).__zod_globalConfig ?? (_a$1.__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$1 = (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$1);\nconst $ZodRealError = $constructor(\"$ZodError\", initializer$1, void 0, { Parent: Error });\n/** Get-or-create `obj[key]` as an own data property. A path segment naming an inherited member\n* (\"toString\", \"constructor\") would otherwise read through to the prototype, and assigning\n* \"__proto__\" would hit the setter instead of creating a key. */\nfunction node(obj, key, make) {\n if (!Object.prototype.hasOwnProperty.call(obj, key)) {\n if (key === \"__proto__\") Object.defineProperty(obj, key, {\n value: make(),\n writable: true,\n enumerable: true,\n configurable: true\n });\n else obj[key] = make();\n }\n return obj[key];\n}\nfunction flattenError(error, mapper = (issue) => issue.message) {\n const fieldErrors = {};\n const formErrors = [];\n for (const sub of error.issues) if (sub.path.length > 0) node(fieldErrors, sub.path[0], () => []).push(mapper(sub));\n else formErrors.push(mapper(sub));\n return {\n formErrors,\n fieldErrors\n };\n}\nfunction formatError(error, mapper = (issue) => issue.message) {\n const fieldErrors = { _errors: [] };\n const processError = (error, path = []) => {\n for (const issue of error.issues) if (issue.code === \"invalid_union\" && issue.errors.length) issue.errors.map((issues) => processError({ issues }, [...path, ...issue.path]));\n else if (issue.code === \"invalid_key\") processError({ issues: issue.issues }, [...path, ...issue.path]);\n else if (issue.code === \"invalid_element\") processError({ issues: issue.issues }, [...path, ...issue.path]);\n else {\n const fullpath = [...path, ...issue.path];\n if (fullpath.length === 0) fieldErrors._errors.push(mapper(issue));\n else {\n let curr = fieldErrors;\n let i = 0;\n while (i < fullpath.length) {\n const el = fullpath[i];\n const terminal = i === fullpath.length - 1;\n if (el === \"_errors\") {\n if (terminal) curr._errors.push(mapper(issue));\n i++;\n continue;\n }\n if (!Object.prototype.hasOwnProperty.call(curr, el)) Object.defineProperty(curr, el, {\n value: { _errors: [] },\n enumerable: true,\n writable: true,\n configurable: true\n });\n const node = curr[el];\n if (terminal) node._errors.push(mapper(issue));\n curr = node;\n i++;\n }\n }\n }\n };\n processError(error);\n return fieldErrors;\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/parse.js\nfunction finalizeParams(callee, params) {\n return {\n callee: params?.callee ?? callee,\n Err: params?.Err\n };\n}\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 _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 _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};\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 COMPILE_INVALID = /* @__PURE__ */ Symbol.for(\"zod.compile.invalid\");\nconst COMPILE_FALLBACK = /* @__PURE__ */ Symbol.for(\"zod.compile.fallback\");\nconst validate = ((schema, value, _ctx) => {\n const validator = schema._zod.bag.validator;\n if (validator !== void 0) {\n if (validator(value) !== COMPILE_INVALID) return true;\n if (validator.definite === true && _ctx === void 0) return false;\n }\n return validateFallback(schema, value, _ctx);\n});\nfunction validateFallback(schema, value, _ctx) {\n const ctx = _ctx ? {\n ..._ctx,\n async: false,\n abortEarly: true\n } : {\n async: false,\n abortEarly: true\n };\n const fallbackRun = schema._zod.bag.fallbackRun;\n let result;\n if (fallbackRun) {\n ctx[COMPILE_FALLBACK] = true;\n result = fallbackRun({\n value,\n issues: []\n }, ctx);\n } else result = schema._zod.run({\n value,\n issues: []\n }, ctx);\n if (result instanceof Promise) throw new $ZodAsyncError();\n return result.issues.length === 0;\n}\nconst validateAsync$1 = async (schema, value, _ctx) => {\n const ctx = _ctx ? {\n ..._ctx,\n async: true,\n abortEarly: true\n } : {\n async: true,\n abortEarly: true\n };\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 === 0;\n};\nconst _encode = (_Err) => {\n const parse = _parse(_Err);\n const fn = (schema, value, _ctx, _params) => {\n const ctx = _ctx ? {\n ..._ctx,\n direction: \"backward\"\n } : { direction: \"backward\" };\n return parse(schema, value, ctx, finalizeParams(fn, _params));\n };\n return fn;\n};\nconst _decode = (_Err) => {\n const parse = _parse(_Err);\n const fn = (schema, value, _ctx, _params) => {\n return parse(schema, value, _ctx, finalizeParams(fn, _params));\n };\n return fn;\n};\nconst _encodeAsync = (_Err) => {\n const parseAsync = _parseAsync(_Err);\n const fn = async (schema, value, _ctx, _params) => {\n const ctx = _ctx ? {\n ..._ctx,\n direction: \"backward\"\n } : { direction: \"backward\" };\n return await parseAsync(schema, value, ctx, finalizeParams(fn, _params));\n };\n return fn;\n};\nconst _decodeAsync = (_Err) => {\n const parseAsync = _parseAsync(_Err);\n const fn = async (schema, value, _ctx, _params) => {\n return await parseAsync(schema, value, _ctx, finalizeParams(fn, _params));\n };\n return fn;\n};\nconst _safeEncode = (_Err) => (schema, value, _ctx) => {\n const ctx = _ctx ? {\n ..._ctx,\n direction: \"backward\"\n } : { direction: \"backward\" };\n return _safeParse(_Err)(schema, value, ctx);\n};\nconst _safeDecode = (_Err) => (schema, value, _ctx) => {\n return _safeParse(_Err)(schema, value, _ctx);\n};\nconst _safeEncodeAsync = (_Err) => async (schema, value, _ctx) => {\n const ctx = _ctx ? {\n ..._ctx,\n direction: \"backward\"\n } : { direction: \"backward\" };\n return _safeParseAsync(_Err)(schema, value, ctx);\n};\nconst _safeDecodeAsync = (_Err) => async (schema, value, _ctx) => {\n return _safeParseAsync(_Err)(schema, value, _ctx);\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/regexes.js\n/**\n* @deprecated CUID v1 is deprecated by its authors due to information leakage\n* (timestamps embedded in the id). Use {@link cuid2} instead.\n* See https://github.com/paralleldrive/cuid.\n*/\nconst cuid = /^[cC][0-9a-z]{6,}$/;\nconst cuid2 = /^[0-9a-z]+$/;\nconst ulid = /^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$/;\nconst xid = /^[0-9a-vA-V]{20}$/;\nconst ksuid = /^[A-Za-z0-9]{27}$/;\nconst nanoid = /^[a-zA-Z0-9_-]{21}$/;\nfunction nanoidOfLength(length) {\n return new RegExp(`^[a-zA-Z0-9_-]{${length}}$`);\n}\n/** ISO 8601-1 duration regex. Does not support the 8601-2 extensions like negative durations or fractional/negative components. */\nconst duration = /^P(?:(\\d+W)|(?!.*W)(?=\\d|T\\d)(\\d+Y)?(\\d+M)?(\\d+D)?(T(?=\\d)(\\d+H)?(\\d+M)?(\\d+([.,]\\d+)?S)?)?)$/;\n/** A regex for any UUID-like identifier: 8-4-4-4-12 hex pattern */\nconst guid = /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/;\n/** Returns a regex for validating an RFC 9562/4122 UUID.\n*\n* @param version Optionally specify a version 1-8. If no version is specified, all versions are supported. */\nconst uuid = (version) => {\n if (!version) return /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/;\n return new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${version}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`);\n};\n/** Practical email validation */\nconst email = /^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$/;\nconst _emoji$1 = `^(?=[\\\\s\\\\S]*[\\\\p{Extended_Pictographic}\\\\p{Regional_Indicator}\\\\u20E3])[\\\\p{Extended_Pictographic}\\\\p{Emoji_Component}]+$`;\nfunction emoji() {\n return new RegExp(_emoji$1, \"u\");\n}\nconst ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;\nconst ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;\nconst cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/([0-9]|[1-2][0-9]|3[0-2])$/;\nconst cidrv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/;\nconst base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;\nconst base64url = /^(?:[A-Za-z0-9_-]{4})*(?:[A-Za-z0-9_-]{2,3})?$/;\nconst httpProtocol = /^https?$/;\nconst e164 = /^\\+[1-9]\\d{6,14}$/;\nconst dateSource = `(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))`;\n/** Anchors a pattern source. The interpolation lives here rather than at the call site because\n* esbuild will not drop a `@__PURE__` call whose own argument interpolates a variable, but it\n* will drop `anchor(dateSource)`. Keeping it inline pinned `date` into every bundle. */\nfunction anchor(source) {\n return new RegExp(`^${source}$`);\n}\nconst date = /*@__PURE__*/ anchor(dateSource);\nfunction timeSource(args) {\n const hhmm = `(?:[01]\\\\d|2[0-3]):[0-5]\\\\d`;\n return typeof args.precision === \"number\" ? args.precision === -1 ? `${hhmm}` : args.precision === 0 ? `${hhmm}:[0-5]\\\\d` : `${hhmm}:[0-5]\\\\d\\\\.\\\\d{${args.precision}}` : args.seconds ? `${hhmm}:[0-5]\\\\d(?:\\\\.\\\\d+)?` : `${hhmm}(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?`;\n}\nfunction time(args) {\n return new RegExp(`^${timeSource(args)}$`);\n}\nfunction datetime(args) {\n const opts = [\"Z\"];\n if (args.offset) opts.push(`([+-](?:[01]\\\\d|2[0-3]):[0-5]\\\\d)`);\n const qualified = `${timeSource({\n precision: args.precision,\n seconds: true\n })}(?:${opts.join(\"|\")})`;\n const timeRegex = args.local ? `${qualified}|${timeSource({ precision: args.precision })}` : qualified;\n return new RegExp(`^${dateSource}T(?:${timeRegex})$`);\n}\nconst anyString = /^[\\s\\S]{0,}$/;\nconst integer = /^-?\\d+$/;\nconst number = /^-?\\d+(?:\\.\\d+)?$/;\nconst lowercase = /^[^A-Z]*$/;\nconst uppercase = /^[^a-z]*$/;\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});\n/** Default `when` for length-based checks: run only on non-nullish values with a `length`. */\nconst _whenHasLength = (payload) => {\n const val = payload.value;\n return !nullish(val) && val.length !== void 0;\n};\nconst $ZodCheckMaxLength = /*@__PURE__*/ $constructor(\"$ZodCheckMaxLength\", (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.maximum ? codePointLength(input) : units) <= def.maximum) return;\n const origin = getLengthableOrigin(input);\n payload.issues.push({\n origin,\n code: \"too_big\",\n maximum: def.maximum,\n inclusive: true,\n input,\n inst,\n continue: !def.abort\n });\n };\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});\nconst $ZodCheckLengthEquals = /*@__PURE__*/ $constructor(\"$ZodCheckLengthEquals\", (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 const length = typeof input === \"string\" && units >= def.length && units <= def.length * 2 ? codePointLength(input) : units;\n if (length === def.length) return;\n const origin = getLengthableOrigin(input);\n const tooBig = length > def.length;\n payload.issues.push({\n origin,\n ...tooBig ? {\n code: \"too_big\",\n maximum: def.length\n } : {\n code: \"too_small\",\n minimum: def.length\n },\n inclusive: true,\n exact: true,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckStringFormat = /*@__PURE__*/ $constructor(\"$ZodCheckStringFormat\", (inst, def) => {\n var _a, _b;\n $ZodCheck.init(inst, def);\n if (def.pattern) (_a = inst._zod).check ?? (_a.check = (payload) => {\n def.pattern.lastIndex = 0;\n if (def.pattern.test(payload.value)) return;\n payload.issues.push({\n origin: \"string\",\n code: \"invalid_format\",\n format: def.format,\n input: payload.value,\n ...def.pattern ? { pattern: def.pattern.toString() } : {},\n inst,\n continue: !def.abort\n });\n });\n else (_b = inst._zod).check ?? (_b.check = () => {});\n});\nconst $ZodCheckRegex = /*@__PURE__*/ $constructor(\"$ZodCheckRegex\", (inst, def) => {\n $ZodCheckStringFormat.init(inst, def);\n inst._zod.check = (payload) => {\n def.pattern.lastIndex = 0;\n if (def.pattern.test(payload.value)) return;\n payload.issues.push({\n origin: \"string\",\n code: \"invalid_format\",\n format: \"regex\",\n input: payload.value,\n pattern: def.pattern.toString(),\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckLowerCase = /*@__PURE__*/ $constructor(\"$ZodCheckLowerCase\", (inst, def) => {\n def.pattern ?? (def.pattern = lowercase);\n $ZodCheckStringFormat.init(inst, def);\n});\nconst $ZodCheckUpperCase = /*@__PURE__*/ $constructor(\"$ZodCheckUpperCase\", (inst, def) => {\n def.pattern ?? (def.pattern = uppercase);\n $ZodCheckStringFormat.init(inst, def);\n});\nconst $ZodCheckIncludes = /*@__PURE__*/ $constructor(\"$ZodCheckIncludes\", (inst, def) => {\n $ZodCheck.init(inst, def);\n const escapedRegex = escapeRegex(def.includes);\n def.pattern = new RegExp(typeof def.position === \"number\" ? `^.{${def.position},}${escapedRegex}` : escapedRegex);\n inst._zod.check = (payload) => {\n if (payload.value.includes(def.includes, def.position)) return;\n payload.issues.push({\n origin: \"string\",\n code: \"invalid_format\",\n format: \"includes\",\n includes: def.includes,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckStartsWith = /*@__PURE__*/ $constructor(\"$ZodCheckStartsWith\", (inst, def) => {\n $ZodCheck.init(inst, def);\n const pattern = new RegExp(`^${escapeRegex(def.prefix)}.*`);\n def.pattern ?? (def.pattern = pattern);\n inst._zod.check = (payload) => {\n if (payload.value.startsWith(def.prefix)) return;\n payload.issues.push({\n origin: \"string\",\n code: \"invalid_format\",\n format: \"starts_with\",\n prefix: def.prefix,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckEndsWith = /*@__PURE__*/ $constructor(\"$ZodCheckEndsWith\", (inst, def) => {\n $ZodCheck.init(inst, def);\n const pattern = new RegExp(`.*${escapeRegex(def.suffix)}$`);\n def.pattern ?? (def.pattern = pattern);\n inst._zod.check = (payload) => {\n if (payload.value.endsWith(def.suffix)) return;\n payload.issues.push({\n origin: \"string\",\n code: \"invalid_format\",\n format: \"ends_with\",\n suffix: def.suffix,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckOverwrite = /*@__PURE__*/ $constructor(\"$ZodCheckOverwrite\", (inst, def) => {\n $ZodCheck.init(inst, def);\n inst._zod.check = (payload) => {\n payload.value = def.tx(payload.value);\n };\n});\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/doc.js\nvar Doc = class {\n constructor(args = [], closed = {}) {\n this.content = [];\n this.indent = 0;\n this.args = args;\n this.closed = closed;\n }\n indented(fn) {\n this.indent += 1;\n try {\n fn(this);\n } finally {\n this.indent -= 1;\n }\n }\n write(arg) {\n if (typeof arg === \"function\") {\n arg(this, { execution: \"sync\" });\n arg(this, { execution: \"async\" });\n return;\n }\n const lines = arg.split(\"\\n\").filter((x) => x);\n const minIndent = Math.min(...lines.map((x) => x.length - x.trimStart().length));\n const dedented = lines.map((x) => x.slice(minIndent)).map((x) => \" \".repeat(this.indent * 2) + x);\n for (const line of dedented) this.content.push(line);\n }\n compile() {\n const F = Function;\n const content = this?.content ?? [``];\n return new F(...Object.keys(this.closed), `return function (${this.args.join(\", \")}) {\\n${content.join(\"\\n\")}\\n};`)(...Object.values(this.closed));\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});\n/** The Standard Schema surface for `inst`. Shared so wrappers can extend it without forcing it. */\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 $ZodStringFormat = /*@__PURE__*/ $constructor(\"$ZodStringFormat\", (inst, def) => {\n $ZodCheckStringFormat.init(inst, def);\n $ZodString.init(inst, def);\n});\nconst $ZodGUID = /*@__PURE__*/ $constructor(\"$ZodGUID\", (inst, def) => {\n def.pattern ?? (def.pattern = guid);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodUUID = /*@__PURE__*/ $constructor(\"$ZodUUID\", (inst, def) => {\n if (def.version) {\n const v = {\n v1: 1,\n v2: 2,\n v3: 3,\n v4: 4,\n v5: 5,\n v6: 6,\n v7: 7,\n v8: 8\n }[def.version];\n if (v === void 0) throw new Error(`Invalid UUID version: \"${def.version}\"`);\n def.pattern ?? (def.pattern = uuid(v));\n } else def.pattern ?? (def.pattern = uuid());\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodEmail = /*@__PURE__*/ $constructor(\"$ZodEmail\", (inst, def) => {\n def.pattern ?? (def.pattern = email);\n $ZodStringFormat.init(inst, def);\n});\n/** The `://` guard rejected the input before the URL constructor saw it. */\nconst URL_BAD_FORMAT = 1;\n/** The URL parser rejected the input. */\nconst URL_UNPARSEABLE = 2;\nfunction canParseURL(input) {\n try {\n if (typeof URL !== \"undefined\" && typeof URL.canParse === \"function\") return URL.canParse(input);\n new URL(input);\n return true;\n } catch {\n return false;\n }\n}\nfunction validateURL(trimmed, def) {\n if (!(\"normalize\" in def) && !(\"hostname\" in def) && !(\"protocol\" in def)) return canParseURL(trimmed) || 2;\n return parseURLObject(trimmed, def);\n}\n/** Parses a URL while preserving the non-normalizing HTTP guard. */\nfunction parseURLObject(trimmed, def) {\n if (!def.normalize && def.protocol?.source === httpProtocol.source && !/^https?:\\/\\//i.test(trimmed)) return 1;\n try {\n if (typeof URL !== \"undefined\") {\n const URLStatic = URL;\n if (typeof URLStatic.parse === \"function\") return URLStatic.parse(trimmed) ?? 2;\n }\n return new URL(trimmed);\n } catch {\n return 2;\n }\n}\nconst asciiTabOrNewline = /[\\t\\n\\r]/g;\n/** The URL parser deletes every ASCII tab, LF and CR from its input before it parses, so `new URL(\"https://exa\\nmple.com\")` reports on `example.com`. Applying the same deletion to the returned value closes the half of that divergence which can move the host; the parser's other rewrite, stripping C0 controls at the edges, cannot. */\nfunction stripTabAndNewline(value) {\n return value.replace(asciiTabOrNewline, \"\");\n}\nfunction urlHostnameOk(url, hostname) {\n hostname.lastIndex = 0;\n return hostname.test(url.hostname);\n}\nfunction urlProtocolOk(url, protocol) {\n protocol.lastIndex = 0;\n return protocol.test(url.protocol.endsWith(\":\") ? url.protocol.slice(0, -1) : url.protocol);\n}\nconst $ZodURL = /*@__PURE__*/ $constructor(\"$ZodURL\", (inst, def) => {\n $ZodStringFormat.init(inst, def);\n inst._zod.check = (payload) => {\n try {\n const trimmed = payload.value.trim();\n const url = validateURL(trimmed, def);\n if (url === 1) {\n payload.issues.push({\n code: \"invalid_format\",\n format: \"url\",\n note: \"Invalid URL format\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n return;\n }\n if (url === 2) {\n payload.issues.push({\n code: \"invalid_format\",\n format: \"url\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n return;\n }\n if (url === true) {\n payload.value = stripTabAndNewline(trimmed);\n return;\n }\n if (def.hostname && !urlHostnameOk(url, def.hostname)) payload.issues.push({\n code: \"invalid_format\",\n format: \"url\",\n note: \"Invalid hostname\",\n pattern: def.hostname.source,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n if (def.protocol && !urlProtocolOk(url, def.protocol)) payload.issues.push({\n code: \"invalid_format\",\n format: \"url\",\n note: \"Invalid protocol\",\n pattern: def.protocol.source,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n payload.value = def.normalize ? url.href : stripTabAndNewline(trimmed);\n return;\n } catch (_) {\n payload.issues.push({\n code: \"invalid_format\",\n format: \"url\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n }\n };\n});\nconst $ZodEmoji = /*@__PURE__*/ $constructor(\"$ZodEmoji\", (inst, def) => {\n def.pattern ?? (def.pattern = emoji());\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodNanoID = /*@__PURE__*/ $constructor(\"$ZodNanoID\", (inst, def) => {\n if (def.length !== void 0 && (!Number.isInteger(def.length) || def.length < 1)) throw new Error(`Invalid nanoid length: ${def.length}`);\n def.pattern ?? (def.pattern = def.length === void 0 ? nanoid : nanoidOfLength(def.length));\n $ZodStringFormat.init(inst, def);\n});\n/**\n* @deprecated CUID v1 is deprecated by its authors due to information leakage\n* (timestamps embedded in the id). Use {@link $ZodCUID2} instead.\n* See https://github.com/paralleldrive/cuid.\n*/\nconst $ZodCUID = /*@__PURE__*/ $constructor(\"$ZodCUID\", (inst, def) => {\n def.pattern ?? (def.pattern = cuid);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodCUID2 = /*@__PURE__*/ $constructor(\"$ZodCUID2\", (inst, def) => {\n def.pattern ?? (def.pattern = cuid2);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodULID = /*@__PURE__*/ $constructor(\"$ZodULID\", (inst, def) => {\n def.pattern ?? (def.pattern = ulid);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodXID = /*@__PURE__*/ $constructor(\"$ZodXID\", (inst, def) => {\n def.pattern ?? (def.pattern = xid);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodKSUID = /*@__PURE__*/ $constructor(\"$ZodKSUID\", (inst, def) => {\n def.pattern ?? (def.pattern = ksuid);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodISODateTime = /*@__PURE__*/ $constructor(\"$ZodISODateTime\", (inst, def) => {\n def.pattern ?? (def.pattern = datetime(def));\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodISODate = /*@__PURE__*/ $constructor(\"$ZodISODate\", (inst, def) => {\n def.pattern ?? (def.pattern = date);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodISOTime = /*@__PURE__*/ $constructor(\"$ZodISOTime\", (inst, def) => {\n def.pattern ?? (def.pattern = time(def));\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodISODuration = /*@__PURE__*/ $constructor(\"$ZodISODuration\", (inst, def) => {\n def.pattern ?? (def.pattern = duration);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodIPv4 = /*@__PURE__*/ $constructor(\"$ZodIPv4\", (inst, def) => {\n def.pattern ?? (def.pattern = ipv4);\n $ZodStringFormat.init(inst, def);\n});\n/** An IPv6 address is written with hex digits, colons and dots, and nothing else. The guard is what makes the check below an IPv6 check: `new URL(\"http://[...]\")` parses an authority, not an address, so `@` and `\\` re-delimit it and `\"::@1\\\\\"` validates against the host `0.0.0.1`. The URL parser also deletes ASCII tab, LF and CR rather than failing, which is how `\"::1\\n\"` validated as `::1`. */\nconst ipv6Alphabet = /^[0-9a-fA-F:.]+$/;\nfunction isValidIPv6(value) {\n if (!ipv6Alphabet.test(value)) return false;\n return canParseURL(`http://[${value}]`);\n}\nconst $ZodIPv6 = /*@__PURE__*/ $constructor(\"$ZodIPv6\", (inst, def) => {\n def.pattern ?? (def.pattern = ipv6);\n $ZodStringFormat.init(inst, def);\n inst._zod.check = (payload) => {\n if (!isValidIPv6(payload.value)) payload.issues.push({\n code: \"invalid_format\",\n format: \"ipv6\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCIDRv4 = /*@__PURE__*/ $constructor(\"$ZodCIDRv4\", (inst, def) => {\n def.pattern ?? (def.pattern = cidrv4);\n $ZodStringFormat.init(inst, def);\n});\nfunction isValidCIDRv6(value) {\n const parts = value.split(\"/\");\n if (parts.length !== 2) return false;\n const [address, prefix] = parts;\n if (!prefix) return false;\n const prefixNum = Number(prefix);\n if (`${prefixNum}` !== prefix) return false;\n if (prefixNum < 0 || prefixNum > 128) return false;\n return isValidIPv6(address);\n}\nconst $ZodCIDRv6 = /*@__PURE__*/ $constructor(\"$ZodCIDRv6\", (inst, def) => {\n def.pattern ?? (def.pattern = cidrv6);\n $ZodStringFormat.init(inst, def);\n inst._zod.check = (payload) => {\n if (!isValidCIDRv6(payload.value)) payload.issues.push({\n code: \"invalid_format\",\n format: \"cidrv6\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nfunction isValidBase64(data) {\n if (data === \"\") return true;\n if (/\\s/.test(data)) return false;\n if (data.length % 4 !== 0) return false;\n try {\n atob(data);\n return true;\n } catch {\n return false;\n }\n}\nconst base64Charset = /^[0-9a-zA-Z+/]*={0,2}$/;\nconst $ZodBase64 = /*@__PURE__*/ $constructor(\"$ZodBase64\", (inst, def) => {\n def.pattern ?? (def.pattern = base64Charset);\n $ZodStringFormat.init(inst, def);\n inst._zod.check = (payload) => {\n if (isValidBase64(payload.value)) return;\n payload.issues.push({\n code: \"invalid_format\",\n format: \"base64\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst base64urlCharset = /^[A-Za-z0-9_-]*$/;\nfunction isValidBase64URL(data) {\n if (!base64urlCharset.test(data)) return false;\n const base64 = data.replace(/[-_]/g, (c) => c === \"-\" ? \"+\" : \"/\");\n return isValidBase64(base64.padEnd(Math.ceil(base64.length / 4) * 4, \"=\"));\n}\nconst $ZodBase64URL = /*@__PURE__*/ $constructor(\"$ZodBase64URL\", (inst, def) => {\n def.pattern ?? (def.pattern = base64urlCharset);\n $ZodStringFormat.init(inst, def);\n inst._zod.check = (payload) => {\n if (isValidBase64URL(payload.value)) return;\n payload.issues.push({\n code: \"invalid_format\",\n format: \"base64url\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodE164 = /*@__PURE__*/ $constructor(\"$ZodE164\", (inst, def) => {\n def.pattern ?? (def.pattern = e164);\n $ZodStringFormat.init(inst, def);\n});\nfunction isValidJWT(token, algorithm = null) {\n try {\n const tokensParts = token.split(\".\");\n if (tokensParts.length !== 3) return false;\n const [header] = tokensParts;\n if (!header) return false;\n const parsedHeader = JSON.parse(atob(header));\n if (\"typ\" in parsedHeader && parsedHeader?.typ !== \"JWT\") return false;\n if (!parsedHeader.alg) return false;\n if (algorithm && (!(\"alg\" in parsedHeader) || parsedHeader.alg !== algorithm)) return false;\n return true;\n } catch {\n return false;\n }\n}\nconst $ZodJWT = /*@__PURE__*/ $constructor(\"$ZodJWT\", (inst, def) => {\n $ZodStringFormat.init(inst, def);\n inst._zod.check = (payload) => {\n if (isValidJWT(payload.value, def.alg)) return;\n payload.issues.push({\n code: \"invalid_format\",\n format: \"jwt\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodUnknown = /*@__PURE__*/ $constructor(\"$ZodUnknown\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.parse = (payload) => payload;\n});\nconst $ZodNever = /*@__PURE__*/ $constructor(\"$ZodNever\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.parse = (payload, _ctx) => {\n payload.issues.push({\n expected: \"never\",\n code: \"invalid_type\",\n input: payload.value,\n inst\n });\n return payload;\n };\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 $ZodObjectJIT = /*@__PURE__*/ $constructor(\"$ZodObjectJIT\", (inst, def) => {\n $ZodObject.init(inst, def);\n const superParse = inst._zod.parse;\n const _normalized = cached(() => normalizeDef(def));\n const memo = globalConfig.memoizer;\n const generateFastpass = (shape) => {\n const normalized = _normalized.value;\n const syms = normalized.symbolKeys;\n const doc = new Doc([\"payload\", \"ctx\"], {\n shape,\n inst,\n memo,\n syms\n });\n const parseStr = (k) => `shape[${k}]._zod.run({ value: input[${k}], issues: [] }, ctx)`;\n const prefixStr = (id, k) => `\n let ${id}_ab = false;\n for (let i = 0; i < ${id}.issues.length; i++) {\n const iss = ${id}.issues[i];\n iss.path = iss.path ? [${k}, ...iss.path] : [${k}];\n payload.issues.push(iss);\n if (iss.continue !== true) ${id}_ab = true;\n }\n if (${id}_ab && ctx && ctx.abortEarly) {\n payload.value = newResult;\n return payload;\n }`;\n doc.write(`const input = payload.value;`);\n const ids = Object.create(null);\n let counter = 0;\n for (const key of normalized.allKeys) ids[key] = `key_${counter++}`;\n doc.write(memo ? `const newResult = memo.alloc(inst, payload, {}, ctx);` : `const newResult = {};`);\n for (const key of normalized.allKeys) {\n if (key === \"__proto__\") continue;\n const id = ids[key];\n const k = typeof key === \"symbol\" ? `syms[${syms.indexOf(key)}]` : esc(key);\n const isPresent = `${k} in input`;\n const schema = shape[key];\n const optin = schema?._zod?.optin;\n const isOptionalIn = optin !== void 0;\n const isOptionalOut = schema?._zod?.optout === \"optional\";\n doc.write(`const ${id} = ${parseStr(k)};`);\n if (isOptionalIn && isOptionalOut) {\n const assign = optin === \"optional\" ? `${id}_present` : `${id}.value !== undefined || ${id}_present`;\n doc.write(`\n const ${id}_present = ${isPresent};\n if (!${id}.issues.length || ${id}_present) {\n if (${id}.issues.length) {${prefixStr(id, k)}\n }\n\n if (${assign}) {\n newResult[${k}] = ${id}.value;\n }\n }\n\n `);\n } else if (!isOptionalIn) doc.write(`\n const ${id}_present = ${isPresent};\n if (${id}.issues.length) {${prefixStr(id, k)}\n }\n if (!${id}_present && !${id}.issues.length) {\n payload.issues.push({\n code: \"invalid_type\",\n expected: \"nonoptional\",\n input: undefined,\n path: [${k}]\n });\n if (ctx && ctx.abortEarly) {\n payload.value = newResult;\n return payload;\n }\n }\n\n if (${id}_present) {\n newResult[${k}] = ${id}.value;\n }\n\n `);\n else {\n doc.write(`\n if (${id}.issues.length) {${prefixStr(id, k)}\n }\n `);\n if (optin === \"defaulted\") doc.write(`newResult[${k}] = ${id}.value;`);\n else doc.write(`\n if (${id}.value !== undefined || ${isPresent}) {\n newResult[${k}] = ${id}.value;\n }\n `);\n }\n }\n doc.write(`payload.value = newResult;`);\n doc.write(`return payload;`);\n return doc.compile();\n };\n let fastpass;\n const isObject$1 = isObject;\n const jit = !globalConfig.jitless;\n const allowsEval$1 = allowsEval;\n const fastEnabled = jit && allowsEval$1.value;\n const catchall = def.catchall;\n let value;\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 if (jit && fastEnabled && ctx?.async === false && ctx.jitless !== true) {\n if (!fastpass) fastpass = generateFastpass(def.shape);\n payload = fastpass(payload, ctx);\n if (!catchall) return payload;\n return handleCatchall([], input, payload, ctx, value, inst, ctx?.abortEarly === true);\n }\n return superParse(payload, ctx);\n };\n});\nfunction handleUnionResults(results, final, inst, ctx) {\n for (const result of results) if (result.issues.length === 0) {\n final.value = result.value;\n return final;\n }\n const nonaborted = results.filter((r) => !aborted(r));\n if (nonaborted.length === 1) {\n final.value = nonaborted[0].value;\n return nonaborted[0];\n }\n final.issues.push({\n code: \"invalid_union\",\n input: final.value,\n inst,\n errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx, config())))\n });\n return final;\n}\nconst $ZodUnion = /*@__PURE__*/ $constructor(\"$ZodUnion\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazyInternal(inst, \"optin\", (zod) => zod.def.options.some((o) => o._zod.optin === \"defaulted\") ? \"defaulted\" : zod.def.options.some((o) => o._zod.optin !== void 0) ? \"optional\" : void 0);\n defineLazyInternal(inst, \"optout\", (zod) => zod.def.options.some((o) => o._zod.optout === \"optional\") ? \"optional\" : void 0);\n defineLazyInternal(inst, \"values\", (zod) => {\n if (zod.def.options.every((o) => o._zod.values)) return new Set(zod.def.options.flatMap((option) => Array.from(option._zod.values)));\n });\n defineLazyInternal(inst, \"pattern\", (zod) => {\n if (zod.def.options.every((o) => o._zod.pattern)) {\n const patterns = zod.def.options.map((o) => o._zod.pattern);\n return new RegExp(`^(${patterns.map((p) => cleanRegex(p.source)).join(\"|\")})$`);\n }\n });\n const first = def.options.length === 1 ? def.options[0]._zod.run : null;\n inst._zod.parse = (payload, ctx) => {\n if (first) return first(payload, ctx);\n let async = false;\n const results = [];\n for (const option of def.options) {\n const result = option._zod.run({\n value: payload.value,\n issues: []\n }, ctx);\n if (result instanceof Promise) {\n results.push(result);\n async = true;\n } else {\n if (result.issues.length === 0) return result;\n results.push(result);\n }\n }\n if (!async) return handleUnionResults(results, payload, inst, ctx);\n return Promise.all(results).then((results) => {\n return handleUnionResults(results, payload, inst, ctx);\n });\n };\n});\nconst $ZodIntersection = /*@__PURE__*/ $constructor(\"$ZodIntersection\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.parse = (payload, ctx) => {\n const input = payload.value;\n const left = def.left._zod.run({\n value: input,\n issues: []\n }, ctx);\n const right = def.right._zod.run({\n value: input,\n issues: []\n }, ctx);\n if (left instanceof Promise || right instanceof Promise) return Promise.all([left, right]).then(([left, right]) => {\n return handleIntersectionResults(payload, left, right);\n });\n return handleIntersectionResults(payload, left, right);\n };\n});\nfunction mergeValues(a, b) {\n if (a === b) return {\n valid: true,\n data: a\n };\n if (a instanceof Date && b instanceof Date && +a === +b) return {\n valid: true,\n data: a\n };\n if (isPlainObject(a) && isPlainObject(b)) {\n const bKeys = Object.keys(b);\n const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);\n const newObj = {\n ...a,\n ...b\n };\n if (Object.prototype.hasOwnProperty.call(newObj, \"__proto__\")) delete newObj.__proto__;\n for (const key of sharedKeys) {\n if (key === \"__proto__\") continue;\n const sharedValue = mergeValues(a[key], b[key]);\n if (!sharedValue.valid) return {\n valid: false,\n mergeErrorPath: [key, ...sharedValue.mergeErrorPath]\n };\n newObj[key] = sharedValue.data;\n }\n return {\n valid: true,\n data: newObj\n };\n }\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return {\n valid: false,\n mergeErrorPath: []\n };\n const newArray = [];\n for (let index = 0; index < a.length; index++) {\n const itemA = a[index];\n const itemB = b[index];\n const sharedValue = mergeValues(itemA, itemB);\n if (!sharedValue.valid) return {\n valid: false,\n mergeErrorPath: [index, ...sharedValue.mergeErrorPath]\n };\n newArray.push(sharedValue.data);\n }\n return {\n valid: true,\n data: newArray\n };\n }\n return {\n valid: false,\n mergeErrorPath: []\n };\n}\nfunction handleIntersectionResults(result, left, right) {\n const unrecKeys = /* @__PURE__ */ new Map();\n let unrecIssue;\n const keyIssues = /* @__PURE__ */ new Map();\n const collect = (iss, side) => {\n let keys;\n if (iss.code === \"unrecognized_keys\" && !iss.path?.length) {\n unrecIssue ?? (unrecIssue = iss);\n keys = iss.keys;\n } else if (iss.code === \"invalid_key\" && iss.origin === \"record\" && iss.path?.length === 1) {\n const k = String(iss.path[0]);\n if (!keyIssues.has(k)) keyIssues.set(k, iss);\n keys = [k];\n } else return false;\n for (const k of keys) {\n if (!unrecKeys.has(k)) unrecKeys.set(k, {});\n unrecKeys.get(k)[side] = true;\n }\n return true;\n };\n for (const iss of left.issues) if (!collect(iss, \"l\")) result.issues.push(iss);\n for (const iss of right.issues) if (!collect(iss, \"r\")) result.issues.push(iss);\n const bothKeys = [...unrecKeys].filter(([, f]) => f.l && f.r).map(([k]) => k);\n if (bothKeys.length) {\n const aggregated = unrecIssue ? bothKeys.filter((k) => unrecIssue.keys.includes(k)) : [];\n if (aggregated.length) result.issues.push({\n ...unrecIssue,\n keys: aggregated\n });\n for (const k of bothKeys) if (!aggregated.includes(k) && keyIssues.has(k)) result.issues.push(keyIssues.get(k));\n }\n const merged = mergeValues(left.value, right.value);\n if (!merged.valid) {\n if (aborted(result)) return result;\n throw new Error(`Unmergable intersection. Error path: ${JSON.stringify(merged.mergeErrorPath)}`);\n }\n result.value = merged.data;\n return result;\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});\nconst $ZodEnum = /*@__PURE__*/ $constructor(\"$ZodEnum\", (inst, def) => {\n $ZodType.init(inst, def);\n const values = getEnumValues(def.entries);\n const valuesSet = new Set(values);\n inst._zod.values = valuesSet;\n defineLazyInternal(inst, \"pattern\", (zod) => {\n const patternValues = getEnumValues(zod.def.entries).filter((k) => propertyKeyTypes.has(typeof k));\n return new RegExp(patternValues.length ? `^(${patternValues.map((o) => escapeRegex(o.toString())).join(\"|\")})$` : \"^[^\\\\s\\\\S]$\");\n });\n inst._zod.parse = (payload, _ctx) => {\n const input = payload.value;\n if (valuesSet.has(input)) return payload;\n payload.issues.push({\n code: \"invalid_value\",\n values,\n input,\n inst\n });\n return payload;\n };\n});\nconst $ZodTransform = /*@__PURE__*/ $constructor(\"$ZodTransform\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.optin = \"optional\";\n globalConfig.memoizer?.guard(inst);\n inst._zod.parse = (payload, ctx) => {\n if (ctx.direction === \"backward\") throw new $ZodEncodeError(inst.constructor.name);\n const _out = def.transform(payload.value, payload);\n if (ctx.async) return (_out instanceof Promise ? _out : Promise.resolve(_out)).then((output) => {\n payload.value = output;\n return payload;\n });\n if (_out instanceof Promise) throw new $ZodAsyncError();\n payload.value = _out;\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 $ZodExactOptional = /*@__PURE__*/ $constructor(\"$ZodExactOptional\", (inst, def) => {\n $ZodOptional.init(inst, def);\n defineLazyInternal(inst, \"values\", (zod) => zod.def.innerType._zod.values);\n defineLazyInternal(inst, \"pattern\", (zod) => zod.def.innerType._zod.pattern);\n inst._zod.parse = (payload, ctx) => {\n return def.innerType._zod.run(payload, ctx);\n };\n});\nconst $ZodNullable = /*@__PURE__*/ $constructor(\"$ZodNullable\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazyInternal(inst, \"optin\", (zod) => zod.def.innerType._zod.optin);\n defineLazyInternal(inst, \"optout\", (zod) => zod.def.innerType._zod.optout);\n defineLazyInternal(inst, \"pattern\", (zod) => {\n const pattern = zod.def.innerType._zod.pattern;\n return pattern ? new RegExp(`^(${cleanRegex(pattern.source)}|null)$`) : void 0;\n });\n defineLazyInternal(inst, \"values\", (zod) => {\n return zod.def.innerType._zod.values ? /* @__PURE__ */ new Set([...zod.def.innerType._zod.values, null]) : void 0;\n });\n inst._zod.parse = (payload, ctx) => {\n if (payload.value === null) return payload;\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 /**\n * $ZodDefault returns the default value immediately in forward direction.\n * It doesn't pass the default value into the validator (\"prefault\"). There's no reason to pass the default value through validation. The validity of the default is enforced by TypeScript statically. Otherwise, it's the responsibility of the user to ensure the default is valid. In the case of pipes with divergent in/out types, you can specify the default on the `in` schema of your ZodPipe to set a \"prefault\" for the pipe. */\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}\nconst $ZodPrefault = /*@__PURE__*/ $constructor(\"$ZodPrefault\", (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) payload.value = def.defaultValue;\n return def.innerType._zod.run(payload, ctx);\n };\n});\nconst $ZodNonOptional = /*@__PURE__*/ $constructor(\"$ZodNonOptional\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazyInternal(inst, \"values\", (zod) => {\n const v = zod.def.innerType._zod.values;\n return v ? new Set([...v].filter((x) => x !== void 0)) : void 0;\n });\n inst._zod.parse = (payload, ctx) => {\n const result = def.innerType._zod.run(payload, ctx);\n if (result instanceof Promise) return result.then((result) => handleNonOptionalResult(result, inst));\n return handleNonOptionalResult(result, inst);\n };\n});\nfunction handleNonOptionalResult(payload, inst) {\n if (!payload.issues.length && payload.value === void 0) payload.issues.push({\n code: \"invalid_type\",\n expected: \"nonoptional\",\n input: payload.value,\n inst\n });\n return payload;\n}\nfunction handleCatchResult(payload, result, def, ctx) {\n if (!result.issues.length) {\n payload.value = result.value;\n if (result.memo) payload.memo = true;\n return payload;\n }\n payload.value = def.catchValue({\n ...result,\n value: payload.value,\n error: { issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config())) },\n input: payload.value\n });\n return payload;\n}\nconst $ZodCatch = /*@__PURE__*/ $constructor(\"$ZodCatch\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazyInternal(inst, \"optin\", (zod) => zod.def.innerType._zod.optin === \"defaulted\" ? \"defaulted\" : \"optional\");\n defineLazyInternal(inst, \"optout\", (zod) => zod.def.innerType._zod.optout);\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 const result = def.innerType._zod.run({\n value: payload.value,\n issues: []\n }, ctx);\n if (result instanceof Promise) return result.then((result) => handleCatchResult(payload, result, def, ctx));\n return handleCatchResult(payload, result, def, ctx);\n };\n});\nconst $ZodPipe = /*@__PURE__*/ $constructor(\"$ZodPipe\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazyInternal(inst, \"values\", (zod) => zod.def.in._zod.values);\n defineLazyInternal(inst, \"optin\", (zod) => zod.def.in._zod.optin);\n defineLazyInternal(inst, \"optout\", (zod) => zod.def.out._zod.optout);\n defineLazyInternal(inst, \"propValues\", (zod) => zod.def.in._zod.propValues);\n inst._zod.parse = (payload, ctx) => {\n if (ctx.direction === \"backward\") {\n const right = def.out._zod.run(payload, ctx);\n if (right instanceof Promise) return right.then((right) => handlePipeResult(right, def.in, ctx));\n return handlePipeResult(right, def.in, ctx);\n }\n const left = def.in._zod.run(payload, ctx);\n if (left instanceof Promise) return left.then((left) => handlePipeResult(left, def.out, ctx));\n return handlePipeResult(left, def.out, ctx);\n };\n});\nfunction handlePipeResult(left, next, ctx) {\n if (left.issues.some((iss) => iss.code !== \"unrecognized_keys\")) {\n left.aborted = true;\n return left;\n }\n return next._zod.run({\n value: left.value,\n issues: left.issues\n }, ctx);\n}\nconst $ZodReadonly = /*@__PURE__*/ $constructor(\"$ZodReadonly\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazyInternal(inst, \"propValues\", (zod) => zod.def.innerType._zod.propValues);\n defineLazyInternal(inst, \"values\", (zod) => zod.def.innerType._zod.values);\n defineLazyInternal(inst, \"optin\", (zod) => zod.def.innerType?._zod?.optin);\n defineLazyInternal(inst, \"optout\", (zod) => zod.def.innerType?._zod?.optout);\n inst._zod.parse = (payload, ctx) => {\n if (ctx.direction === \"backward\") return def.innerType._zod.run(payload, ctx);\n const result = def.innerType._zod.run(payload, ctx);\n if (result instanceof Promise) return result.then(handleReadonlyResult);\n return handleReadonlyResult(result);\n };\n});\nfunction handleReadonlyResult(payload) {\n if (!payload.memo) payload.value = Object.freeze(payload.value);\n return payload;\n}\nconst $ZodCustom = /*@__PURE__*/ $constructor(\"$ZodCustom\", (inst, def) => {\n $ZodCheck.init(inst, def);\n $ZodType.init(inst, def);\n inst._zod.parse = (payload, _) => {\n return payload;\n };\n inst._zod.check = (payload) => {\n const input = payload.value;\n const r = def.fn(input);\n if (r instanceof Promise) return r.then((r) => handleRefineResult(r, payload, input, inst));\n handleRefineResult(r, payload, input, inst);\n };\n});\nfunction handleRefineResult(result, payload, input, inst) {\n if (!result) {\n const _iss = {\n code: \"custom\",\n input,\n inst,\n path: [...inst._zod.def.path ?? []],\n continue: !inst._zod.def.abort\n };\n if (inst._zod.def.params) _iss.params = inst._zod.def.params;\n payload.issues.push(issue(_iss));\n }\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/memoizer.js\nvar $ZodCyclicError = class extends Error {\n constructor() {\n super(`Cannot parse a reference cycle that closes through a transform`);\n this.name = \"ZodCyclicError\";\n }\n};\n/** Keyed off the context object every schema in one parse call already shares. */\nconst STATE = \"~memo\";\nconst NO_ISSUES = [];\nfunction isRef(value) {\n return value !== null && typeof value === \"object\";\n}\nfunction cloneIssues(issues) {\n return issues.map((iss) => iss.path ? {\n ...iss,\n path: iss.path.slice()\n } : { ...iss });\n}\nconst recursive = /*@__PURE__*/ new WeakMap();\n/** What the walk established, in order of certainty: ordered so the strongest answer among children wins. */\nconst NONE = 0;\nconst ASSUMED = 1;\nconst PROVEN = 2;\n/** Whether this schema's subtree contains a cycle, so one parse can re-enter it. */\nfunction isRecursive(inst, stack, resolve) {\n const cached = recursive.get(inst);\n if (cached !== void 0) return cached ? PROVEN : NONE;\n if (stack.has(inst)) return PROVEN;\n stack.add(inst);\n let result = NONE;\n const check = (child) => {\n if (result !== PROVEN && child?._zod) {\n const answer = isRecursive(child, stack, resolve);\n if (answer > result) result = answer;\n }\n };\n const shape = (sh, spread) => {\n let answer = NONE;\n for (const key of Reflect.ownKeys(sh)) {\n const desc = Object.getOwnPropertyDescriptor(sh, key);\n if (spread && !desc.enumerable) continue;\n const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve) : NONE;\n if (child > answer) answer = child;\n }\n return answer;\n };\n const merge = (answer) => {\n if (answer > result) result = answer;\n };\n const def = inst._zod.def;\n switch (def.type) {\n case \"object\": {\n const raw = rawShape(def);\n merge(raw ? shape(raw, true) : ASSUMED);\n check(def.catchall);\n break;\n }\n case \"array\":\n check(def.element);\n break;\n case \"tuple\":\n for (const el of def.items) check(el);\n check(def.rest);\n break;\n case \"record\":\n case \"map\":\n check(def.keyType);\n check(def.valueType);\n break;\n case \"set\":\n check(def.valueType);\n break;\n case \"union\":\n for (const el of def.options) check(el);\n break;\n case \"intersection\":\n check(def.left);\n check(def.right);\n break;\n case \"optional\":\n case \"nullable\":\n case \"default\":\n case \"prefault\":\n case \"catch\":\n case \"readonly\":\n case \"nonoptional\":\n case \"promise\":\n case \"success\":\n check(def.innerType);\n break;\n case \"pipe\":\n check(def.in);\n check(def.out);\n break;\n case \"function\":\n check(def.input);\n check(def.output);\n break;\n case \"lazy\": {\n const inner = def._cachedInner ?? (resolve ? inst._zod.innerType : void 0);\n merge(inner ? isRecursive(inner, stack, false) : ASSUMED);\n break;\n }\n case \"template_literal\":\n case \"string\":\n case \"number\":\n case \"int\":\n case \"boolean\":\n case \"bigint\":\n case \"symbol\":\n case \"undefined\":\n case \"null\":\n case \"void\":\n case \"never\":\n case \"any\":\n case \"unknown\":\n case \"date\":\n case \"nan\":\n case \"enum\":\n case \"literal\":\n case \"file\":\n case \"transform\":\n case \"custom\": break;\n default: for (const key in def) {\n const desc = Object.getOwnPropertyDescriptor(def, key);\n if (!desc || desc.get) continue;\n const value = desc.value;\n if (!value || typeof value !== \"object\") continue;\n if (value._zod) check(value);\n else if (Array.isArray(value)) for (const el of value) check(el);\n }\n }\n stack.delete(inst);\n return settle(inst, result);\n}\n/** An assumed answer must not outlive the resolution that settles it, so only a certain one is cached. */\nfunction settle(inst, answer) {\n if (answer !== ASSUMED) recursive.set(inst, answer === PROVEN);\n return answer;\n}\nfunction bucketFor(state, inst) {\n let bucket = state.buckets.get(inst);\n if (!bucket) {\n bucket = /* @__PURE__ */ new WeakMap();\n state.buckets.set(inst, bucket);\n }\n return bucket;\n}\nlet handoff;\nconst open = [];\nconst memo = {\n alloc(_inst, payload, empty) {\n const bucket = handoff;\n if (!bucket) return empty;\n handoff = void 0;\n const entry = {\n value: empty,\n issues: null\n };\n bucket.set(payload.value, entry);\n open.push(entry);\n return empty;\n },\n guard(inst) {\n var _a;\n (_a = inst._zod).deferred ?? (_a.deferred = []);\n inst._zod.deferred.push(() => {\n const base = inst._zod.parse;\n const wrapped = (payload, ctx) => {\n if (ctx.direction !== \"backward\" && isBackEdge(ctx, payload.value)) throw new $ZodCyclicError();\n return base(payload, ctx);\n };\n inst._zod.parse = wrapped;\n if (inst._zod.run === base) inst._zod.run = wrapped;\n });\n },\n attach(inst) {\n var _a;\n let isRecursiveInst;\n let rechecked = false;\n let lastCtx;\n let lastBucket;\n (_a = inst._zod).deferred ?? (_a.deferred = []);\n inst._zod.deferred.push(() => {\n const base = inst._zod.parse;\n const wrapped = (payload, ctx) => {\n if (isRecursiveInst === void 0) {\n const walked = isRecursive(inst, /* @__PURE__ */ new Set(), false);\n if (walked === NONE) {\n inst._zod.parse = base;\n if (inst._zod.run === wrapped) inst._zod.run = base;\n return base(payload, ctx);\n }\n if (walked === PROVEN || rechecked) isRecursiveInst = true;\n else rechecked = true;\n }\n const input = payload.value;\n if (!isRef(input)) return base(payload, ctx);\n let state = ctx[STATE];\n if (!state) {\n state = {\n buckets: /* @__PURE__ */ new WeakMap(),\n backEdges: void 0\n };\n ctx[STATE] = state;\n }\n let bucket;\n if (lastCtx === ctx) bucket = lastBucket;\n else {\n bucket = bucketFor(state, inst);\n lastCtx = ctx;\n lastBucket = bucket;\n }\n const hit = bucket.get(input);\n if (hit) {\n payload.value = hit.value;\n if (hit.issues) {\n if (hit.issues.length) payload.issues.push(...cloneIssues(hit.issues));\n } else {\n payload.memo = true;\n state.backEdges ?? (state.backEdges = /* @__PURE__ */ new WeakSet());\n state.backEdges.add(hit.value);\n }\n return payload;\n }\n handoff = bucket;\n const depth = open.length;\n const result = base(payload, ctx);\n handoff = void 0;\n const entry = open.length > depth ? open.pop() : void 0;\n if (result instanceof Promise) return result.then((r) => {\n if (entry) entry.issues = r.issues.length ? cloneIssues(r.issues) : NO_ISSUES;\n return r;\n });\n if (entry) entry.issues = result.issues.length ? cloneIssues(result.issues) : NO_ISSUES;\n return result;\n };\n inst._zod.parse = wrapped;\n if (inst._zod.run === base) inst._zod.run = wrapped;\n });\n }\n};\n/** The memoizer that gives containers cycle support. `zod` installs it by default; `zod/mini` opts in with `config({ memoizer: memoizer() })`. */\nfunction memoizer() {\n return memo;\n}\n/** Whether this value is a node a back-edge resolved to before it finished. */\nfunction isBackEdge(ctx, value) {\n const backEdges = ctx[STATE]?.backEdges;\n return backEdges !== void 0 && isRef(value) && backEdges.has(value);\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/registries.js\nvar _a;\nvar $ZodRegistry = class {\n constructor() {\n this._map = /* @__PURE__ */ new WeakMap();\n this._idmap = /* @__PURE__ */ new Map();\n }\n add(schema, ..._meta) {\n const meta = _meta[0];\n this._map.set(schema, meta);\n if (meta && typeof meta === \"object\" && \"id\" in meta) this._idmap.set(meta.id, schema);\n return this;\n }\n clear() {\n this._map = /* @__PURE__ */ new WeakMap();\n this._idmap = /* @__PURE__ */ new Map();\n return this;\n }\n remove(schema) {\n const meta = this._map.get(schema);\n if (meta && typeof meta === \"object\" && \"id\" in meta) this._idmap.delete(meta.id);\n this._map.delete(schema);\n return this;\n }\n get(schema) {\n const p = schema._zod.parent;\n if (p) {\n const pm = { ...this.get(p) ?? {} };\n delete pm.id;\n const f = {\n ...pm,\n ...this._map.get(schema)\n };\n return Object.keys(f).length ? f : void 0;\n }\n return this._map.get(schema);\n }\n has(schema) {\n return this._map.has(schema);\n }\n};\nfunction registry() {\n return new $ZodRegistry();\n}\n(_a = globalThis).__zod_globalRegistry ?? (_a.__zod_globalRegistry = registry());\nconst globalRegistry = globalThis.__zod_globalRegistry;\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 _email(Class, params) {\n return new Class({\n type: \"string\",\n format: \"email\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _guid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"guid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _uuid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"uuid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _uuidv4(Class, params) {\n return new Class({\n type: \"string\",\n format: \"uuid\",\n check: \"string_format\",\n abort: false,\n version: \"v4\",\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _uuidv6(Class, params) {\n return new Class({\n type: \"string\",\n format: \"uuid\",\n check: \"string_format\",\n abort: false,\n version: \"v6\",\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _uuidv7(Class, params) {\n return new Class({\n type: \"string\",\n format: \"uuid\",\n check: \"string_format\",\n abort: false,\n version: \"v7\",\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _url(Class, params) {\n return new Class({\n type: \"string\",\n format: \"url\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _emoji(Class, params) {\n return new Class({\n type: \"string\",\n format: \"emoji\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _nanoid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"nanoid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/**\n* @deprecated CUID v1 is deprecated by its authors due to information leakage\n* (timestamps embedded in the id). Use {@link _cuid2} instead.\n* See https://github.com/paralleldrive/cuid.\n*/\n// @__NO_SIDE_EFFECTS__\nfunction _cuid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"cuid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _cuid2(Class, params) {\n return new Class({\n type: \"string\",\n format: \"cuid2\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _ulid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"ulid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _xid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"xid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _ksuid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"ksuid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _ipv4(Class, params) {\n return new Class({\n type: \"string\",\n format: \"ipv4\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _ipv6(Class, params) {\n return new Class({\n type: \"string\",\n format: \"ipv6\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _cidrv4(Class, params) {\n return new Class({\n type: \"string\",\n format: \"cidrv4\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _cidrv6(Class, params) {\n return new Class({\n type: \"string\",\n format: \"cidrv6\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _base64(Class, params) {\n return new Class({\n type: \"string\",\n format: \"base64\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _base64url(Class, params) {\n return new Class({\n type: \"string\",\n format: \"base64url\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _e164(Class, params) {\n return new Class({\n type: \"string\",\n format: \"e164\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _jwt(Class, params) {\n return new Class({\n type: \"string\",\n format: \"jwt\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _isoDateTime(Class, params) {\n return new Class({\n type: \"string\",\n format: \"datetime\",\n check: \"string_format\",\n offset: false,\n local: false,\n precision: null,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _isoDate(Class, params) {\n return new Class({\n type: \"string\",\n format: \"date\",\n check: \"string_format\",\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _isoTime(Class, params) {\n return new Class({\n type: \"string\",\n format: \"time\",\n check: \"string_format\",\n precision: null,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _isoDuration(Class, params) {\n return new Class({\n type: \"string\",\n format: \"duration\",\n check: \"string_format\",\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _unknown(Class) {\n return new Class({ type: \"unknown\" });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _never(Class, params) {\n return new Class({\n type: \"never\",\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _maxLength(maximum, params) {\n return new $ZodCheckMaxLength({\n check: \"max_length\",\n ...normalizeParams(params),\n maximum\n });\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// @__NO_SIDE_EFFECTS__\nfunction _length(length, params) {\n return new $ZodCheckLengthEquals({\n check: \"length_equals\",\n ...normalizeParams(params),\n length\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _regex(pattern, params) {\n return new $ZodCheckRegex({\n check: \"string_format\",\n format: \"regex\",\n ...normalizeParams(params),\n pattern\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _lowercase(params) {\n return new $ZodCheckLowerCase({\n check: \"string_format\",\n format: \"lowercase\",\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _uppercase(params) {\n return new $ZodCheckUpperCase({\n check: \"string_format\",\n format: \"uppercase\",\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _includes(includes, params) {\n return new $ZodCheckIncludes({\n check: \"string_format\",\n format: \"includes\",\n ...normalizeParams(params),\n includes\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _startsWith(prefix, params) {\n return new $ZodCheckStartsWith({\n check: \"string_format\",\n format: \"starts_with\",\n ...normalizeParams(params),\n prefix\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _endsWith(suffix, params) {\n return new $ZodCheckEndsWith({\n check: \"string_format\",\n format: \"ends_with\",\n ...normalizeParams(params),\n suffix\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _overwrite(tx) {\n return new $ZodCheckOverwrite({\n check: \"overwrite\",\n tx\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _normalize(form) {\n return /* @__PURE__ */ _overwrite((input) => input.normalize(form));\n}\n// @__NO_SIDE_EFFECTS__\nfunction _trim() {\n return /* @__PURE__ */ _overwrite((input) => input.trim());\n}\n// @__NO_SIDE_EFFECTS__\nfunction _toLowerCase() {\n return /* @__PURE__ */ _overwrite((input) => input.toLowerCase());\n}\n// @__NO_SIDE_EFFECTS__\nfunction _toUpperCase() {\n return /* @__PURE__ */ _overwrite((input) => input.toUpperCase());\n}\n// @__NO_SIDE_EFFECTS__\nfunction _slugify() {\n return /* @__PURE__ */ _overwrite((input) => slugify(input));\n}\n// @__NO_SIDE_EFFECTS__\nfunction _array(Class, element, params) {\n return new Class({\n type: \"array\",\n element,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _refine(Class, fn, _params) {\n return new Class({\n type: \"custom\",\n check: \"custom\",\n fn,\n ...normalizeParams(_params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _superRefine(fn, params) {\n const ch = /* @__PURE__ */ _check((payload) => {\n payload.addIssue = (issue$2) => {\n if (typeof issue$2 === \"string\") payload.issues.push(issue(issue$2, payload.value, ch._zod.def));\n else {\n const _issue = issue$2;\n if (_issue.fatal) _issue.continue = false;\n _issue.code ?? (_issue.code = \"custom\");\n if (!(\"input\" in _issue)) _issue.input = payload.value;\n _issue.inst ?? (_issue.inst = ch);\n _issue.continue ?? (_issue.continue = !ch._zod.def.abort);\n payload.issues.push(issue(_issue));\n }\n };\n return fn(payload.value, payload);\n }, params);\n return ch;\n}\n// @__NO_SIDE_EFFECTS__\nfunction _check(fn, params) {\n const ch = new $ZodCheck({\n check: \"custom\",\n ...normalizeParams(params)\n });\n ch._zod.check = fn;\n return ch;\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/to-json-schema.js\nfunction assignProps(target, ...sources) {\n for (const source of sources) for (const key of Reflect.ownKeys(source)) if (Object.prototype.propertyIsEnumerable.call(source, key)) assignProp(target, key, source[key]);\n return target;\n}\nfunction initializeContext(params) {\n let target = params?.target ?? \"draft-2020-12\";\n if (target === \"draft-4\") target = \"draft-04\";\n if (target === \"draft-7\") target = \"draft-07\";\n return {\n processors: params.processors ?? {},\n metadataRegistry: params?.metadata ?? globalRegistry,\n target,\n unrepresentable: params?.unrepresentable ?? \"throw\",\n override: params?.override ?? (() => {}),\n io: params?.io ?? \"output\",\n counter: 0,\n seen: /* @__PURE__ */ new Map(),\n sharedDefsExtractedFor: void 0,\n sharedEmitDoneFor: void 0,\n cycles: params?.cycles ?? \"ref\",\n reused: params?.reused ?? \"inline\",\n intersections: [],\n deferred: [],\n external: params?.external ?? void 0\n };\n}\n/**\n* Applies the `unrepresentable` setting at a site that has no JSON Schema equivalent. Throws\n* `message` unless the setting (or the handler's return value) says otherwise. Returns `true` if a\n* custom JSON Schema was written into `json`, in which case the caller must not write its own.\n*/\nfunction handleUnrepresentable(schema, ctx, json, params, message) {\n const result = typeof ctx.unrepresentable === \"function\" ? ctx.unrepresentable({\n zodSchema: schema,\n path: params.path,\n message\n }) : ctx.unrepresentable;\n if (result === \"any\") return false;\n if (result === void 0 || result === \"throw\") throw new Error(message);\n Object.assign(json, result);\n return true;\n}\nfunction processSchema(schema, ctx, _params = {\n path: [],\n schemaPath: []\n}) {\n var _a;\n const def = schema._zod.def;\n const seen = ctx.seen.get(schema);\n if (seen) {\n seen.count++;\n if (_params.schemaPath.includes(schema)) seen.cycle = _params.path;\n return seen.schema;\n }\n const result = {\n schema: {},\n count: 1,\n cycle: void 0,\n path: _params.path\n };\n ctx.seen.set(schema, result);\n ctx.sharedDefsExtractedFor = void 0;\n ctx.sharedEmitDoneFor = void 0;\n const overrideSchema = schema._zod.toJSONSchema?.();\n if (overrideSchema) result.schema = overrideSchema;\n else {\n const params = {\n ..._params,\n schemaPath: [..._params.schemaPath, schema],\n path: _params.path\n };\n if (schema._zod.processJSONSchema) schema._zod.processJSONSchema(ctx, result.schema, params);\n else {\n const _json = result.schema;\n const processor = ctx.processors[def.type];\n if (!processor) throw new Error(`[toJSONSchema]: Non-representable type encountered: ${def.type}`);\n processor(schema, ctx, _json, params);\n }\n const parent = schema._zod.parent;\n if (parent) {\n if (!result.ref) result.ref = parent;\n processSchema(parent, ctx, params);\n ctx.seen.get(parent).isParent = true;\n }\n }\n const meta = ctx.metadataRegistry.get(schema);\n if (meta) assignProps(result.schema, meta);\n if (ctx.io === \"input\" && isTransforming(schema)) {\n delete result.schema.examples;\n delete result.schema.default;\n }\n if (ctx.io === \"input\" && \"_prefault\" in result.schema) (_a = result.schema).default ?? (_a.default = result.schema._prefault);\n delete result.schema._prefault;\n return ctx.seen.get(schema).schema;\n}\nfunction encodeJSONPointerSegment(segment) {\n return segment.replace(/~/g, \"~0\").replace(/\\//g, \"~1\");\n}\nfunction extractDefs(ctx, schema) {\n const root = ctx.seen.get(schema);\n if (!root) throw new Error(\"Unprocessed schema. This is a bug in Zod.\");\n if (ctx.external && ctx.sharedDefsExtractedFor === ctx.external) return;\n const idToSchema = /* @__PURE__ */ new Map();\n for (const entry of ctx.seen.entries()) {\n const id = ctx.metadataRegistry.get(entry[0])?.id;\n if (id) {\n const existing = idToSchema.get(id);\n if (existing && existing !== entry[0]) throw new Error(`Duplicate schema id \"${id}\" detected during JSON Schema conversion. Two different schemas cannot share the same id when converted together.`);\n idToSchema.set(id, entry[0]);\n }\n }\n const makeURI = (entry) => {\n const defsSegment = ctx.target === \"draft-2020-12\" ? \"$defs\" : \"definitions\";\n if (ctx.external) {\n const externalId = ctx.external.registry.get(entry[0])?.id;\n const uriGenerator = ctx.external.uri ?? ((id) => id);\n if (externalId) return { ref: uriGenerator(externalId) };\n const id = entry[1].defId ?? entry[1].schema.id ?? `schema${ctx.counter++}`;\n entry[1].defId = id;\n return {\n defId: id,\n ref: `${uriGenerator(\"__shared\")}#/${defsSegment}/${encodeJSONPointerSegment(id)}`\n };\n }\n const uriPrefix = `#`;\n const defUriPrefix = `${uriPrefix}/${defsSegment}/`;\n if (entry[1] === root && !entry[1].schema.id) return { ref: uriPrefix };\n const defId = entry[1].schema.id ?? `__schema${ctx.counter++}`;\n return {\n defId,\n ref: defUriPrefix + encodeJSONPointerSegment(defId)\n };\n };\n const extractToDef = (entry) => {\n if (entry[1].schema.$ref) return;\n const seen = entry[1];\n const { ref, defId } = makeURI(entry);\n seen.def = { ...seen.schema };\n if (defId) seen.defId = defId;\n const schema = seen.schema;\n for (const key in schema) delete schema[key];\n schema.$ref = ref;\n };\n if (ctx.cycles === \"throw\") for (const entry of ctx.seen.entries()) {\n const seen = entry[1];\n if (seen.cycle) throw new Error(`Cycle detected: #/${seen.cycle?.join(\"/\")}/<root>\n\nSet the \\`cycles\\` parameter to \\`\"ref\"\\` to resolve cyclical schemas with defs.`);\n }\n for (const entry of ctx.seen.entries()) {\n const seen = entry[1];\n if (schema === entry[0]) {\n extractToDef(entry);\n continue;\n }\n if (ctx.external) {\n const ext = ctx.external.registry.get(entry[0])?.id;\n if (schema !== entry[0] && ext) {\n extractToDef(entry);\n continue;\n }\n }\n if (ctx.metadataRegistry.get(entry[0])?.id) {\n extractToDef(entry);\n continue;\n }\n if (seen.cycle) {\n extractToDef(entry);\n continue;\n }\n if (seen.count > 1) {\n if (ctx.reused === \"ref\") extractToDef(entry);\n }\n }\n if (ctx.external) ctx.sharedDefsExtractedFor = ctx.external;\n}\n/** Rewrites `anyOf: [{type: \"a\"}, {type: \"b\"}]` to `type: [\"a\", \"b\"]`, which every JSON Schema draft treats as equivalent and most consumers render far better for the nullable case. Only branches that are a bare type assertion qualify — anything carrying a constraint, `$ref`, `const` or metadata is left alone. Runs after `flattenRef`, so a branch an override decorated or `$defs` extraction turned into a `$ref` is no longer bare and correctly stays in `anyOf`. `oneOf` is excluded: `integer` and `number` overlap, so \"exactly one\" and \"at least one\" are not the same there. OpenAPI 3.0 is excluded: its `type` must be a single string. */\nfunction compactTypeUnion(schema) {\n const options = schema.anyOf;\n if (!Array.isArray(options) || options.length === 0 || schema.type !== void 0) return;\n const types = [];\n for (const option of options) {\n if (!option || typeof option !== \"object\") return;\n compactTypeUnion(option);\n const keys = Object.keys(option);\n if (keys.length !== 1 || keys[0] !== \"type\") return;\n const type = option.type;\n for (const member of Array.isArray(type) ? type : [type]) {\n if (typeof member !== \"string\") return;\n if (!types.includes(member)) types.push(member);\n }\n }\n delete schema.anyOf;\n schema.type = types.length === 1 ? types[0] : types;\n}\n/** Keywords `foldIntersection` knows how to combine. Anything else — `$ref`, `patternProperties`,\n* an annotation like `description` — makes a member unfoldable, so a constraint this does not\n* understand leaves the `allOf` alone instead of being silently dropped or misattributed. */\nconst FOLDABLE_KEYS = /* @__PURE__ */ new Set([\n \"type\",\n \"properties\",\n \"required\",\n \"additionalProperties\"\n]);\nconst UNION_KEYS = [\"oneOf\", \"anyOf\"];\n/** A member's constraint on a key it does not declare itself. A `catchall` states one; `false`, an absent `additionalProperties`, and the empty schema a loose object emits state nothing. */\nfunction undeclaredConstraint(member) {\n const extra = member.additionalProperties;\n if (extra === void 0 || extra === false || typeof extra !== \"object\" || extra === null) return null;\n return Object.keys(extra).length ? extra : null;\n}\n/** Combines object members into the single object they describe together, or returns `null` if any of them carries a keyword outside {@link FOLDABLE_KEYS}. */\nfunction foldObjects(members) {\n const objects = [];\n for (const member of members) {\n if (typeof member !== \"object\" || member.type !== \"object\") return null;\n for (const key in member) if (!FOLDABLE_KEYS.has(key)) return null;\n objects.push(member);\n }\n const properties = {};\n const required = /* @__PURE__ */ new Set();\n for (const object of objects) {\n for (const key in object.properties) {\n if (Object.prototype.hasOwnProperty.call(properties, key)) continue;\n const parts = [];\n for (const other of objects) {\n const part = other.properties?.[key] ?? undeclaredConstraint(other);\n if (part === null || part === void 0) continue;\n if (!parts.some((seen) => JSON.stringify(seen) === JSON.stringify(part))) parts.push(part);\n }\n const merged = parts.length === 1 ? parts[0] : foldObjects(parts) ?? { allOf: parts };\n assignProp(properties, key, merged);\n }\n for (const key of object.required ?? []) required.add(key);\n }\n const folded = {\n type: \"object\",\n properties\n };\n if (required.size) folded.required = [...required];\n if (objects.every((object) => object.additionalProperties === false)) folded.additionalProperties = false;\n else {\n const constraints = [];\n for (const object of objects) {\n const constraint = undeclaredConstraint(object);\n if (constraint && !constraints.some((seen) => JSON.stringify(seen) === JSON.stringify(constraint))) constraints.push(constraint);\n }\n if (constraints.length === 1) folded.additionalProperties = constraints[0];\n else if (constraints.length > 1) folded.additionalProperties = { allOf: constraints };\n }\n return folded;\n}\n/** `additionalProperties` in an `allOf` member sees only that member's own `properties`, so two\n* closed object members reject each other's keys and the schema validates nothing. Zod's parser\n* pools the key sets instead — `handleIntersectionResults` reports a key as unrecognized only when\n* *every* side rejects it — so the emitted schema has to pool them too, and folding the members\n* into one object is the encoding that says so on every target.\n*\n* This runs from `finalize`, after `extractDefs`, which is what keeps it clear of the `$ref`\n* machinery: a member extracted into `$defs` is already a `$ref` by now and declines to fold, so it\n* keeps its reference and its own closedness rather than being inlined as a stale copy. */\nfunction foldIntersection(json) {\n const allOf = json.allOf;\n if (!Array.isArray(allOf) || allOf.length < 2) return;\n for (const key of FOLDABLE_KEYS) if (key in json) return;\n const unions = allOf.filter((m) => UNION_KEYS.some((k) => Array.isArray(m[k])));\n let folded = null;\n if (!unions.length) folded = foldObjects(allOf);\n else {\n const union = unions[0];\n const keyword = UNION_KEYS.find((k) => Array.isArray(union[k]));\n if (Object.keys(union).length !== 1) return;\n const rest = allOf.filter((m) => m !== union);\n const branches = union[keyword].map((branch) => foldObjects([...rest, branch]));\n if (branches.some((b) => !b)) return;\n folded = { [keyword]: branches };\n }\n if (!folded) return;\n delete json.allOf;\n assignProps(json, folded);\n}\nfunction finalize(ctx, schema) {\n const root = ctx.seen.get(schema);\n if (!root) throw new Error(\"Unprocessed schema. This is a bug in Zod.\");\n const flattenRef = (zodSchema) => {\n const seen = ctx.seen.get(zodSchema);\n if (seen.ref === null) return;\n const schema = seen.def ?? seen.schema;\n const _cached = { ...schema };\n const ref = seen.ref;\n seen.ref = null;\n if (ref) {\n flattenRef(ref);\n const refSeen = ctx.seen.get(ref);\n const refSchema = refSeen.schema;\n if (refSchema.$ref && (ctx.target === \"draft-07\" || ctx.target === \"draft-04\" || ctx.target === \"openapi-3.0\")) {\n schema.allOf = schema.allOf ?? [];\n schema.allOf.push(refSchema);\n } else assignProps(schema, refSchema);\n assignProps(schema, _cached);\n if (zodSchema._zod.parent === ref) for (const key in schema) {\n if (key === \"$ref\" || key === \"allOf\") continue;\n if (!(key in _cached)) delete schema[key];\n }\n if (refSchema.$ref && refSeen.def) for (const key in schema) {\n if (key === \"$ref\" || key === \"allOf\") continue;\n if (key in refSeen.def && JSON.stringify(schema[key]) === JSON.stringify(refSeen.def[key])) delete schema[key];\n }\n }\n const parent = zodSchema._zod.parent;\n if (parent && parent !== ref) {\n flattenRef(parent);\n const parentSeen = ctx.seen.get(parent);\n if (parentSeen?.schema.$ref) {\n schema.$ref = parentSeen.schema.$ref;\n if (parentSeen.def) for (const key in schema) {\n if (key === \"$ref\" || key === \"allOf\") continue;\n if (key in parentSeen.def && JSON.stringify(schema[key]) === JSON.stringify(parentSeen.def[key])) delete schema[key];\n }\n }\n }\n ctx.override({\n zodSchema,\n jsonSchema: schema,\n path: seen.path ?? []\n });\n };\n if (!ctx.external || ctx.sharedEmitDoneFor !== ctx.external) {\n for (const entry of [...ctx.seen.entries()].reverse()) flattenRef(entry[0]);\n if (ctx.target !== \"openapi-3.0\") for (const entry of ctx.seen.entries()) compactTypeUnion(entry[1].def ?? entry[1].schema);\n for (const rewrite of ctx.deferred) rewrite();\n if (ctx.intersections.length) {\n const carriers = /* @__PURE__ */ new Map();\n for (const seen of ctx.seen.values()) for (const json of [seen.schema, seen.def]) {\n const allOf = json?.allOf;\n if (!Array.isArray(allOf)) continue;\n const existing = carriers.get(allOf);\n if (existing) existing.push(json);\n else carriers.set(allOf, [json]);\n }\n for (const allOf of ctx.intersections) for (const json of carriers.get(allOf) ?? []) foldIntersection(json);\n }\n }\n const result = {};\n if (ctx.target === \"draft-2020-12\") result.$schema = \"https://json-schema.org/draft/2020-12/schema\";\n else if (ctx.target === \"draft-07\") result.$schema = \"http://json-schema.org/draft-07/schema#\";\n else if (ctx.target === \"draft-04\") result.$schema = \"http://json-schema.org/draft-04/schema#\";\n else if (ctx.target === \"openapi-3.0\") {}\n if (ctx.external?.uri) {\n const id = ctx.external.registry.get(schema)?.id;\n if (!id) throw new Error(\"Schema is missing an `id` property\");\n result.$id = ctx.external.uri(id);\n }\n assignProps(result, root.defId ? root.schema : root.def ?? root.schema);\n const rootMetaId = ctx.metadataRegistry.get(schema)?.id;\n if (rootMetaId !== void 0 && result.id === rootMetaId) delete result.id;\n const defs = ctx.external?.defs ?? {};\n if (!ctx.external || ctx.sharedEmitDoneFor !== ctx.external) for (const entry of ctx.seen.entries()) {\n const seen = entry[1];\n if (seen.def && seen.defId) {\n if (seen.def.id === seen.defId) delete seen.def.id;\n assignProp(defs, seen.defId, seen.def);\n }\n }\n if (ctx.external) ctx.sharedEmitDoneFor = ctx.external;\n if (ctx.external) {} else if (Object.keys(defs).length > 0) {\n if (ctx.target === \"draft-2020-12\") result.$defs = defs;\n else result.definitions = defs;\n }\n try {\n const finalized = JSON.parse(JSON.stringify(result));\n Object.defineProperty(finalized, \"~standard\", {\n value: {\n ...schema[\"~standard\"],\n jsonSchema: {\n input: createStandardJSONSchemaMethod(schema, \"input\", ctx.processors),\n output: createStandardJSONSchemaMethod(schema, \"output\", ctx.processors)\n }\n },\n enumerable: false,\n writable: false\n });\n return finalized;\n } catch (_err) {\n throw new Error(\"Error converting schema to JSON.\");\n }\n}\nfunction isTransforming(_schema, _ctx) {\n const ctx = _ctx ?? { seen: /* @__PURE__ */ new Set() };\n if (ctx.seen.has(_schema)) return false;\n ctx.seen.add(_schema);\n const def = _schema._zod.def;\n if (def.type === \"transform\") return true;\n if (def.type === \"array\") return isTransforming(def.element, ctx);\n if (def.type === \"set\") return isTransforming(def.valueType, ctx);\n if (def.type === \"lazy\") return isTransforming(def.getter(), ctx);\n if (def.type === \"promise\" || def.type === \"optional\" || def.type === \"nonoptional\" || def.type === \"nullable\" || def.type === \"readonly\" || def.type === \"default\" || def.type === \"prefault\" || def.type === \"catch\") return isTransforming(def.innerType, ctx);\n if (def.type === \"intersection\") return isTransforming(def.left, ctx) || isTransforming(def.right, ctx);\n if (def.type === \"record\" || def.type === \"map\") return isTransforming(def.keyType, ctx) || isTransforming(def.valueType, ctx);\n if (def.type === \"pipe\") {\n if (_schema._zod.traits.has(\"$ZodCodec\")) return true;\n return isTransforming(def.in, ctx) || isTransforming(def.out, ctx);\n }\n if (def.type === \"object\") {\n for (const key in def.shape) if (isTransforming(def.shape[key], ctx)) return true;\n return false;\n }\n if (def.type === \"union\") {\n for (const option of def.options) if (isTransforming(option, ctx)) return true;\n return false;\n }\n if (def.type === \"tuple\") {\n for (const item of def.items) if (isTransforming(item, ctx)) return true;\n if (def.rest && isTransforming(def.rest, ctx)) return true;\n return false;\n }\n return false;\n}\n/**\n* Creates a toJSONSchema method for a schema instance.\n* This encapsulates the logic of initializing context, processing, extracting defs, and finalizing.\n*/\nconst createToJSONSchemaMethod = (schema, processors = {}) => (params) => {\n const ctx = initializeContext({\n ...params,\n processors\n });\n processSchema(schema, ctx);\n extractDefs(ctx, schema);\n return finalize(ctx, schema);\n};\nconst createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) => {\n const { libraryOptions, target } = params ?? {};\n const ctx = initializeContext({\n ...libraryOptions ?? {},\n target,\n io,\n processors\n });\n processSchema(schema, ctx);\n extractDefs(ctx, schema);\n return finalize(ctx, schema);\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/json-schema-processors.js\nconst narrowMin = (agg, key, value) => {\n if (agg[key] === void 0 || value > agg[key]) agg[key] = value;\n};\nconst narrowMax = (agg, key, value) => {\n if (agg[key] === void 0 || value < agg[key]) agg[key] = value;\n};\nconst narrowBoth = (agg, value) => {\n narrowMin(agg, \"minimum\", value);\n narrowMax(agg, \"maximum\", value);\n};\nconst addDivisor = (agg, value) => {\n agg.multipleOf ?? (agg.multipleOf = []);\n if (!agg.multipleOf.includes(value)) agg.multipleOf.push(value);\n};\nconst addPattern = (agg, pattern) => {\n agg.patterns ?? (agg.patterns = /* @__PURE__ */ new Set());\n agg.patterns.add(pattern);\n};\nconst intersectMime = (agg, mime) => {\n agg.mime = agg.mime ? agg.mime.filter((m) => mime.includes(m)) : [...mime];\n};\nconst setFormat = (agg, format) => {\n agg.format = format;\n if (format.includes(\"int\")) agg.isInt = true;\n};\nconst minContributor = (agg, def) => narrowMin(agg, \"minimum\", def.minimum);\nconst maxContributor = (agg, def) => narrowMax(agg, \"maximum\", def.maximum);\nconst formatContributor = (ranges) => (agg, def) => {\n setFormat(agg, def.format);\n const [minimum, maximum] = ranges[def.format];\n narrowMin(agg, \"minimum\", minimum);\n narrowMax(agg, \"maximum\", maximum);\n};\nconst contributors = {\n greater_than: (agg, def) => narrowMin(agg, def.inclusive ? \"minimum\" : \"exclusiveMinimum\", def.value),\n less_than: (agg, def) => narrowMax(agg, def.inclusive ? \"maximum\" : \"exclusiveMaximum\", def.value),\n multiple_of: (agg, def) => addDivisor(agg, def.value),\n number_format: formatContributor(NUMBER_FORMAT_RANGES),\n bigint_format: formatContributor(BIGINT_FORMAT_RANGES),\n min_length: minContributor,\n max_length: maxContributor,\n length_equals: (agg, def) => narrowBoth(agg, def.length),\n min_size: minContributor,\n max_size: maxContributor,\n size_equals: (agg, def) => narrowBoth(agg, def.size),\n string_format: (agg, def) => {\n setFormat(agg, def.format);\n if (def.pattern) addPattern(agg, def.pattern);\n if (def.format === \"base64\" || def.format === \"base64url\") agg.contentEncoding = def.format;\n if (def.local || def.precision === -1) agg.laxFormat = true;\n },\n mime_type: (agg, def) => intersectMime(agg, def.mime)\n};\nfunction aggregateChecks(schema) {\n const agg = {};\n const def = schema._zod.def;\n const list = schema._zod.traits.has(\"$ZodCheck\") ? [schema, ...def.checks ?? []] : def.checks ?? [];\n for (const ch of list) contributors[ch._zod.def.check]?.(agg, ch._zod.def);\n const bag = schema._zod.bag;\n if (bag.minimum !== void 0) narrowMin(agg, \"minimum\", bag.minimum);\n if (bag.exclusiveMinimum !== void 0) narrowMin(agg, \"exclusiveMinimum\", bag.exclusiveMinimum);\n if (bag.maximum !== void 0) narrowMax(agg, \"maximum\", bag.maximum);\n if (bag.exclusiveMaximum !== void 0) narrowMax(agg, \"exclusiveMaximum\", bag.exclusiveMaximum);\n if (bag.multipleOf !== void 0) addDivisor(agg, bag.multipleOf);\n if (bag.format !== void 0) {\n agg.format ?? (agg.format = bag.format);\n if (bag.format.includes(\"int\")) agg.isInt = true;\n }\n if (bag.mime) intersectMime(agg, bag.mime);\n for (const pattern of bag.patterns ?? []) addPattern(agg, pattern);\n return agg;\n}\nconst formatMap = {\n guid: \"uuid\",\n url: \"uri\",\n datetime: \"date-time\",\n json_string: \"json-string\",\n regex: \"\"\n};\nconst exactPatterns = /* @__PURE__ */ new Map([[base64Charset, base64], [base64urlCharset, base64url]]);\nconst exactPattern = (p) => exactPatterns.get(p) ?? p;\nconst stringProcessor = (schema, ctx, _json, _params) => {\n const json = _json;\n json.type = \"string\";\n const { minimum, maximum, format, patterns, contentEncoding, laxFormat } = aggregateChecks(schema);\n if (typeof minimum === \"number\") json.minLength = minimum;\n if (typeof maximum === \"number\") json.maxLength = maximum;\n if (format) {\n json.format = formatMap[format] ?? format;\n if (json.format === \"\") delete json.format;\n if (format === \"time\" || laxFormat) delete json.format;\n }\n if (contentEncoding) json.contentEncoding = contentEncoding;\n if (patterns && patterns.size > 0) {\n const patternList = [...patterns].map(exactPattern);\n if (patternList.length === 1) json.pattern = patternList[0].source;\n else if (patternList.length > 1) json.allOf = [...patternList.map((regex) => ({\n ...ctx.target === \"draft-07\" || ctx.target === \"draft-04\" || ctx.target === \"openapi-3.0\" ? { type: \"string\" } : {},\n pattern: regex.source\n }))];\n }\n};\nconst neverProcessor = (_schema, _ctx, json, _params) => {\n json.not = {};\n};\nconst unknownProcessor = (_schema, _ctx, _json, _params) => {};\nconst enumProcessor = (schema, _ctx, json, _params) => {\n const def = schema._zod.def;\n const values = getEnumValues(def.entries);\n if (values.length === 0) {\n json.not = {};\n return;\n }\n if (values.every((v) => typeof v === \"number\")) json.type = \"number\";\n if (values.every((v) => typeof v === \"string\")) json.type = \"string\";\n json.enum = values;\n};\nconst customProcessor = (schema, ctx, json, params) => {\n handleUnrepresentable(schema, ctx, json, params, \"Custom types cannot be represented in JSON Schema\");\n};\nconst transformProcessor = (schema, ctx, json, params) => {\n handleUnrepresentable(schema, ctx, json, params, \"Transforms cannot be represented in JSON Schema\");\n};\nconst arrayProcessor = (schema, ctx, _json, params) => {\n const json = _json;\n const def = schema._zod.def;\n const { minimum, maximum } = aggregateChecks(schema);\n if (typeof minimum === \"number\") json.minItems = minimum;\n if (typeof maximum === \"number\") json.maxItems = maximum;\n json.type = \"array\";\n json.items = processSchema(def.element, ctx, {\n ...params,\n path: [...params.path, \"items\"]\n });\n};\nfunction inputOptin(schema) {\n const def = schema._zod.def;\n if (def.type === \"pipe\" && def.in._zod.traits.has(\"$ZodTransform\")) return inputOptin(def.out);\n if (def.type === \"catch\") return inputOptin(def.innerType);\n return schema._zod.optin;\n}\nconst objectProcessor = (schema, ctx, _json, params) => {\n const json = _json;\n const def = schema._zod.def;\n const shape = def.shape;\n if (Object.getOwnPropertySymbols(shape).length && handleUnrepresentable(schema, ctx, json, params, \"Symbol keys cannot be represented in JSON Schema\")) return;\n json.type = \"object\";\n json.properties = {};\n for (const key in shape) assignProp(json.properties, key, processSchema(shape[key], ctx, {\n ...params,\n path: [\n ...params.path,\n \"properties\",\n key\n ]\n }));\n const requiredKeys = [];\n for (const key of Object.keys(shape)) {\n const field = def.shape[key];\n if (ctx.io === \"input\" ? inputOptin(field) === void 0 : field._zod.optout === void 0) requiredKeys.push(key);\n }\n if (requiredKeys.length > 0) json.required = requiredKeys;\n if (def.catchall?._zod.def.type === \"never\") json.additionalProperties = false;\n else if (!def.catchall) {\n if (ctx.io === \"output\") json.additionalProperties = false;\n } else if (def.catchall) json.additionalProperties = processSchema(def.catchall, ctx, {\n ...params,\n path: [...params.path, \"additionalProperties\"]\n });\n};\nconst unionProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n const isExclusive = def.inclusive === false;\n const options = def.options.map((x, i) => processSchema(x, ctx, {\n ...params,\n path: [\n ...params.path,\n isExclusive ? \"oneOf\" : \"anyOf\",\n i\n ]\n }));\n if (isExclusive) json.oneOf = options;\n else json.anyOf = options;\n};\nconst intersectionProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n const a = processSchema(def.left, ctx, {\n ...params,\n path: [\n ...params.path,\n \"allOf\",\n 0\n ]\n });\n const b = processSchema(def.right, ctx, {\n ...params,\n path: [\n ...params.path,\n \"allOf\",\n 1\n ]\n });\n const isSimpleIntersection = (val) => \"allOf\" in val && Object.keys(val).length === 1;\n const allOf = [...isSimpleIntersection(a) ? a.allOf : [a], ...isSimpleIntersection(b) ? b.allOf : [b]];\n json.allOf = allOf;\n ctx.intersections.push(allOf);\n};\n/** JSON object keys are always strings, so a numeric record key schema is re-expressed over the\n* numeric-string form the record parser matches. Deferred to `finalize`, after the flatten: a key\n* behind a wrapper only carries its own `type` before then, and a union key only has its branches.\n*\n* A numeric bound cannot apply to a property name, so `minimum` and its siblings are dropped rather\n* than carried over: keeping them beside `type: \"string\"` reproduces the match-nothing schema this\n* exists to fix. A key that carries one therefore emits wider than the record parses — `z.record(z.number().min(5), V)`\n* accepts `\"3\"` — which is the deliberate trade, since throwing on it would reject an ordinary schema\n* outright. */\nfunction stringifyKeyNames(bySchema, json, visited) {\n if (json.$ref) {\n if (visited.has(json)) return json;\n visited.add(json);\n const def = bySchema.get(json)?.def;\n if (!def) return json;\n const inlined = stringifyKeyNames(bySchema, def, visited);\n return inlined === def ? json : inlined;\n }\n for (const keyword of [\"anyOf\", \"oneOf\"]) {\n const branches = json[keyword];\n if (!Array.isArray(branches)) continue;\n const mapped = branches.map((branch) => stringifyKeyNames(bySchema, branch, visited));\n if (mapped.some((branch, i) => branch !== branches[i])) json = {\n ...json,\n [keyword]: mapped\n };\n }\n const types = Array.isArray(json.type) ? json.type : [json.type];\n const numericType = !types.includes(\"string\") && types.some((t) => t === \"number\" || t === \"integer\");\n const values = json.enum ?? (json.const !== void 0 ? [json.const] : void 0);\n if (!numericType && !values?.some((v) => typeof v === \"number\")) return json;\n const { minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, format, id, ...rest } = json;\n if (rest.enum) rest.enum = rest.enum.map((v) => typeof v === \"number\" ? String(v) : v);\n else if (typeof rest.const === \"number\") rest.const = String(rest.const);\n if (!numericType) return rest;\n rest.type = \"string\";\n if (!values) rest.pattern = (types.includes(\"number\") ? number : integer).source;\n return rest;\n}\n/** Every record of one conversion, so the carriers are found in a single pass rather than once per record. */\nconst pendingRecords = /* @__PURE__ */ new WeakMap();\nfunction rewriteKeyNames(ctx) {\n const bySchema = /* @__PURE__ */ new Map();\n for (const entry of ctx.seen.values()) if (entry.def && !bySchema.has(entry.schema)) bySchema.set(entry.schema, entry);\n const rewrites = /* @__PURE__ */ new Map();\n for (const record of pendingRecords.get(ctx) ?? []) {\n const seen = ctx.seen.get(record);\n const names = (seen?.def ?? seen?.schema)?.propertyNames;\n if (!names || names === true || rewrites.has(names)) continue;\n const rewritten = stringifyKeyNames(bySchema, names, /* @__PURE__ */ new Set());\n if (rewritten !== names) rewrites.set(names, rewritten);\n }\n if (!rewrites.size) return;\n for (const entry of ctx.seen.values()) for (const carrier of [entry.schema, entry.def]) {\n const rewritten = carrier && rewrites.get(carrier.propertyNames);\n if (rewritten) carrier.propertyNames = rewritten;\n }\n}\nconst recordProcessor = (schema, ctx, _json, params) => {\n const json = _json;\n const def = schema._zod.def;\n json.type = \"object\";\n const keyType = def.keyType;\n const patterns = aggregateChecks(keyType).patterns;\n if (def.mode === \"loose\" && patterns && patterns.size > 0) {\n const valueSchema = processSchema(def.valueType, ctx, {\n ...params,\n path: [\n ...params.path,\n \"patternProperties\",\n \"*\"\n ]\n });\n json.patternProperties = {};\n for (const pattern of patterns) assignProp(json.patternProperties, exactPattern(pattern).source, valueSchema);\n } else {\n if (ctx.target === \"draft-07\" || ctx.target === \"draft-2020-12\") {\n json.propertyNames = processSchema(def.keyType, ctx, {\n ...params,\n path: [...params.path, \"propertyNames\"]\n });\n let pending = pendingRecords.get(ctx);\n if (!pending) {\n pending = [];\n pendingRecords.set(ctx, pending);\n ctx.deferred.push(() => rewriteKeyNames(ctx));\n }\n pending.push(schema);\n }\n json.additionalProperties = processSchema(def.valueType, ctx, {\n ...params,\n path: [...params.path, \"additionalProperties\"]\n });\n }\n const keyValues = keyType._zod.values;\n const omittableOnInput = ctx.io === \"input\" && inputOptin(def.valueType) !== void 0;\n if (keyValues && !def.partial && !omittableOnInput) {\n const validKeyValues = [...keyValues].filter((v) => typeof v === \"string\" || typeof v === \"number\");\n if (validKeyValues.length > 0) json.required = validKeyValues.map(String);\n }\n};\nconst nullableProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n const inner = processSchema(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n if (ctx.target === \"openapi-3.0\") {\n seen.ref = def.innerType;\n json.nullable = true;\n } else json.anyOf = [inner, { type: \"null\" }];\n};\nconst nonoptionalProcessor = (schema, ctx, _json, params) => {\n const def = schema._zod.def;\n processSchema(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n};\n/** Round-trips a default value through JSON so the emitted schema is guaranteed to be valid JSON.\n* A BigInt has no reliable encoding, so it goes through `unrepresentable` like any other\n* unrepresentable value. Returns a sentinel when the caller must not write a default of its own. */\nconst UNREPRESENTABLE_DEFAULT = Symbol();\nfunction serializeDefaultValue(value, schema, ctx, json, params) {\n let unrepresentable = false;\n const serialized = JSON.stringify(value, (_, val) => {\n if (typeof val !== \"bigint\") return val;\n unrepresentable = true;\n return null;\n });\n if (!unrepresentable) return JSON.parse(serialized);\n handleUnrepresentable(schema, ctx, json, params, \"BigInt defaults cannot be represented in JSON Schema\");\n return UNREPRESENTABLE_DEFAULT;\n}\nconst defaultProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n processSchema(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n const value = serializeDefaultValue(def.defaultValue, schema, ctx, json, params);\n if (value !== UNREPRESENTABLE_DEFAULT) json.default = value;\n};\nconst prefaultProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n processSchema(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n if (ctx.io !== \"input\") return;\n const value = serializeDefaultValue(def.defaultValue, schema, ctx, json, params);\n if (value !== UNREPRESENTABLE_DEFAULT) json._prefault = value;\n};\nconst catchProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n processSchema(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n let catchValue;\n try {\n catchValue = def.catchValue(void 0);\n } catch {\n handleUnrepresentable(schema, ctx, json, params, \"Dynamic catch values are not supported in JSON Schema\");\n return;\n }\n json.default = catchValue;\n};\nconst pipeProcessor = (schema, ctx, _json, params) => {\n const def = schema._zod.def;\n const inIsTransform = def.in._zod.traits.has(\"$ZodTransform\");\n const innerType = ctx.io === \"input\" ? inIsTransform ? def.out : def.in : def.out;\n processSchema(innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = innerType;\n};\nconst readonlyProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n processSchema(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n json.readOnly = true;\n};\nconst optionalProcessor = (schema, ctx, _json, params) => {\n const def = schema._zod.def;\n processSchema(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/classic/errors.js\nconst _installedErrorProtos = /* @__PURE__ */ new WeakSet([Object.prototype, Error.prototype]);\nfunction _lazyMethod(proto, key, make) {\n Object.defineProperty(proto, key, {\n configurable: true,\n enumerable: false,\n get() {\n const value = make(this);\n Object.defineProperty(this, key, {\n value,\n configurable: true,\n writable: true\n });\n return value;\n },\n set(value) {\n Object.defineProperty(this, key, {\n value,\n configurable: true,\n writable: true\n });\n }\n });\n}\nconst initializer = (inst, issues) => {\n $ZodError.init(inst, issues);\n inst.name = \"ZodError\";\n const proto = Object.getPrototypeOf(inst);\n if (_installedErrorProtos.has(proto)) return;\n _installedErrorProtos.add(proto);\n _lazyMethod(proto, \"format\", (self) => (mapper) => formatError(self, mapper));\n _lazyMethod(proto, \"flatten\", (self) => (mapper) => flattenError(self, mapper));\n _lazyMethod(proto, \"addIssue\", (self) => (issue) => {\n self.issues.push(issue);\n self.message = JSON.stringify(self.issues, jsonStringifyReplacer, 2);\n });\n _lazyMethod(proto, \"addIssues\", (self) => (issues) => {\n self.issues.push(...issues);\n self.message = JSON.stringify(self.issues, jsonStringifyReplacer, 2);\n });\n Object.defineProperty(proto, \"isEmpty\", {\n configurable: true,\n enumerable: false,\n get() {\n return this.issues.length === 0;\n }\n });\n};\nconst ZodRealError = /*@__PURE__*/ $constructor(\"ZodError\", initializer, void 0, { Parent: Error });\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/classic/parse.js\nconst parse = /* @__PURE__ */ _parse(ZodRealError);\nconst parseAsync = /* @__PURE__ */ _parseAsync(ZodRealError);\nconst safeParse = /* @__PURE__ */ _safeParse(ZodRealError);\nconst safeParseAsync = /* @__PURE__ */ _safeParseAsync(ZodRealError);\nconst encode = /* @__PURE__ */ _encode(ZodRealError);\nconst decode = /* @__PURE__ */ _decode(ZodRealError);\nconst encodeAsync = /* @__PURE__ */ _encodeAsync(ZodRealError);\nconst decodeAsync = /* @__PURE__ */ _decodeAsync(ZodRealError);\nconst safeEncode = /* @__PURE__ */ _safeEncode(ZodRealError);\nconst safeDecode = /* @__PURE__ */ _safeDecode(ZodRealError);\nconst safeEncodeAsync = /* @__PURE__ */ _safeEncodeAsync(ZodRealError);\nconst safeDecodeAsync = /* @__PURE__ */ _safeDecodeAsync(ZodRealError);\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/classic/schemas.js\nfunction _ensureDefaultLocale() {\n if (!globalConfig.localeError) config(en_default());\n}\nfunction _ensureDefaultMemoizer() {\n if (!globalConfig.memoizer) config({ memoizer: memoizer() });\n}\nconst ZodType = /*@__PURE__*/ $constructor(\"ZodType\", (inst, def) => {\n _ensureDefaultLocale();\n $ZodType.init(inst, def);\n inst.def = def;\n inst.type = def.type;\n return inst;\n}, {\n check(...chks) {\n const def = this.def;\n return this.clone(mergeDefs(def, { checks: [...def.checks ?? [], ...chks.map((ch) => typeof ch === \"function\" ? { _zod: {\n check: ch,\n def: { check: \"custom\" },\n onattach: []\n } } : ch)] }), { parent: true });\n },\n with(...chks) {\n return this.check(...chks);\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 refine(check, params) {\n return this.check(refine(check, params));\n },\n superRefine(refinement, params) {\n return this.check(superRefine(refinement, params));\n },\n overwrite(fn) {\n return this.check(_overwrite(fn));\n },\n optional() {\n return optional(this);\n },\n exactOptional() {\n return exactOptional(this);\n },\n nullable() {\n return nullable(this);\n },\n nullish() {\n return optional(nullable(this));\n },\n nonoptional(params) {\n return nonoptional(this, params);\n },\n array() {\n return array(this);\n },\n or(arg) {\n return union([this, arg]);\n },\n and(arg) {\n return intersection(this, arg);\n },\n transform(tx) {\n return pipe(this, transform(tx));\n },\n default(d) {\n return _default(this, d);\n },\n prefault(d) {\n return prefault(this, d);\n },\n catch(params) {\n return _catch(this, params);\n },\n pipe(target) {\n return pipe(this, target);\n },\n readonly() {\n return readonly(this);\n },\n describe(description) {\n const cl = this.clone();\n globalRegistry.add(cl, { description });\n return cl;\n },\n meta(...args) {\n if (args.length === 0) return globalRegistry.get(this);\n const cl = this.clone();\n globalRegistry.add(cl, args[0]);\n return cl;\n },\n isOptional() {\n return this.safeParse(void 0).success;\n },\n isNullable() {\n return this.safeParse(null).success;\n },\n apply(fn, ...args) {\n return args.length === 0 ? fn(this) : fn(this, ...args);\n },\n get \"~standard\"() {\n return hide(this, \"~standard\", {\n ...standardProps(this),\n jsonSchema: {\n input: createStandardJSONSchemaMethod(this, \"input\"),\n output: createStandardJSONSchemaMethod(this, \"output\")\n }\n });\n },\n set \"~standard\"(value) {\n own(this, \"~standard\", value);\n },\n parse: function _parse(data, params) {\n return parse(this, data, params, { callee: _parse });\n },\n parseAsync: async function _parseAsync(data, params) {\n return await parseAsync(this, data, params, { callee: _parseAsync });\n },\n safeParse(data, params) {\n return safeParse(this, data, params);\n },\n async safeParseAsync(data, params) {\n return safeParseAsync(this, data, params);\n },\n get spa() {\n return this?.safeParseAsync;\n },\n set spa(value) {\n own(this, \"spa\", value);\n },\n validate(data, params) {\n return validate(this, data, params);\n },\n validateAsync(data, params) {\n return validateAsync$1(this, data, params);\n },\n encode: function _encode(data, params) {\n return encode(this, data, params, { callee: _encode });\n },\n decode: function _decode(data, params) {\n return decode(this, data, params, { callee: _decode });\n },\n encodeAsync: async function _encodeAsync(data, params) {\n return await encodeAsync(this, data, params, { callee: _encodeAsync });\n },\n decodeAsync: async function _decodeAsync(data, params) {\n return await decodeAsync(this, data, params, { callee: _decodeAsync });\n },\n safeEncode(data, params) {\n return safeEncode(this, data, params);\n },\n safeDecode(data, params) {\n return safeDecode(this, data, params);\n },\n async safeEncodeAsync(data, params) {\n return safeEncodeAsync(this, data, params);\n },\n async safeDecodeAsync(data, params) {\n return safeDecodeAsync(this, data, params);\n },\n toJSONSchema(params) {\n return createToJSONSchemaMethod(this, {})(params);\n },\n get description() {\n return globalRegistry.get(this)?.description;\n },\n get _def() {\n return this._zod.def;\n }\n});\n/** @internal */\nconst _ZodString = /*@__PURE__*/ $constructor(\"_ZodString\", (inst, def) => {\n $ZodString.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => stringProcessor(inst, ctx, json, params);\n}, /*@__PURE__*/ derived({\n format: (inst) => aggregateChecks(inst).format ?? null,\n minLength: (inst) => aggregateChecks(inst).minimum ?? null,\n maxLength: (inst) => aggregateChecks(inst).maximum ?? null\n}, {\n regex(...args) {\n return this.check(_regex(...args));\n },\n includes(...args) {\n return this.check(_includes(...args));\n },\n startsWith(...args) {\n return this.check(_startsWith(...args));\n },\n endsWith(...args) {\n return this.check(_endsWith(...args));\n },\n min(...args) {\n return this.check(_minLength(...args));\n },\n max(...args) {\n return this.check(_maxLength(...args));\n },\n length(...args) {\n return this.check(_length(...args));\n },\n nonempty(...args) {\n return this.check(_minLength(1, ...args));\n },\n lowercase(params) {\n return this.check(_lowercase(params));\n },\n uppercase(params) {\n return this.check(_uppercase(params));\n },\n trim() {\n return this.check(_trim());\n },\n normalize(...args) {\n return this.check(_normalize(...args));\n },\n toLowerCase() {\n return this.check(_toLowerCase());\n },\n toUpperCase() {\n return this.check(_toUpperCase());\n },\n slugify() {\n return this.check(_slugify());\n }\n}));\nconst ZodString = /*@__PURE__*/ $constructor(\"ZodString\", (inst, def) => {\n $ZodString.init(inst, def);\n _ZodString.init(inst, def);\n}, {\n email(params) {\n return this.check(_email(ZodEmail, params));\n },\n url(params) {\n return this.check(_url(ZodURL, params));\n },\n jwt(params) {\n return this.check(_jwt(ZodJWT, params));\n },\n emoji(params) {\n return this.check(_emoji(ZodEmoji, params));\n },\n guid(params) {\n return this.check(_guid(ZodGUID, params));\n },\n uuid(params) {\n return this.check(_uuid(ZodUUID, params));\n },\n uuidv4(params) {\n return this.check(_uuidv4(ZodUUID, params));\n },\n uuidv6(params) {\n return this.check(_uuidv6(ZodUUID, params));\n },\n uuidv7(params) {\n return this.check(_uuidv7(ZodUUID, params));\n },\n nanoid(params) {\n return this.check(_nanoid(ZodNanoID, params));\n },\n cuid(params) {\n return this.check(_cuid(ZodCUID, params));\n },\n cuid2(params) {\n return this.check(_cuid2(ZodCUID2, params));\n },\n ulid(params) {\n return this.check(_ulid(ZodULID, params));\n },\n base64(params) {\n return this.check(_base64(ZodBase64, params));\n },\n base64url(params) {\n return this.check(_base64url(ZodBase64URL, params));\n },\n xid(params) {\n return this.check(_xid(ZodXID, params));\n },\n ksuid(params) {\n return this.check(_ksuid(ZodKSUID, params));\n },\n ipv4(params) {\n return this.check(_ipv4(ZodIPv4, params));\n },\n ipv6(params) {\n return this.check(_ipv6(ZodIPv6, params));\n },\n cidrv4(params) {\n return this.check(_cidrv4(ZodCIDRv4, params));\n },\n cidrv6(params) {\n return this.check(_cidrv6(ZodCIDRv6, params));\n },\n e164(params) {\n return this.check(_e164(ZodE164, params));\n },\n datetime(params) {\n return this.check(_isoDateTime(ZodISODateTime, params));\n },\n date(params) {\n return this.check(_isoDate(ZodISODate, params));\n },\n time(params) {\n return this.check(_isoTime(ZodISOTime, params));\n },\n duration(params) {\n return this.check(_isoDuration(ZodISODuration, params));\n }\n});\nfunction string(params) {\n return _string(ZodString, params);\n}\nconst ZodStringFormat = /*@__PURE__*/ $constructor(\"ZodStringFormat\", (inst, def) => {\n $ZodStringFormat.init(inst, def);\n _ZodString.init(inst, def);\n});\nconst ZodISODateTime = /*@__PURE__*/ $constructor(\"ZodISODateTime\", (inst, def) => {\n $ZodISODateTime.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodISODate = /*@__PURE__*/ $constructor(\"ZodISODate\", (inst, def) => {\n $ZodISODate.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodISOTime = /*@__PURE__*/ $constructor(\"ZodISOTime\", (inst, def) => {\n $ZodISOTime.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodISODuration = /*@__PURE__*/ $constructor(\"ZodISODuration\", (inst, def) => {\n $ZodISODuration.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodEmail = /*@__PURE__*/ $constructor(\"ZodEmail\", (inst, def) => {\n $ZodEmail.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodGUID = /*@__PURE__*/ $constructor(\"ZodGUID\", (inst, def) => {\n $ZodGUID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodUUID = /*@__PURE__*/ $constructor(\"ZodUUID\", (inst, def) => {\n $ZodUUID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodURL = /*@__PURE__*/ $constructor(\"ZodURL\", (inst, def) => {\n $ZodURL.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodEmoji = /*@__PURE__*/ $constructor(\"ZodEmoji\", (inst, def) => {\n $ZodEmoji.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodNanoID = /*@__PURE__*/ $constructor(\"ZodNanoID\", (inst, def) => {\n $ZodNanoID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\n/**\n* @deprecated CUID v1 is deprecated by its authors due to information leakage\n* (timestamps embedded in the id). Use {@link ZodCUID2} instead.\n* See https://github.com/paralleldrive/cuid.\n*/\nconst ZodCUID = /*@__PURE__*/ $constructor(\"ZodCUID\", (inst, def) => {\n $ZodCUID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodCUID2 = /*@__PURE__*/ $constructor(\"ZodCUID2\", (inst, def) => {\n $ZodCUID2.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodULID = /*@__PURE__*/ $constructor(\"ZodULID\", (inst, def) => {\n $ZodULID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodXID = /*@__PURE__*/ $constructor(\"ZodXID\", (inst, def) => {\n $ZodXID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodKSUID = /*@__PURE__*/ $constructor(\"ZodKSUID\", (inst, def) => {\n $ZodKSUID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodIPv4 = /*@__PURE__*/ $constructor(\"ZodIPv4\", (inst, def) => {\n $ZodIPv4.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodIPv6 = /*@__PURE__*/ $constructor(\"ZodIPv6\", (inst, def) => {\n $ZodIPv6.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodCIDRv4 = /*@__PURE__*/ $constructor(\"ZodCIDRv4\", (inst, def) => {\n $ZodCIDRv4.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodCIDRv6 = /*@__PURE__*/ $constructor(\"ZodCIDRv6\", (inst, def) => {\n $ZodCIDRv6.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodBase64 = /*@__PURE__*/ $constructor(\"ZodBase64\", (inst, def) => {\n $ZodBase64.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodBase64URL = /*@__PURE__*/ $constructor(\"ZodBase64URL\", (inst, def) => {\n $ZodBase64URL.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodE164 = /*@__PURE__*/ $constructor(\"ZodE164\", (inst, def) => {\n $ZodE164.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodJWT = /*@__PURE__*/ $constructor(\"ZodJWT\", (inst, def) => {\n $ZodJWT.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodUnknown = /*@__PURE__*/ $constructor(\"ZodUnknown\", (inst, def) => {\n $ZodUnknown.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => unknownProcessor(inst, ctx, json, params);\n});\nfunction unknown() {\n return _unknown(ZodUnknown);\n}\nconst ZodNever = /*@__PURE__*/ $constructor(\"ZodNever\", (inst, def) => {\n $ZodNever.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => neverProcessor(inst, ctx, json, params);\n});\nfunction never(params) {\n return _never(ZodNever, params);\n}\nconst ZodArray = /*@__PURE__*/ $constructor(\"ZodArray\", (inst, def) => {\n _ensureDefaultMemoizer();\n $ZodArray.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => arrayProcessor(inst, ctx, json, params);\n inst.element = def.element;\n}, {\n min(n, params) {\n return this.check(_minLength(n, params));\n },\n nonempty(params) {\n return this.check(_minLength(1, params));\n },\n max(n, params) {\n return this.check(_maxLength(n, params));\n },\n length(n, params) {\n return this.check(_length(n, params));\n },\n unwrap() {\n return this.element;\n }\n});\nfunction array(element, params) {\n return _array(ZodArray, element, params);\n}\nconst ZodObject = /*@__PURE__*/ $constructor(\"ZodObject\", (inst, def) => {\n _ensureDefaultMemoizer();\n $ZodObjectJIT.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => objectProcessor(inst, ctx, json, params);\n installLazyProp(inst, \"shape\", (self) => self._zod.def.shape, false);\n}, {\n keyof() {\n return _enum(Object.keys(this._zod.def.shape));\n },\n catchall(catchall) {\n return this.clone(mergeDefs(this._zod.def, { catchall }));\n },\n passthrough() {\n return this.clone(mergeDefs(this._zod.def, { catchall: unknown() }));\n },\n loose() {\n return this.clone(mergeDefs(this._zod.def, { catchall: unknown() }));\n },\n strict() {\n return this.clone(mergeDefs(this._zod.def, { catchall: never() }));\n },\n strip() {\n return this.clone(mergeDefs(this._zod.def, { catchall: void 0 }));\n },\n extend(incoming) {\n return extend(this, incoming);\n },\n safeExtend(incoming) {\n return safeExtend(this, incoming);\n },\n merge(other) {\n return merge(this, other);\n },\n pick(mask) {\n return pick(this, mask);\n },\n omit(mask) {\n return omit(this, mask);\n },\n partial(...args) {\n return partial(ZodOptional, this, args[0]);\n },\n exactPartial(...args) {\n return partial(ZodExactOptional, this, args[0], \"exactPartial\");\n },\n required(...args) {\n return required(ZodNonOptional, this, args[0]);\n }\n});\nfunction object(shape, params) {\n const def = {\n type: \"object\",\n shape: shape ?? {},\n ...normalizeParams(params)\n };\n return new ZodObject(def);\n}\nconst ZodUnion = /*@__PURE__*/ $constructor(\"ZodUnion\", (inst, def) => {\n $ZodUnion.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => unionProcessor(inst, ctx, json, params);\n inst.options = def.options;\n});\nfunction union(options, params) {\n return new ZodUnion({\n type: \"union\",\n options,\n ...normalizeParams(params)\n });\n}\nconst ZodIntersection = /*@__PURE__*/ $constructor(\"ZodIntersection\", (inst, def) => {\n $ZodIntersection.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => intersectionProcessor(inst, ctx, json, params);\n});\nfunction intersection(left, right) {\n return new ZodIntersection({\n type: \"intersection\",\n left,\n right\n });\n}\nconst ZodRecord = /*@__PURE__*/ $constructor(\"ZodRecord\", (inst, def) => {\n _ensureDefaultMemoizer();\n $ZodRecord.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => recordProcessor(inst, ctx, json, params);\n inst.keyType = def.keyType;\n inst.valueType = def.valueType;\n});\nfunction record(keyType, valueType, params) {\n if (!valueType || !valueType._zod) return new ZodRecord({\n type: \"record\",\n keyType: string(),\n valueType: keyType,\n ...normalizeParams(valueType)\n });\n return new ZodRecord({\n type: \"record\",\n keyType,\n valueType,\n ...normalizeParams(params)\n });\n}\nconst ZodEnum = /*@__PURE__*/ $constructor(\"ZodEnum\", (inst, def) => {\n $ZodEnum.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => enumProcessor(inst, ctx, json, params);\n inst.enum = def.entries;\n inst.options = [...inst._zod.values];\n const keys = new Set(Object.keys(def.entries));\n inst.extract = (values, params) => {\n const newEntries = {};\n for (const value of values) if (keys.has(value)) newEntries[value] = def.entries[value];\n else throw new Error(`Key ${value} not found in enum`);\n return new ZodEnum({\n ...def,\n checks: [],\n ...normalizeParams(params),\n entries: newEntries\n });\n };\n inst.exclude = (values, params) => {\n const newEntries = { ...def.entries };\n for (const value of values) if (keys.has(value)) delete newEntries[value];\n else throw new Error(`Key ${value} not found in enum`);\n return new ZodEnum({\n ...def,\n checks: [],\n ...normalizeParams(params),\n entries: newEntries\n });\n };\n});\nfunction _enum(values, params) {\n const entries = Array.isArray(values) ? Object.fromEntries(values.map((v) => [v, v])) : values;\n return new ZodEnum({\n type: \"enum\",\n entries,\n ...normalizeParams(params)\n });\n}\nconst ZodTransform = /*@__PURE__*/ $constructor(\"ZodTransform\", (inst, def) => {\n _ensureDefaultMemoizer();\n $ZodTransform.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => transformProcessor(inst, ctx, json, params);\n inst._zod.parse = (payload, _ctx) => {\n if (_ctx.direction === \"backward\") throw new $ZodEncodeError(inst.constructor.name);\n payload.addIssue = (issue$1) => {\n if (typeof issue$1 === \"string\") payload.issues.push(issue(issue$1, payload.value, def));\n else {\n const _issue = issue$1;\n if (_issue.fatal) _issue.continue = false;\n _issue.code ?? (_issue.code = \"custom\");\n if (!(\"input\" in _issue)) _issue.input = payload.value;\n _issue.inst ?? (_issue.inst = inst);\n payload.issues.push(issue(_issue));\n }\n };\n const output = def.transform(payload.value, payload);\n if (output instanceof Promise) return output.then((output) => {\n payload.value = output;\n return payload;\n });\n payload.value = output;\n return payload;\n };\n});\nfunction transform(fn) {\n return new ZodTransform({\n type: \"transform\",\n transform: fn\n });\n}\nconst ZodOptional = /*@__PURE__*/ $constructor(\"ZodOptional\", (inst, def) => {\n $ZodOptional.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => optionalProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction optional(innerType) {\n return new ZodOptional({\n type: \"optional\",\n innerType\n });\n}\nconst ZodExactOptional = /*@__PURE__*/ $constructor(\"ZodExactOptional\", (inst, def) => {\n $ZodExactOptional.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => optionalProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction exactOptional(innerType) {\n return new ZodExactOptional({\n type: \"optional\",\n innerType\n });\n}\nconst ZodNullable = /*@__PURE__*/ $constructor(\"ZodNullable\", (inst, def) => {\n $ZodNullable.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => nullableProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction nullable(innerType) {\n return new ZodNullable({\n type: \"nullable\",\n innerType\n });\n}\nconst ZodDefault = /*@__PURE__*/ $constructor(\"ZodDefault\", (inst, def) => {\n $ZodDefault.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => defaultProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n inst.removeDefault = inst.unwrap;\n});\nfunction _default(innerType, defaultValue) {\n return new ZodDefault({\n type: \"default\",\n innerType,\n get defaultValue() {\n return typeof defaultValue === \"function\" ? defaultValue() : shallowClone(defaultValue);\n }\n });\n}\nconst ZodPrefault = /*@__PURE__*/ $constructor(\"ZodPrefault\", (inst, def) => {\n $ZodPrefault.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => prefaultProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction prefault(innerType, defaultValue) {\n return new ZodPrefault({\n type: \"prefault\",\n innerType,\n get defaultValue() {\n return typeof defaultValue === \"function\" ? defaultValue() : shallowClone(defaultValue);\n }\n });\n}\nconst ZodNonOptional = /*@__PURE__*/ $constructor(\"ZodNonOptional\", (inst, def) => {\n $ZodNonOptional.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => nonoptionalProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction nonoptional(innerType, params) {\n return new ZodNonOptional({\n type: \"nonoptional\",\n innerType,\n ...normalizeParams(params)\n });\n}\nconst ZodCatch = /*@__PURE__*/ $constructor(\"ZodCatch\", (inst, def) => {\n $ZodCatch.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => catchProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n inst.removeCatch = inst.unwrap;\n});\nfunction _catch(innerType, catchValue) {\n return new ZodCatch({\n type: \"catch\",\n innerType,\n catchValue: typeof catchValue === \"function\" ? catchValue : constantCatch(catchValue)\n });\n}\nconst ZodPipe = /*@__PURE__*/ $constructor(\"ZodPipe\", (inst, def) => {\n $ZodPipe.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => pipeProcessor(inst, ctx, json, params);\n inst.in = def.in;\n inst.out = def.out;\n});\nfunction pipe(in_, out) {\n return new ZodPipe({\n type: \"pipe\",\n in: in_,\n out\n });\n}\nconst ZodReadonly = /*@__PURE__*/ $constructor(\"ZodReadonly\", (inst, def) => {\n $ZodReadonly.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => readonlyProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction readonly(innerType) {\n return new ZodReadonly({\n type: \"readonly\",\n innerType\n });\n}\nconst ZodCustom = /*@__PURE__*/ $constructor(\"ZodCustom\", (inst, def) => {\n $ZodCustom.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => customProcessor(inst, ctx, json, params);\n});\nfunction refine(fn, _params = {}) {\n return _refine(ZodCustom, fn, _params);\n}\nfunction superRefine(fn, params) {\n return _superRefine(fn, params);\n}\n\n//#endregion\n//#region src/lib/hook-io.ts\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});\n/**\n* Read and parse the full stdin as JSON.\n* Returns a result with the validated HookInput, or the raw data and error on failure.\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});\n/** Parse tool_input as Bash input. Returns null if invalid. */\nfunction parseBashInput(toolInput) {\n const result = BashInputSchema.safeParse(toolInput);\n return result.success ? result.data : null;\n}\n/** Parse tool_input as file-path input (Read, Write, Edit). Returns null if invalid. */\nfunction parseFilePathInput(toolInput) {\n const result = FilePathInputSchema.safeParse(toolInput);\n return result.success ? result.data : null;\n}\n/** Parse tool_input as search input (Glob, Grep). Returns null if invalid. */\nfunction parseSearchInput(toolInput) {\n const result = SearchInputSchema.safeParse(toolInput);\n return result.success ? result.data : null;\n}\nfunction writeDecision(action, detail) {\n const decision = { hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: action,\n ...action === \"deny\" && detail ? { permissionDecisionReason: detail } : {},\n ...action === \"allow\" && detail ? { additionalContext: detail } : {}\n } };\n process.stdout.write(JSON.stringify(decision));\n}\n\n//#endregion\n//#region src/lib/audit.ts\nconst MAX_LOG_SIZE = 1048576;\n/**\n* Resolve the audit log path relative to the hook's own location.\n* Co-located at `.claude/hooks/bst-hook.audit.local.jsonl`.\n*/\nfunction getLogPath() {\n const hookDir = path.dirname(fileURLToPath(import.meta.url));\n return path.join(hookDir, \"bst-hook.audit.local.jsonl\");\n}\n/**\n* Rotate the log file if it exceeds MAX_LOG_SIZE.\n* Renames current log to `.1` and starts fresh.\n* Safe against concurrent rotation -- ENOENT on rename is expected and ignored.\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}\n/** Whether rotation has been checked this session */\nlet rotationChecked = false;\n/**\n* Append an audit entry to the JSONL log.\n* Never throws -- audit failures log to stderr and continue.\n*/\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\n/**\n* Scan a double-quoted string starting at `start` (the char after the opening\n* quote). Returns the unescaped content and the index just past the closing\n* quote, or `null` if the quote is never closed.\n*/\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}\n/**\n* Characters safe to leave unquoted when re-emitting a token. Anything outside\n* this set could be interpreted by the shell-splitter or tokenizer on a\n* subsequent pass (operators, whitespace, quotes, globs, vars, etc.).\n*/\nconst SAFE_UNQUOTED_RE = /^[A-Za-z0-9_\\-./=:@%+,]+$/;\n/** Single-quote-wrap a token unless it only contains safe characters. */\nfunction quoteToken(token) {\n if (token.length > 0 && SAFE_UNQUOTED_RE.test(token)) return token;\n return \"'\" + token.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n/**\n* Inverse of `tokenize`: join tokens into a command string that survives\n* round-tripping through `splitCommand` and `tokenize`.\n*\n* Naive `tokens.join(\" \")` is lossy: any token that was originally quoted\n* loses its quote wrapper and may be mis-parsed when split/tokenized again\n* (e.g. `--testPathPattern=a|b` would split on the embedded `|`).\n*/\nfunction joinTokens(tokens) {\n return tokens.map(quoteToken).join(\" \");\n}\n\n//#endregion\n//#region src/lib/shell-splitter.ts\n/**\n* Split a raw command string into segments on unquoted &&, ||, ;, and | operators.\n* Quote-aware: operators inside single or double quotes are not split points.\n*\n* Returns segments with the operator that follows each one (null for the last segment).\n*/\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\n/** Safe redirect patterns that don't constitute file writes */\nconst SAFE_REDIRECTS = /* @__PURE__ */ new Set([\n \"2>&1\",\n \"2>/dev/null\",\n \">/dev/null\",\n \"1>/dev/null\"\n]);\n/**\n* Bash brace expansion: a `{...}` group containing a comma (list form,\n* `{a,b}`/`{a,}`/`{,a}`) or `..` (range form, `{1..3}`/`{a..z}`) is expanded\n* into multiple words *before* the command runs -- so `--for{c,}e` reaches the\n* program as argv `--force --fore` and `r{m,}m` as `rmm rm`. Braces without a\n* comma or `..` (`a{b}`, `HEAD@{1}`, `{}`) are left literal by bash and must\n* not be flagged.\n*\n* Bash brace expansion is *recursive*: a comma/range list expands even when a\n* branch of it contains its own nested `{...}`, so `--for{c,{}}e` still yields\n* `--force`. A regex cannot express \"a `,` or `..` at balanced brace depth\"\n* (the earlier `/\\{[^{}]*(?:,|\\.\\.)[^{}]*\\}/` required the separator to be\n* flanked by brace-free text out to the enclosing braces, which nested braces\n* defeat while bash still expands). So this is a depth-aware scan: increment on\n* `{`, decrement on `}` (never below 0), and flag the token if a `,` or `..`\n* appears while depth >= 1 -- i.e. anywhere inside any brace group, no matter\n* how deeply nested. Fail-closed: unbalanced braces are flagged too.\n*\n* As with `$`-expansion, the tokenizer has already stripped single quotes by\n* the time `checkInjection` runs, so a genuinely-quoted `'{a,b}'` or\n* `'{\"a\":1,\"b\":2}'` is indistinguishable from an unquoted brace-expansion here\n* and is flagged too -- an accepted fail-closed false positive (passthrough\n* only prompts a human).\n*/\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}\n/**\n* An output-redirect token is safe only when it targets `/dev/null` -- either as\n* a combined token (`>/dev/null`) or a bare operator whose next token is `/dev/null`.\n*/\nfunction isSafeOutputRedirect(token, next) {\n if (token.endsWith(\">/dev/null\")) return true;\n return token.match(/^(&>>|&>|>>|>|1>|2>)$/) !== null && next === \"/dev/null\";\n}\n/**\n* Detect dangerous patterns in tokenized command segments.\n*\n* Checks for:\n* - Command substitution (`$(...)`, backticks)\n* - Any other unresolved `$`-expansion (`${...}`, `$'...'`, `$\"...\"`, `$VAR`, `$1`, ...)\n* - Brace expansion (`{a,b}` list form, `{1..3}` range form)\n* - Process substitution (`<(...)`, `>(...)`)\n* - File redirects (`>`, `>>`, `<` to non-/dev/null targets) -- skipped when `skipRedirects` is true\n* - Newlines in raw segment (injection via line splitting)\n*\n* The tokenizer (`shell-tokenizer.ts`) resolves single-quoted and\n* backslash-escaped content into plain characters before this function ever\n* sees it -- so a token's surviving `$` is indistinguishable between \"bash\n* will expand this\" (`$(...)`, `${...}`, `$'...'`, `$\"...\"`, `$VAR`, `$1`,\n* `$@`, `$?`, ...) and \"this `$` was always a literal character\" (e.g. from\n* `'cost $5'` or `\\$5`). Rather than trying to reconstruct quote context\n* after the fact, any token still containing a `$` is treated as unsafe.\n* This is deliberately fail-closed: it routes the segment to passthrough\n* (a human is prompted) rather than risking an unresolved expansion silently\n* auto-approving or auto-denying based on the wrong argv.\n*\n* ## Expansions this guard knowingly does NOT model\n*\n* Two weaker members of the same \"unmodeled expansion\" family are left open. Both were\n* surfaced by the adversarial review of the issue-attachments hardening and verified\n* empirically against real bash plus this evaluator. **Decision (2026-07-13): accept as\n* documented limitations.** They are recorded here, not as a todo, because the next\n* person to touch this function is the person who needs to know.\n*\n* **Glob reconstruction.** `bst issue:download 273 --forc[e]` (also `--forc?`, `--for*`,\n* `--*`) expands to a real `--force` argv and this returns `safe` -- but *only when a file\n* literally named `--force` already exists in the working directory*. Creating that file\n* is itself hook-gated (`> --force` is caught as a redirect; `touch -- --force` falls to\n* passthrough), so it is a two-step, precondition-gated exploit -- strictly weaker than the\n* unconditional `$`/brace bypasses that were fixed. The same class defeats deny rules\n* (`r[m] -rf`), again only with a pre-existing matching file.\n*\n* *Why not fixed:* closing it means routing any token containing an unquoted glob\n* metacharacter (`*`, `?`, `[`) to passthrough -- which prompts on `ls *.md`, `find . -name\n* '*.ts'`, and nearly every globbing command an agent legitimately runs. Unlike `$` and\n* brace expansion (rare in real commands), globs are everywhere, so the prompt-fatigue\n* cost is real and daily, and a guard people learn to click through protects nobody.\n* Revisit only if a cheaper predicate emerges -- the candidate is to flag a glob metachar\n* **only in a token that also begins with `-`** (a flag position, which is where the\n* reconstruction attack lives), leaving bare-argument globs like `*.md` allowed. That\n* would live right here, beside {@link hasBraceExpansion}.\n*\n* **Tilde expansion.** `~` is unmodeled. Bash only expands it to a home *path* at word\n* start, so it cannot reconstruct a flag or a command name -- it is a path-scope concern,\n* not the flag/command-reconstruction vector this guard defends against. Lowest priority;\n* noted for completeness.\n*\n* If either is ever closed, pin it with regression fixtures the way the nested-brace cases\n* were pinned in `tests/fixtures.ts`.\n*\n* @param skipRedirects When true, skip redirect analysis (used for trusted resolved scripts)\n*/\nfunction checkInjection(tokens, rawSegment, skipRedirects = false) {\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(\">(\") && !SAFE_REDIRECTS.has(token)) return {\n safe: false,\n reason: \"process substitution\"\n };\n }\n if (!skipRedirects) for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i];\n if (SAFE_REDIRECTS.has(token)) continue;\n if (token.includes(\"<\") && !token.includes(\"<(\")) return {\n safe: false,\n reason: \"input redirect\"\n };\n if (token.includes(\">\")) {\n if (isSafeOutputRedirect(token, tokens[i + 1])) continue;\n return {\n safe: false,\n reason: \"output redirect to file\"\n };\n }\n }\n return { safe: true };\n}\n\n//#endregion\n//#region src/lib/env-file.ts\n/**\n* File extensions considered safe documentation templates.\n* Files ending with these suffixes are NOT treated as secret .env files.\n*/\nconst ENV_SAFE_SUFFIXES = [\n \".sample\",\n \".example\",\n \".template\"\n];\n/**\n* Env files owned by a bst command rather than by the developer's secrets.\n*\n* `docker-compose.check.env` is the file `bst docker:check` auto-detects and\n* passes to compose as `--env-file`. It is committed, and it holds placeholder\n* values for a throwaway check stack -- yet it is also the *only* way to feed\n* compose interpolation, so denying it left an agent unable to create or read\n* the one file the check needs. Two bst features blocking each other is worse\n* than the marginal secret risk of a file whose purpose is to hold non-secrets.\n*/\nconst ENV_TOOL_OWNED_SUFFIXES = [\".check.env\"];\n/**\n* Pattern matching .env files: `.env`, `.env.local`, `.env.production`, etc.\n* Does NOT match `.env.sample`, `.env.example`, `.env.template`.\n*/\nconst ENV_FILE_PATTERN = /(^|[/\\\\])(\\.env(\\..*)?|.*\\.env(\\..*)?|\\.env)$/;\n/** Check if a filename is a safe .env documentation template, or a bst-owned env file. */\nfunction isEnvSafeSuffix(filename) {\n return [...ENV_SAFE_SUFFIXES, ...ENV_TOOL_OWNED_SUFFIXES].some((suffix) => filename.endsWith(suffix));\n}\n/**\n* Check if a path targets a .env file (excluding safe templates).\n* Works with both bare filenames and full paths.\n*/\nfunction isEnvFile(filePath) {\n if (isEnvSafeSuffix(path.basename(filePath))) return false;\n return ENV_FILE_PATTERN.test(filePath);\n}\n/**\n* Check if any non-flag token in a Bash command targets a .env file.\n* Used by Bash deny rules for cat, head, tail, grep, etc.\n*/\nfunction tokensTargetEnvFile(tokens) {\n return tokens.slice(1).some((t) => !t.startsWith(\"-\") && isEnvFile(t));\n}\n\n//#endregion\n//#region src/lib/git-state.ts\n/**\n* Return the current branch name, or null when:\n* - cwd is not a git repository,\n* - HEAD is detached,\n* - any git invocation fails.\n*\n* Null is the safe protected default -- callers treat it as \"protected\".\n*/\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 }).toString().trim();\n if (!out || out === \"HEAD\") return null;\n return out;\n } catch {\n return null;\n }\n}\n/**\n* Return the remote default branch (from refs/remotes/origin/HEAD), stripped\n* of the \"origin/\" prefix. Returns null when the symbolic ref isn't set\n* (common for fresh clones with no remote).\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 }).toString().trim();\n if (!out) return null;\n return out.startsWith(\"origin/\") ? out.slice(7) : out;\n } catch {\n return null;\n }\n}\nconst ALWAYS_PROTECTED = [\"main\", \"master\"];\n/**\n* Compute the full protected set: hard-coded {main, master}, plus the remote\n* default branch when available, plus any caller-supplied extras (e.g. from\n* .claude/hooks/bst-hook.settings.json#protectedBranches).\n*\n* Known limitation: if a repo's real default branch is neither \"main\" nor\n* \"master\" AND refs/remotes/origin/HEAD is unset (getRemoteDefaultBranch()\n* returns null -- e.g. a shallow or partial clone that never fetched the\n* symbolic ref), that default branch is absent from this set. A push whose\n* refspec destination is that branch would then pass the protected-branch\n* check in isGitSafePush. This is not treated as a reason to block pushes\n* whenever origin/HEAD is unset, since that would break the far more common\n* case (default branch already covered by {main, master} or by\n* protectedBranches); repos with a non-standard default branch name should\n* set it explicitly via .claude/hooks/bst-hook.settings.json#protectedBranches.\n*/\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}\n/**\n* Configured remote names (`git remote`). Empty when cwd is not a repo or git\n* fails -- the safe default, since callers require membership to auto-approve.\n*/\nfunction getRemotes(cwd) {\n try {\n const out = execFileSync(\"git\", [\"remote\"], {\n cwd,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ]\n }).toString().trim();\n if (!out) return /* @__PURE__ */ new Set();\n return new Set(out.split(\"\\n\").map((line) => line.trim()).filter(Boolean));\n } catch {\n return /* @__PURE__ */ new Set();\n }\n}\n/**\n* True when `cwd` is the repository's *primary* worktree (not a linked one\n* created by `git worktree add`). In the primary worktree the git dir and the\n* common git dir are the same directory; in a linked worktree the git dir is\n* `.../.git/worktrees/<name>` while the common dir is the shared `.../.git`.\n*\n* Fail-safe: any git/filesystem error returns `false` (treated as \"not primary\",\n* i.e. no advisory) so a hiccup never disturbs the decision. Mirrors the\n* \"safe default\" convention of getCurrentBranch.\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 }).toString().trim();\n const commonRaw = execFileSync(\"git\", [\"rev-parse\", \"--git-common-dir\"], {\n cwd,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ]\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\n/** Per-subcommand required-value short option letters. Deliberately excludes\n* `-t` (`--track`): see the note below the value-taking sets.\n*/\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};\n/**\n* Decompose a short-option cluster token for `sub`. Returns `undefined` for\n* anything that is not a short-option cluster: a token that does not start\n* with `-`, the bare `-`, and anything starting with `--`. Every caller keeps\n* handling those tokens exactly as it does today.\n*\n* Otherwise walks the characters after the leading `-` left to right,\n* collecting each into `letters`, and stops at the first character that is a\n* required-value option letter for `sub` -- that letter is the last one\n* collected, and the remainder of the token (possibly empty) becomes\n* `attachedValue`, never inspected as flags again.\n*\n* Worked cases:\n* - `-fc` (switch or checkout) -> letters `[\"f\", \"c\"]`, no attached value --\n* `f` never stops the scan, so `c`/`C` is just collected as another letter\n* unless the subcommand's own value-taking set includes it earlier.\n* - `-cf` (switch) -> letters `[\"c\"]`, attached value `\"f\"`.\n* - `-cfeat/x` (switch) -> letters `[\"c\"]`, attached value `\"feat/x\"`.\n* - `-c` (switch) -> letters `[\"c\"]`, attached value `\"\"` (git reads the\n* branch name from the next argv element).\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\n/**\n* Path-containment helpers that are correct across operating systems.\n*\n* The hook receives a `cwd` from its host. On Windows the same location can be\n* spelled with either drive-letter case (`D:\\proj` vs `d:\\proj`) -- the IDE is\n* inconsistent -- and NTFS treats them as identical. Plain `===` / `startsWith`\n* comparisons on the resolved path therefore misfire whenever the drive-letter\n* case differs, falling through to passthrough (reads) or a false\n* \"outside the project\" deny (writes).\n*\n* The fix: pick the path flavor (win32 vs posix) from the *trusted* base path --\n* `cwd` -- and run every resolve/compare through that flavor. win32's\n* `relative()` is case-insensitive and separator-aware, so drive-letter case no\n* longer matters; posix stays case-sensitive, as that filesystem demands.\n* Deriving the flavor from the base (not the incoming target) means a crafted\n* target can't flip the comparison mode.\n*/\n/** Select the path flavor implied by a base path (a leading drive letter => win32). */\nfunction pathFlavorFor(base) {\n return /^[a-zA-Z]:[\\\\/]/.test(base) ? path.win32 : path.posix;\n}\n/** Resolve `target` against `base` using the flavor implied by `base`. */\nfunction resolveIn(base, target) {\n return pathFlavorFor(base).resolve(base, target);\n}\n/**\n* Whether `target` is `dir` itself or nested beneath it, compared with `dir`'s\n* flavor. `target` is expected to be already resolved (absolute); pass it through\n* `resolveIn` first when it may be relative or contain `..`.\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}\n/** Whether two paths refer to the same location, compared with `a`'s flavor. */\nfunction samePath(a, b) {\n return pathFlavorFor(a).relative(a, b) === \"\";\n}\n/**\n* Whether `filePath` (resolved against `cwd`) is a `.git` directory\n* anywhere under `cwd` -- top-level or nested inside a subdirectory repo --\n* or anything beneath one. Covers both the directory in a normal clone and\n* the gitdir-pointer *file* git writes at the root of a worktree checkout\n* (both cases have a path segment exactly equal to `.git`).\n*\n* A nested repo's git dir (`<cwd>/sub/.git/config`) is just as sensitive as\n* the top-level one: an unguarded write there can add a malicious remote or\n* rewrite push config that a later `cd sub && git push` trusts. So this\n* checks every path segment of `filePath` resolved against `cwd`, not just\n* `<cwd>/.git` -- matching `.git` at *any* depth, as long as the resolved\n* path stays within `cwd` (paths outside `cwd` are handled by the\n* outside-cwd-write rule instead).\n*\n* Matches on a resolved path *segment* exactly equal to `.git`, not a\n* substring, so it does not mistake `.gitignore`, `.gitkeep`, `.github/`,\n* `src/foo.git.ts`, or a directory merely named `mygit`/`git` for a `.git/`\n* target.\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}\n/**\n* Whether `filePath` (resolved against `cwd`) has a path segment exactly equal\n* to `node_modules` -- i.e. it names a location inside *any* installed package\n* tree, at any depth, including pnpm's virtual store\n* (`node_modules/.pnpm/<pkg>/node_modules/<pkg>/...`).\n*\n* **Deliberately not scoped to `cwd`**, unlike `targetsGitDir`. pnpm populates\n* `node_modules` with *hardlinks into the global content-addressable store*, so\n* a single in-place overwrite of an installed file (a plain `cp`, a `>`\n* redirect -- anything that opens the destination `O_WRONLY|O_TRUNC` rather than\n* unlinking it first) writes *through* the link and mutates the store's own\n* content-addressed file. That corrupts the package for the global store, every\n* other project on the machine that shares the inode, and every future install\n* or `pnpm dlx` that hardlinks it -- with no error at the scene, because the\n* directory that did the writing ends up perfectly consistent.\n*\n* This is exactly how a local `@bensandee/tooling` build landed on top of the\n* published 0.64.2 in the store on 2026-07-13: `cp -r <local>/dist/*` over a\n* scratch consumer's `node_modules/@bensandee/tooling/dist/`. The destination\n* was outside `cwd` -- so a cwd-scoped check would not have seen it -- and the\n* store stayed broken until a consumer's `pnpm dlx` failed days later with\n* `ERR_MODULE_NOT_FOUND` for a chunk that no published tarball has ever\n* contained.\n*\n* The lesson encoded here: **cwd-containment is not a safety boundary for\n* `node_modules`.** A path inside it is not \"in the project\" in any meaningful\n* sense -- it is a shared, machine-global resource wearing a local path. Hence\n* every segment of the *resolved* path is checked, wherever it lands.\n*\n* Matches on a path *segment*, not a substring, so `src/node_modules_helper.ts`\n* and `docs/node_modules.md` are not mistaken for the real thing.\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_READ_OPS = /* @__PURE__ */ new Set([\n \"status\",\n \"log\",\n \"diff\",\n \"show\",\n \"blame\",\n \"grep\",\n \"shortlog\",\n \"range-diff\",\n \"describe\",\n \"remote\",\n \"fetch\",\n \"ls-files\",\n \"ls-tree\",\n \"ls-remote\",\n \"show-ref\",\n \"for-each-ref\",\n \"rev-parse\",\n \"rev-list\",\n \"cat-file\",\n \"diff-tree\",\n \"name-rev\",\n \"merge-base\",\n \"check-ignore\"\n]);\n/** Git subcommands that modify the working tree, index, or history. */\nconst GIT_WRITE_OPS = /* @__PURE__ */ new Set([\n \"push\",\n \"add\",\n \"commit\",\n \"reset\",\n \"merge\",\n \"rebase\",\n \"cherry-pick\",\n \"checkout\",\n \"switch\",\n \"stash\",\n \"tag\",\n \"revert\",\n \"clean\",\n \"rm\",\n \"mv\"\n]);\n/** Flags on git that enable arbitrary code execution */\nconst GIT_DANGEROUS_FLAGS = /* @__PURE__ */ new Set([\n \"-c\",\n \"--exec-path\",\n \"--pager\",\n \"--upload-pack\",\n \"--receive-pack\"\n]);\n/** Git global flags that consume the next token as an argument. */\nconst GIT_FLAGS_WITH_ARG = /* @__PURE__ */ new Set([\n \"-C\",\n \"-c\",\n \"--git-dir\",\n \"--work-tree\"\n]);\n/** Extract the git subcommand (first non-flag token after \"git\"), skipping flag arguments. */\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}\nfunction isGitReadOp(tokens) {\n for (const t of tokens.slice(1)) {\n if (GIT_DANGEROUS_FLAGS.has(t) || t === \"-o\" || t.startsWith(\"--output\")) return false;\n if (t.startsWith(\"--exec-path=\") || t.startsWith(\"--pager=\") || t.startsWith(\"--upload-pack=\") || t.startsWith(\"--receive-pack=\") || t.startsWith(\"-o=\") || t.startsWith(\"--output=\")) return false;\n }\n const sub = getGitSubcommand(tokens);\n if (!sub) return false;\n if (sub === \"stash\") {\n const stashAction = tokens[tokens.indexOf(\"stash\") + 1];\n return stashAction === \"list\" || stashAction === void 0;\n }\n if (sub === \"remote\") {\n let actionIdx = tokens.indexOf(\"remote\") + 1;\n while (tokens[actionIdx] === \"-v\" || tokens[actionIdx] === \"--verbose\") actionIdx++;\n const remoteAction = tokens[actionIdx];\n return remoteAction === void 0 || remoteAction === \"-v\" || remoteAction === \"--verbose\" || remoteAction === \"show\" || remoteAction === \"get-url\";\n }\n return GIT_READ_OPS.has(sub);\n}\nfunction isGitWriteOp(tokens) {\n const sub = getGitSubcommand(tokens);\n if (!sub) return false;\n if (sub === \"stash\") {\n const stashAction = tokens[tokens.indexOf(\"stash\") + 1];\n if (stashAction === \"list\" || stashAction === void 0) return false;\n }\n return GIT_WRITE_OPS.has(sub);\n}\n/** Safe stash actions that don't risk data loss. */\nconst STASH_SAFE_ACTIONS = /* @__PURE__ */ new Set([\n \"push\",\n \"pop\",\n \"apply\",\n \"show\"\n]);\n/**\n* Positional (non-flag) path arguments of a `git mv` command. The caller only\n* invokes this once the subcommand is known to be `mv`, so `mv` is present.\n* `git mv` flags (-f/--force, -k, -n/--dry-run, -v/--verbose) never consume a\n* separate argument, so every non-flag token after `mv` is a path.\n*/\nfunction gitMvPaths(tokens) {\n const mvIdx = tokens.indexOf(\"mv\");\n return tokens.slice(mvIdx + 1).filter((t) => !t.startsWith(\"-\"));\n}\n/**\n* True when a short-option letter appears in any cluster after `sub`, or one of\n* `longFlags` appears verbatim. Neither `git rm` nor `git clean` has a short\n* flag taking an attached value, so a plain character scan is exact for them;\n* `git clean -e<pattern>` does, and a pattern containing the letter only ever\n* produces a false positive, which leaves the caller on the deny side.\n*/\nfunction hasShortLetterOrLongFlag(tokens, sub, letter, longFlags) {\n for (const t of tokens.slice(tokens.indexOf(sub) + 1)) {\n if (!t.startsWith(\"-\")) continue;\n if (t.startsWith(\"--\")) {\n if (longFlags.includes(t)) return true;\n continue;\n }\n if (t.slice(1).includes(letter)) return true;\n }\n return false;\n}\n/**\n* `git mv` and `git rm` are not auto-approvable, but they are not known-unsafe\n* either: the shell spellings they mirror already reach a prompt, and `git rm`\n* additionally refuses a file carrying uncommitted changes unless forced -- so\n* denying it while prompting for `rm` inverts the tiers.\n*\n* `git rm -r` stays denied to mirror the `rm-recursive` rule, which cannot\n* cover it: that rule matches on tokens[0] (`/^rm$/`) and never sees `git`.\n*/\nfunction isGitWorktreeMoveOrRemove(tokens) {\n const sub = getGitSubcommand(tokens);\n if (sub !== \"mv\" && sub !== \"rm\") return false;\n if (hasGitPreSubcommandDangerousFlag(tokens, sub)) return false;\n if (sub === \"rm\" && hasShortLetterOrLongFlag(tokens, sub, \"r\", [\"--recursive\"])) return false;\n return true;\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). But it is the home of a **separate session opened there**: writes to a path outside this session's project directory are denied, so this session cannot edit it. If you cannot open a session there, stay in the primary checkout on this branch and check `git branch --show-current` before each commit.\";\n/**\n* True when the command creates a branch: `git checkout -b/-B` or\n* `git switch -c/-C`, including those letters carried in a short-option\n* cluster with other letters or an attached value (`-bfix-bug`, `-qc\n* feat/x`). A plain `git switch <branch>` is not creation. Long spellings\n* (`--create`, `--force-create`) are deliberately not recognized as creation\n* here -- only the short grammar is in scope.\n*/\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}\n/** Per-kind creation letter: `C`/`c` on switch, `B`/`b` on checkout and `worktree add`. */\nconst GIT_CREATE_LETTERS = {\n force: {\n switch: \"C\",\n checkout: \"B\"\n },\n create: {\n switch: \"c\",\n checkout: \"b\"\n }\n};\n/**\n* The long creation spelling per kind, switch only, with the shortest token the\n* resolver accepts. Force matches every unambiguous prefix down to `--force-`\n* (the shortest that is not the distinct `--force` option) because a prefix the\n* force resolver missed would soften its deny to a prompt; non-force matches\n* `--create` exactly, since an unrecognized abbreviation already reaches\n* passthrough through isGitUnrecognizedBranchFlag -- the same tier this kind\n* routes to -- so prefix handling would change no outcome.\n*/\nconst GIT_CREATE_LONG_FLAGS = {\n force: {\n flag: \"--force-create\",\n shortest: \"--force-\"\n },\n create: {\n flag: \"--create\",\n shortest: \"--create\"\n }\n};\n/**\n* Every branch name a creation spelling of `kind` in `after` (the tokens\n* following the subcommand -- and past `add`, for `worktree add`) targets.\n* Usually zero or one, but git's last-occurrence-wins on a repeated creation\n* flag is not modeled: every occurrence contributes its target, so a repeated\n* flag (`git switch -c main -c feat/x`) is handled conservatively rather than\n* resolving which one wins.\n*\n* Grammar, reusing `splitShortOptionCluster`: a cluster whose letters include\n* the kind's letter contributes its attached value when non-empty, else the\n* next argv token; the kind's long spelling contributes the next argv token, or\n* the name attached to it with `=`. A creation flag with nothing after it (a\n* valueless trailing `-C`, or `--force-create` as the last token) resolves no\n* target for that occurrence -- git errors on the command anyway, so today's\n* routing is preserved.\n*/\nfunction gitCreateTargets(after, letterSub, kind) {\n const letter = GIT_CREATE_LETTERS[kind][letterSub];\n const longFlag = GIT_CREATE_LONG_FLAGS[kind];\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}\n/**\n* Every branch name a creation spelling of `kind` targets in a full `git ...`\n* command: `switch -C`/`--force-create` or `switch -c`/`--create`,\n* `checkout -B`/`-b`, or `worktree add -B`/`-b`. Any other subcommand --\n* including `worktree list`/`remove`/`move` -- always resolves no target.\n* `worktree add` reuses checkout's `-b`/`-B` grammar (identical required-value\n* letter), so no new letter set is needed.\n*/\nfunction gitCommandCreateTargets(tokens, kind) {\n const sub = getGitSubcommand(tokens);\n if (sub === \"switch\" || sub === \"checkout\") return gitCreateTargets(tokens.slice(tokens.indexOf(sub) + 1), sub, kind);\n if (sub === \"worktree\") {\n const worktreeIdx = tokens.indexOf(\"worktree\");\n if (tokens[worktreeIdx + 1] !== \"add\") return [];\n return gitCreateTargets(tokens.slice(worktreeIdx + 2), \"checkout\", kind);\n }\n return [];\n}\n/**\n* True when a `switch`/`checkout`/`worktree add` command force-creates a\n* branch under a name in the protected set. Force-create resets an existing\n* branch to the start point (HEAD by default) rather than merely creating\n* it, so this is a claim about the TOKENS, not the current branch -- unlike\n* isGitProtectedBranchWrite (below), this never calls getCurrentBranch and is\n* valid even outside a repository, where main/master are protected\n* unconditionally. Its non-force twin, isGitProtectedBranchCreate, resolves the\n* same positions one letter over and routes to a prompt instead of a deny.\n*/\nfunction isGitProtectedBranchForceCreate(tokens, ctx) {\n const targets = gitCommandCreateTargets(tokens, \"force\");\n if (targets.length === 0) return false;\n const protectedSet = getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []);\n return targets.some((t) => protectedSet.has(t));\n}\n/**\n* True when `git branch` destroys or overwrites a protected ref: a delete\n* (`-d`/`-D`/`--delete`) naming one, a non-force rename (`-m`/`-M`/`--move`)\n* moving one away by name, or any force spelling (`-f`/`--force`, or the\n* force letters `-M`/`-C`) writing onto one. A claim about the TOKENS, never\n* calling getCurrentBranch and valid outside a repository -- like its\n* siblings above -- but unlike `isGitProtectedBranchCreate` it carries **no**\n* pre-subcommand dangerous-flag guard: that guard exists only where a\n* predicate softens a deny into a prompt, and this one feeds a deny and\n* excludes nothing, so `git -c core.hooksPath=/tmp/evil branch -D main`\n* still denies here.\n*\n* The three checks are independent, not mutually exclusive branches of one\n* mode -- `git branch -M junk main` is a force rename whose *source* (`junk`)\n* is not protected, so the rename check alone would miss it; the force check\n* catches it via the *destination* (`main`).\n*\n* The accepted residual: `git branch -m junk` while sitting on a protected\n* branch renames it away with no victim name in the tokens at all, so this\n* returns false and the command only prompts.\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}\n/**\n* True when a `switch`/`checkout`/`worktree add` command creates a branch under\n* a protected name without the force letter. Nothing is reset -- when the ref\n* already exists git refuses -- but when it does not exist the command\n* succeeds, leaving a local `main` pointing at a feature HEAD and the session\n* parked on a protected branch. Creation is not destructive, so this is not a\n* deny; an agent never legitimately needs to create a protected name, so it is\n* not an allow either: it routes to the passthrough prompt. A claim about the\n* TOKENS exactly like isGitProtectedBranchForceCreate, so it needs no current\n* branch and is valid outside a repository.\n*\n* This one carries a guard the force twin does not: the force twin feeds a deny\n* rule, while this feeds a `git-write` exclusion, and a config/exec-injection or\n* repo-redirect flag is caught by no earlier deny rule. Without the same guard\n* isGitUnrecognizedBranchFlag uses, `git -c core.hooksPath=/tmp/evil switch -c\n* main` would soften from that deny to a prompt.\n*/\nfunction isGitProtectedBranchCreate(tokens, ctx) {\n const sub = getGitSubcommand(tokens);\n if (sub !== \"switch\" && sub !== \"checkout\" && sub !== \"worktree\") return false;\n if (hasGitPreSubcommandDangerousFlag(tokens, sub)) return false;\n const targets = gitCommandCreateTargets(tokens, \"create\");\n if (targets.length === 0) return false;\n const protectedSet = getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []);\n return targets.some((t) => protectedSet.has(t));\n}\n/**\n* True when `worktree add <path>` -- with no `-b`/`-B`/`--detach` and no\n* commit-ish positional -- would implicitly create a branch under a\n* protected name: git's own rule is \"as if `-b $(basename <path>)` was\n* specified\" whenever no such branch already exists. Nothing is reset -- when\n* the ref already exists the command merely checks it out, the auto-approved\n* analogue of `git switch <branch>` -- but when it does not exist the command\n* succeeds, leaving a local `main` at a feature HEAD and the new worktree\n* checked out onto it. Creation is not destructive, so this is not a deny;\n* an agent never legitimately needs to create a protected name, so it is not\n* an allow either: it routes to the passthrough prompt, exactly like\n* isGitProtectedBranchCreate's explicit `-b`/`-B` case. A claim about the\n* TOKENS, so it carries the same pre-subcommand dangerous-flag guard that\n* function does, needs no current branch, and is valid outside a repository.\n*/\nfunction isGitWorktreeImplicitProtectedCreate(tokens, ctx) {\n const sub = getGitSubcommand(tokens);\n if (sub !== \"worktree\") return false;\n if (hasGitPreSubcommandDangerousFlag(tokens, sub)) return false;\n const worktreeIdx = tokens.indexOf(\"worktree\");\n if (tokens[worktreeIdx + 1] !== \"add\") return false;\n if (gitCommandCreateTargets(tokens, \"create\").length > 0) return false;\n if (gitCommandCreateTargets(tokens, \"force\").length > 0) return false;\n const after = tokens.slice(worktreeIdx + 2);\n if (after.includes(\"--detach\")) return false;\n const positionals = [];\n for (let i = 0; i < after.length; i++) {\n const token = after[i];\n if (token === \"--reason\") {\n i++;\n continue;\n }\n if (token !== void 0 && !token.startsWith(\"-\")) positionals.push(token);\n }\n if (positionals.length !== 1) return false;\n const worktreePath = positionals[0];\n if (worktreePath === void 0) return false;\n const basename = path.basename(worktreePath);\n return getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []).has(basename);\n}\n/**\n* True when a global flag sits before the subcommand that enables config or\n* executable injection (GIT_DANGEROUS_FLAGS) or repoints git at an arbitrary\n* directory (GIT_REPO_REDIRECT_FLAGS). Global flags sit *before* the\n* subcommand; only that span is inspected so a positional argument that\n* happens to be spelled \"-C\" is never mistaken for the flag. Shared by\n* isGitSafeWriteOp and isGitBareCheckout -- mirrors isGitFeatureBranchWrite's\n* and isGitSafePush's identical scans (same constants, same rationale).\n* Without this, `git -c core.hooksPath=/tmp/evil add file.txt` or\n* `git -C /etc worktree add ../wt feat/x` would auto-approve via the\n* branches below.\n*/\nfunction hasGitPreSubcommandDangerousFlag(tokens, sub) {\n const subIdx = tokens.indexOf(sub);\n for (const t of tokens.slice(1, subIdx)) {\n if (GIT_DANGEROUS_FLAGS.has(t) || GIT_REPO_REDIRECT_FLAGS.has(t)) return true;\n if (t.startsWith(\"--exec-path=\") || t.startsWith(\"--pager=\") || t.startsWith(\"--upload-pack=\") || t.startsWith(\"--receive-pack=\") || t.startsWith(\"--git-dir=\") || t.startsWith(\"--work-tree=\")) return true;\n }\n return false;\n}\n/**\n* True for a `checkout` that isn't branch creation (`-b`/`-B`, which stays\n* auto-approved via isGitSafeWriteOp -- deny rules run before allow rules\n* unconditionally, so a predicate that forgot to exclude them would start\n* denying branch creation) and carries no global config/exec-injection or\n* repo-redirect flag (those keep routing to the git-write catch-all, whose\n* message already covers them).\n*\n* `git checkout <ref>` (branch change) and `git checkout -- <path>` / `git\n* checkout <ref> -- <path>` (file restore) are not distinguishable from\n* syntax alone, so this predicate -- and the single deny rule built on it --\n* deliberately covers both shapes with one message carrying both redirects,\n* rather than two rules guessing at which one a bare `git checkout <arg>` is.\n*/\nfunction isGitBareCheckout(tokens) {\n const sub = getGitSubcommand(tokens);\n if (sub !== \"checkout\") return false;\n if (isGitBranchCreation(tokens)) return false;\n return !hasGitPreSubcommandDangerousFlag(tokens, sub);\n}\n/**\n* `git checkout -- <path>` restores files; the separator makes the argument\n* unambiguously a path rather than a branch. It discards uncommitted work, so\n* it is never auto-approved -- but `git restore -- <path>`, the spelling\n* git-bare-checkout's reason redirects to, only prompts. One risk, one tier.\n*\n* A `-f` spelling still denies: git-discard-changes is evaluated first.\n*\n* A trailing separator with nothing after it (`git checkout main --`, `git\n* checkout --`) is not a restore -- there is no pathspec to redirect to\n* `git restore`, so it keeps the bare-checkout deny.\n*/\nfunction isGitCheckoutPathRestore(tokens) {\n const sub = getGitSubcommand(tokens);\n if (sub !== \"checkout\") return false;\n if (hasGitPreSubcommandDangerousFlag(tokens, sub)) return false;\n const rest = tokens.slice(tokens.indexOf(sub) + 1);\n const separatorIndex = rest.indexOf(\"--\");\n if (separatorIndex === -1) return false;\n return rest.slice(separatorIndex + 1).some((token) => !token.startsWith(\"-\"));\n}\n/**\n* True when a `switch`/`checkout` command carries a discard-changes spelling\n* after the subcommand: a prefix of `--force` or of `--discard-changes` (`--f`\n* and `--di` and longer -- git's parse-options accepts any unambiguous\n* long-option prefix), or a short cluster whose letters (before any\n* required-value option's attached value) contain `f`.\n*\n* Both long tests read `\"--force\".startsWith(token)`, never\n* `token.startsWith(\"--force\")`. `--force-create` shares the `--force` stem\n* and is a safe, auto-approved flag, so the reversed test would start denying\n* branch creation -- deny rules run before allow rules.\n*\n* This predicate has to be exhaustive rather than merely picking the deny\n* message: isGitUnrecognizedBranchFlag routes a flag outside the safe sets to\n* a human prompt, so a discard spelling missed here would soften to a prompt\n* instead of falling through to a deny.\n*/\nfunction isGitDiscardChanges(tokens) {\n const sub = getGitSubcommand(tokens);\n if (sub !== \"switch\" && sub !== \"checkout\") return false;\n return tokens.slice(tokens.indexOf(sub) + 1).some((t) => {\n if (t.length >= 3 && \"--force\".startsWith(t)) return true;\n if (t.length >= 4 && \"--discard-changes\".startsWith(t)) return true;\n return splitShortOptionCluster(t, sub)?.letters.includes(\"f\") ?? false;\n });\n}\n/**\n* Tokens safe after `switch`, exact-match only. An allowlist rather than a\n* denylist of `-f`/`--discard-changes`: git accepts unambiguous prefixes\n* (`--disc`) and short clusters (`-fc`), so a denylist of exact spellings\n* would not hold. `-` (previous-branch shorthand) is flag-shaped and must be\n* listed explicitly, or it would fall through to the deny side.\n*/\nconst GIT_SWITCH_SAFE_FLAGS = /* @__PURE__ */ new Set([\n \"-c\",\n \"-C\",\n \"--create\",\n \"--force-create\",\n \"-d\",\n \"--detach\",\n \"-t\",\n \"--track\",\n \"--track=direct\",\n \"--track=inherit\",\n \"--no-track\",\n \"--guess\",\n \"--no-guess\",\n \"-q\",\n \"--quiet\",\n \"--progress\",\n \"--no-progress\",\n \"-\"\n]);\n/**\n* Tokens safe after `checkout`, kept separate from the switch set: `-c` is a\n* switch-only spelling and `-b`/`-B` are checkout-only, and sharing one set\n* would auto-approve each subcommand's flags on the other.\n*/\nconst GIT_CHECKOUT_SAFE_FLAGS = /* @__PURE__ */ new Set([\n \"-b\",\n \"-B\",\n \"-t\",\n \"--track\",\n \"--track=direct\",\n \"--track=inherit\",\n \"--no-track\",\n \"--guess\",\n \"--no-guess\",\n \"-q\",\n \"--quiet\",\n \"--progress\",\n \"--no-progress\"\n]);\n/**\n* True when a flag-shaped switch/checkout token is safe: it is an exact\n* member of `safe`, or the helper reads it as a cluster whose every letter,\n* spelled `-<letter>`, is a member of `safe`. Exact-set membership is always\n* tried first and is the only test a non-cluster token (`-`, every long\n* flag) ever gets, so those keep matching exactly as they do today -- the\n* cluster reading is a fallback for short clusters only.\n*/\nfunction isSafeSwitchCheckoutFlag(t, sub, safe) {\n if (safe.has(t)) return true;\n const cluster = splitShortOptionCluster(t, sub);\n return cluster !== void 0 && cluster.letters.every((l) => safe.has(`-${l}`));\n}\n/**\n* True for a `switch`, or a branch-creating `checkout`, that the safe-flag\n* allowlists reject only because a flag is unrecognized -- `--orphan`,\n* `--conflict=`, `--recurse-submodules`, an abbreviation of a safe flag.\n* Ambiguous, not known-unsafe: the allowlists enumerate what has been\n* reasoned about, and a legitimate flag nobody listed must reach a human\n* prompt rather than a hard deny (blocking something safe is the failure\n* direction this hook does not accept). Mirrors\n* isGitPushWithoutExplicitRefspec's role in the same git-write `when`.\n*\n* Three shapes are deliberately left to deny instead: a discard spelling\n* (git-discard-changes, and isGitDiscardChanges is exhaustive so that this\n* predicate cannot soften one to a prompt), a `checkout` that creates no\n* branch (git-bare-checkout, whose message carries the restore redirect), and\n* a global config/exec-injection or repo-redirect flag, which keeps routing to\n* the git-write catch-all exactly as isGitBareCheckout leaves it.\n*/\nfunction isGitUnrecognizedBranchFlag(tokens) {\n const sub = getGitSubcommand(tokens);\n if (sub !== \"switch\" && sub !== \"checkout\") return false;\n if (isGitDiscardChanges(tokens)) return false;\n if (hasGitPreSubcommandDangerousFlag(tokens, sub)) return false;\n const after = tokens.slice(tokens.indexOf(sub) + 1);\n if (sub === \"checkout\" && !isGitBranchCreation(tokens)) return false;\n const safe = sub === \"switch\" ? GIT_SWITCH_SAFE_FLAGS : GIT_CHECKOUT_SAFE_FLAGS;\n return after.some((t) => t.startsWith(\"-\") && !isSafeSwitchCheckoutFlag(t, sub, safe));\n}\n/**\n* Git write operations that are safe to auto-approve:\n* - `git add` -- staging is trivially reversible\n* - `git checkout -b/-B` -- branch creation, without a discarding flag\n* - `git switch -c/-C` -- branch creation, without a discarding flag\n* - `git switch <branch>` -- branch switching, without a discarding flag\n* (switch only handles branches, unlike checkout)\n* - `git branch <name> [<start-point>]` -- branch creation, no checkout, under a non-protected name\n* - `git stash` / `git stash push|pop|apply|show` -- reversible stash operations\n* - `git mv` -- only when every path is a `.md` file under `docs/` (plan-lifecycle moves)\n*/\nfunction isGitSafeWriteOp(tokens, ctx) {\n const sub = getGitSubcommand(tokens);\n if (!sub) return false;\n if (hasGitPreSubcommandDangerousFlag(tokens, sub)) return false;\n if (sub === \"add\") return true;\n if (sub === \"branch\") return isGitBranchSafeCreate(tokens, ctx);\n if (sub === \"mv\") {\n const GIT_MV_UNSAFE_FLAGS = /* @__PURE__ */ new Set([\n \"-C\",\n \"--git-dir\",\n \"--work-tree\",\n \"-c\",\n \"--exec-path\"\n ]);\n if (tokens.some((t) => GIT_MV_UNSAFE_FLAGS.has(t) || t.startsWith(\"--exec-path=\"))) return false;\n const paths = gitMvPaths(tokens);\n return paths.length >= 2 && paths.every((p) => isDocsMarkdownPath(p, ctx));\n }\n if (sub === \"checkout\") {\n const afterCheckout = tokens.slice(tokens.indexOf(\"checkout\") + 1);\n if (!isGitBranchCreation(tokens)) return false;\n if (!afterCheckout.every((t) => !t.startsWith(\"-\") || isSafeSwitchCheckoutFlag(t, \"checkout\", GIT_CHECKOUT_SAFE_FLAGS))) return false;\n return !isGitProtectedBranchForceCreate(tokens, ctx) && !isGitProtectedBranchCreate(tokens, ctx);\n }\n if (sub === \"switch\") {\n if (!tokens.slice(tokens.indexOf(\"switch\") + 1).every((t) => !t.startsWith(\"-\") || isSafeSwitchCheckoutFlag(t, \"switch\", GIT_SWITCH_SAFE_FLAGS))) return false;\n return !isGitProtectedBranchForceCreate(tokens, ctx) && !isGitProtectedBranchCreate(tokens, ctx);\n }\n if (sub === \"stash\") {\n const stashAction = tokens[tokens.indexOf(\"stash\") + 1];\n if (stashAction === void 0) return true;\n return STASH_SAFE_ACTIONS.has(stashAction);\n }\n if (sub === \"worktree\") {\n const worktreeAction = tokens[tokens.indexOf(\"worktree\") + 1];\n if (worktreeAction !== \"list\" && worktreeAction !== \"add\") return false;\n return !isGitProtectedBranchForceCreate(tokens, ctx) && !isGitProtectedBranchCreate(tokens, ctx) && !isGitWorktreeImplicitProtectedCreate(tokens, ctx);\n }\n return false;\n}\n/** Git subcommands eligible for the feature-branch auto-approval. */\nconst GIT_FEATURE_BRANCH_OPS = /* @__PURE__ */ new Set([\n \"commit\",\n \"merge\",\n \"rebase\",\n \"cherry-pick\",\n \"revert\",\n \"reset\"\n]);\n/** Global git flags that redirect the effective repository/work tree. */\nconst GIT_REPO_REDIRECT_FLAGS = /* @__PURE__ */ new Set([\n \"-C\",\n \"--git-dir\",\n \"--work-tree\"\n]);\n/**\n* Checks (2)-(4) of the feature-branch predicate -- subcommand membership\n* plus the three flag guards -- with the branch check (5) left out. \"Eligible\n* but for the branch\": isGitFeatureBranchWrite adds \"and the branch isn't\n* protected\", isGitProtectedBranchWrite adds \"and the branch is protected\".\n* A command this rejects (a dangerous flag, say) has no valid redirect to\n* the feature-branch path either way -- it stays on the git-write catch-all,\n* which is why both callers share this instead of each re-deriving their own\n* notion of \"would work on a feature branch\".\n*/\nfunction isGitFeatureBranchEligible(tokens) {\n const sub = getGitSubcommand(tokens);\n if (!sub || !GIT_FEATURE_BRANCH_OPS.has(sub)) return false;\n for (const t of tokens.slice(1)) {\n if (GIT_DANGEROUS_FLAGS.has(t)) return false;\n if (t === \"-o\" || t.startsWith(\"--output\")) return false;\n if (t.startsWith(\"--exec-path=\") || t.startsWith(\"--pager=\") || t.startsWith(\"--upload-pack=\") || t.startsWith(\"--receive-pack=\") || t.startsWith(\"-o=\")) return false;\n }\n for (const t of tokens.slice(1)) {\n if (GIT_REPO_REDIRECT_FLAGS.has(t)) return false;\n if (t.startsWith(\"--git-dir=\") || t.startsWith(\"--work-tree=\")) return false;\n }\n if (sub === \"rebase\") {\n for (const t of tokens.slice(1)) if (t === \"--exec\" || t === \"-x\" || t.startsWith(\"--exec=\")) return false;\n }\n return true;\n}\n/**\n* True when:\n* 1.-4. isGitFeatureBranchEligible(tokens);\n* 5. getCurrentBranch(ctx.cwd) is non-null AND not in the protected set.\n*\n* Both must hold. Any failure -> return false and let the deny rules fire.\n*/\nfunction isGitFeatureBranchWrite(tokens, ctx) {\n if (!isGitFeatureBranchEligible(tokens)) return false;\n const current = getCurrentBranch(ctx.cwd);\n if (current === null) return false;\n return !getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []).has(current);\n}\n/**\n* True when a write is eligible for the feature-branch auto-approval\n* (isGitFeatureBranchEligible) but the current branch is known and\n* protected. An unknown branch -- detached HEAD, outside a repo, git state\n* unreadable -- must NOT take this rule: \"you are on a protected branch\" is\n* a claim the hook cannot make there, so those keep falling through to the\n* git-write catch-all.\n*/\nfunction isGitProtectedBranchWrite(tokens, ctx) {\n if (!isGitFeatureBranchEligible(tokens)) return false;\n const current = getCurrentBranch(ctx.cwd);\n if (current === null) return false;\n return getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []).has(current);\n}\n/** Push flags that move refs we never auto-approve: bulk, tags, deletes. */\nconst GIT_PUSH_UNSAFE_FLAGS = /* @__PURE__ */ new Set([\n \"--mirror\",\n \"--all\",\n \"--tags\",\n \"--follow-tags\",\n \"--delete\",\n \"-d\",\n \"--prune\"\n]);\n/**\n* Push-specific flags that redirect the destination or the remote-side helper\n* program independently of the positional remote/refspec parsing below.\n* `--repo` is equivalent to (and overrides) the positional <repository>\n* argument -- `git push --repo=<url>` can push to an arbitrary host even with\n* zero positional args, bypassing the \"known remote\" check entirely.\n* `--receive-pack`/`--exec` tell git which program to invoke as the remote\n* helper; on local/dumb transports git runs that program directly, so this is\n* local code execution, not just a remote-side concern. All three are unlike\n* `-C`/`--git-dir`/`--work-tree` (GIT_REPO_REDIRECT_FLAGS) in that they are\n* push subcommand options, not global git flags, so they only need checking\n* after the subcommand -- but the checking still has to happen.\n*/\nconst GIT_PUSH_DANGEROUS_FLAGS = /* @__PURE__ */ new Set([\n \"--repo\",\n \"--receive-pack\",\n \"--exec\"\n]);\nconst GIT_PUSH_DANGEROUS_FLAG_PREFIXES = [\n \"--repo=\",\n \"--receive-pack=\",\n \"--exec=\"\n];\n/**\n* `-o`/`--push-option` send a string to the receiving hook -- but the reason\n* they must be rejected here is parsing, not the hook side effect. Unlike\n* `--repo`/`--receive-pack`/`--exec` above, which only ever appear as\n* `--flag=value`, `-o`/`--push-option` take a *space-separated* value\n* (`-o origin https://evil.example.com/x.git HEAD`). Left unrejected, that\n* value token survives the `!t.startsWith(\"-\")` positional filter below and\n* is mistaken for the remote, silently shifting the real destination into\n* the refspec position -- bypassing the \"known remote\" check in (5) entirely.\n* The `=` forms (`-o=value`, `--push-option=value`) aren't exploitable this\n* way (the URL stays a refspec and gets caught by the remote check), but are\n* rejected too rather than relying on that distinction holding up.\n*/\nconst GIT_PUSH_VALUE_FLAGS = /* @__PURE__ */ new Set([\"-o\", \"--push-option\"]);\nconst GIT_PUSH_VALUE_FLAG_PREFIXES = [\"-o=\", \"--push-option=\"];\n/**\n* The ref a refspec writes to. `src:dst` targets dst; a bare `ref` targets the\n* same name on the remote. Strips the leading \"+\" (force) -- callers reject that\n* separately -- and any `refs/heads/` prefix.\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}\n/**\n* Structural safety check shared by `isGitSafePush` and\n* `isGitPushWithoutExplicitRefspec`. Every condition must hold:\n*\n* 1. No global dangerous flag (config/exec injection) or repo-redirect flag.\n* 2. No bulk/tag/delete flag (GIT_PUSH_UNSAFE_FLAGS), no --repo /\n* --receive-pack / --exec (GIT_PUSH_DANGEROUS_FLAGS), and no -o /\n* --push-option (GIT_PUSH_VALUE_FLAGS) -- these redirect the destination,\n* the remote-side helper, or (for -o) the positional parse itself\n* independently of the remote/refspec parsing below, so they must be\n* rejected up front.\n* 3. No refspec begins with \"+\" -- a force-push the `--force` deny rule misses.\n* 4. Every refspec's *destination* is a non-protected branch, doesn't have\n* an empty source (\":branch\", the unflagged spelling of --delete/-d),\n* and isn't under refs/tags/ (the unflagged spelling of --tags). A\n* current-branch check alone would happily approve `git push origin\n* HEAD:main`.\n* 5. The remote, when named, is a configured remote -- never a URL or path, so\n* the repo cannot be pushed to an arbitrary host.\n* 6. The current branch is known and not protected.\n*\n* Deliberately does NOT require a refspec to be present -- an empty refspec\n* list (bare `git push` / `git push origin`) is structurally fine by these\n* six rules, it's just *ambiguous*: git resolves the actual destination from\n* push.default / branch.<x>.merge, config this hook does not (and, as of the\n* .git/ write deny rule, no longer needs to) trust. Callers decide what to do\n* with that ambiguity -- `isGitSafePush` requires refspecs.length > 0;\n* `isGitPushWithoutExplicitRefspec` requires the opposite, so the `git-write`\n* deny rule can route the ambiguous case to passthrough instead of a hard deny.\n*\n* Returns the parsed refspec list on success, or `null` on any failure.\n*/\nfunction gitPushStructuralRefspecs(tokens, ctx) {\n if (getGitSubcommand(tokens) !== \"push\") return null;\n const pushIdx = tokens.indexOf(\"push\");\n for (const t of tokens.slice(1, pushIdx)) {\n if (GIT_DANGEROUS_FLAGS.has(t) || GIT_REPO_REDIRECT_FLAGS.has(t)) return null;\n if (t.startsWith(\"--exec-path=\") || t.startsWith(\"--git-dir=\") || t.startsWith(\"--work-tree=\")) return null;\n }\n const after = tokens.slice(pushIdx + 1);\n if (after.some((t) => GIT_PUSH_UNSAFE_FLAGS.has(t) || GIT_PUSH_DANGEROUS_FLAGS.has(t) || GIT_PUSH_DANGEROUS_FLAG_PREFIXES.some((p) => t.startsWith(p)) || GIT_PUSH_VALUE_FLAGS.has(t) || GIT_PUSH_VALUE_FLAG_PREFIXES.some((p) => t.startsWith(p)))) return null;\n const current = getCurrentBranch(ctx.cwd);\n if (current === null) return null;\n const protectedSet = getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []);\n if (protectedSet.has(current)) return null;\n const positional = after.filter((t) => !t.startsWith(\"-\"));\n const maybeRemote = positional[0];\n const refspecs = positional.slice(1);\n if (maybeRemote !== void 0 && !getRemotes(ctx.cwd).has(maybeRemote)) return null;\n for (const spec of refspecs) {\n if (spec.startsWith(\"+\")) return null;\n if (spec.startsWith(\":\")) return null;\n const dst = refspecDestination(spec);\n if (protectedSet.has(dst)) return null;\n if (dst.startsWith(\"refs/tags/\")) return null;\n }\n return refspecs;\n}\n/**\n* True when `git push` is safe to auto-approve: structurally safe (see\n* `gitPushStructuralRefspecs`) AND carries at least one explicit refspec\n* positional argument. Without one -- bare `git push`, or `git push <remote>`\n* with no refspec -- the destination comes from push.default /\n* branch.<x>.merge, which this rule does not resolve or trust; see\n* `isGitPushWithoutExplicitRefspec` for that case.\n*\n* `--force-with-lease` is deliberately permitted: it is the alternative the\n* `git-force-push` deny rule already points users at, and it is routine after a\n* rebase. `--force`/`-f` remain denied by that rule -- even when\n* `--force-with-lease` also appears; the lease does not downgrade an\n* unconditional force (see `isGitForcePush`).\n*\n* The \"cannot push to an arbitrary host\" guarantee in condition 5 holds only\n* because writes into .git/ (which is where `remote.<name>.url` lives) are\n* denied at the file-tool and path-argument layers (`git-dir-write` in\n* file-rules.ts, the `targetsGitDir` check in `arePathArgsSafe`) -- this\n* function only ever reads `getRemotes()`, it does not defend the state\n* `getRemotes()` reads from. An agent with un-gated filesystem write access\n* outside this hook (e.g. a tool this hook doesn't mediate) is not\n* containable by any of this.\n*/\nfunction isGitSafePush(tokens, ctx) {\n const refspecs = gitPushStructuralRefspecs(tokens, ctx);\n return refspecs !== null && refspecs.length > 0;\n}\n/**\n* True when `git push` is otherwise structurally safe (see\n* `gitPushStructuralRefspecs`) but carries no explicit refspec -- the\n* ambiguous case `isGitSafePush` rejects. Used only to keep the `git-write`\n* deny rule from firing for this shape, so it falls through to passthrough\n* (normal human review) instead of a hard deny -- the push isn't known-unsafe,\n* it's merely unresolved.\n*\n* A push that fails `gitPushStructuralRefspecs` for an actual reason\n* (protected branch, dangerous flag, unconfigured remote, ...) returns false\n* here too, so the `git-write` deny still fires for those.\n*/\nfunction isGitPushWithoutExplicitRefspec(tokens, ctx) {\n const refspecs = gitPushStructuralRefspecs(tokens, ctx);\n return refspecs !== null && refspecs.length === 0;\n}\n/** `git tag` read-only flags taking no argument. */\nconst GIT_TAG_READ_FLAGS_NOARG = /* @__PURE__ */ new Set([\n \"-l\",\n \"--list\",\n \"-i\",\n \"--ignore-case\",\n \"--column\",\n \"--omit-empty\"\n]);\n/** `git tag` read-only flags taking one argument, as `--flag value` or `--flag=value`. */\nconst GIT_TAG_READ_FLAGS_VALUE = /* @__PURE__ */ new Set([\n \"--contains\",\n \"--no-contains\",\n \"--points-at\",\n \"--merged\",\n \"--no-merged\",\n \"--sort\",\n \"--format\"\n]);\n/**\n* `git branch` flags that select list mode -- a positional beside one of\n* these is a match pattern, not a branch name. Long-flag names only (no\n* leading dashes); `-l`/`-a`/`-r` are covered separately by\n* `GIT_BRANCH_LIST_SELECTOR_LETTERS`.\n*/\nconst GIT_BRANCH_LIST_SELECTOR_NAMES = /* @__PURE__ */ new Set([\n \"list\",\n \"all\",\n \"remotes\",\n \"show-current\",\n \"contains\",\n \"no-contains\",\n \"merged\",\n \"no-merged\",\n \"points-at\"\n]);\n/**\n* `git branch` flags that change listing output but never select list mode\n* on their own -- a positional beside only these is still a branch name.\n* Measured against git 2.47.3: `git branch -v main`, `git branch\n* --sort=refname zzz`, `git branch --color main` and `git branch --abbrev\n* zzz` each **create** the branch.\n*/\nconst GIT_BRANCH_LIST_MODIFIER_NAMES = /* @__PURE__ */ new Set([\n \"verbose\",\n \"ignore-case\",\n \"color\",\n \"no-color\",\n \"column\",\n \"no-column\",\n \"abbrev\",\n \"no-abbrev\",\n \"sort\",\n \"format\"\n]);\n/** Short letters `git branch` accepts inside a listing cluster (`-av`, `-vv`). */\nconst GIT_BRANCH_LIST_LETTERS = /* @__PURE__ */ new Set([\n \"a\",\n \"r\",\n \"l\",\n \"v\",\n \"i\"\n]);\n/** Of those, the ones that alone select list mode -- `a`, `r`, `l`. */\nconst GIT_BRANCH_LIST_SELECTOR_LETTERS = /* @__PURE__ */ new Set([\n \"a\",\n \"r\",\n \"l\"\n]);\n/**\n* Long `git branch` flags whose value is the next token when spelled without\n* `=`. `--abbrev`, `--color` and `--column` are deliberately absent: measured,\n* they consume nothing (`git branch --color zz` creates `zz`), so treating\n* them as value-consuming would swallow a branch name and auto-approve\n* `git branch --color main` as a listing -- the exact bug this predicate\n* closes. `-u`/`--set-upstream-to` also consume the next token, but `-u` is\n* handled by `splitShortOptionCluster`'s own branch table, not by name here.\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]);\n/** Exact `git branch` flag tokens safe on a plain branch creation. */\nconst GIT_BRANCH_CREATE_SAFE_FLAGS = /* @__PURE__ */ new Set([\n \"-q\",\n \"--quiet\",\n \"-t\",\n \"--track\",\n \"--track=direct\",\n \"--track=inherit\",\n \"--no-track\"\n]);\n/** Short letters safe inside a creation cluster (`-qt`) -- `q` and `t` only. */\nconst GIT_BRANCH_CREATE_SAFE_LETTERS = /* @__PURE__ */ new Set([\"q\", \"t\"]);\n/**\n* Split the tokens after `git branch` into its flags and positionals -- the\n* one walk `isGitBranchListing`, `isGitBranchSafeCreate` and\n* `isGitProtectedBranchRefWrite` all share. A token not starting with `-` is\n* a positional. `--` matches no allowlist a caller checks, so it drops any\n* command carrying one to a prompt, and is otherwise skipped. A `--flag`\n* token is recorded by its name (the part before `=`) and consumes the\n* following token when it has no `=` and its name is in\n* `GIT_BRANCH_VALUE_FLAG_NAMES`. A short token is decomposed with\n* `splitShortOptionCluster(t, \"branch\")`, recording its letters and\n* consuming the following token when its attached value is the empty string\n* (a trailing `-u`).\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}\n/**\n* `git branch` in a listing spelling. An allowlist, exactly like\n* `isGitTagListing`: an unrecognized flag returns false and costs a prompt,\n* never a write. Every long flag must be a selector or a modifier, and every\n* short token's letters must all be listing letters; then the command lists\n* when there are no positionals at all, or when a selector is present (a\n* long selector, or a cluster letter in `a`/`r`/`l`) -- a positional beside\n* only modifiers, or beside nothing recognized, is a branch name.\n*/\nfunction isGitBranchListing(after) {\n const { flags, positionals } = splitGitBranchArgs(after);\n let hasSelector = false;\n for (const flag of flags) {\n if (flag.letters !== void 0) {\n if (!flag.letters.every((l) => GIT_BRANCH_LIST_LETTERS.has(l))) return false;\n if (flag.letters.some((l) => GIT_BRANCH_LIST_SELECTOR_LETTERS.has(l))) hasSelector = true;\n continue;\n }\n if (flag.name === void 0) return false;\n if (GIT_BRANCH_LIST_SELECTOR_NAMES.has(flag.name)) {\n hasSelector = true;\n continue;\n }\n if (!GIT_BRANCH_LIST_MODIFIER_NAMES.has(flag.name)) return false;\n }\n return positionals.length === 0 || hasSelector;\n}\n/**\n* `git branch <name> [<start-point>]` (or `-t`/`--track`/`--no-track`/`-q`\n* added), under a name that is not protected. An allowlist for the same\n* reason `GIT_SWITCH_SAFE_FLAGS` is one: every flag token must be an exact\n* member of the small creation set, so a delete/rename/copy/force/upstream\n* flag -- whether spelled long or as a cluster letter -- falls through to\n* false rather than being auto-approved. `git branch <name>` does not move\n* HEAD, so unlike `switch -c`/`checkout -b` this is never advised into a\n* worktree (see `WORKTREE_ADVICE`, gated on `isGitBranchCreation`, which this\n* does not feed).\n*/\nfunction isGitBranchSafeCreate(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 for (const flag of flags) {\n if (flag.letters !== void 0) {\n if (!flag.letters.every((l) => GIT_BRANCH_CREATE_SAFE_LETTERS.has(l))) return false;\n continue;\n }\n if (!GIT_BRANCH_CREATE_SAFE_FLAGS.has(flag.raw)) return false;\n }\n if (positionals.length < 1 || positionals.length > 2) return false;\n return !getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []).has(positionals[0]);\n}\n/**\n* `git tag` in a listing spelling. A positional argument is a tag name (a\n* write) unless a list flag is present, in which case it is a match pattern.\n* Bare `git tag` has no positional at all and lists.\n*\n* `-n<num>` here means \"print this many annotation lines\" -- on `clean` the\n* same letter is the dry run, which is why the two subcommands do not share a\n* flag table.\n*/\nfunction isGitTagListing(after) {\n let hasListFlag = false;\n let positionals = 0;\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++;\n continue;\n }\n if (t === \"-l\" || t === \"--list\") {\n hasListFlag = true;\n continue;\n }\n if (GIT_TAG_READ_FLAGS_NOARG.has(t)) continue;\n if (/^-n\\d*$/.test(t)) continue;\n const eq = t.indexOf(\"=\");\n if (eq > 0 && GIT_TAG_READ_FLAGS_VALUE.has(t.slice(0, eq))) continue;\n if (GIT_TAG_READ_FLAGS_VALUE.has(t)) {\n i++;\n continue;\n }\n return false;\n }\n return positionals === 0 || hasListFlag;\n}\n/**\n* `git clean` in a dry run. Requires `-f` to be absent rather than deciding\n* which flag wins when both are given -- that precedence is a git-version\n* behavior this hook would otherwise have to pin.\n*/\nfunction isGitCleanDryRun(after) {\n let dryRun = false;\n for (const t of after) {\n if (!t.startsWith(\"-\")) continue;\n if (t.startsWith(\"--\")) {\n if (t === \"--force\") return false;\n if (t === \"--dry-run\") dryRun = true;\n continue;\n }\n if (t.slice(1).includes(\"f\")) return false;\n if (t.slice(1).includes(\"n\")) dryRun = true;\n }\n return dryRun;\n}\n/**\n* Read-only spellings of subcommands whose writes are correctly denied. The\n* deny keys on the verb, so without this `git tag -l`, `git clean -n` and\n* `git <sub> --help` inherit the tier of `git tag v1.2.3`.\n*\n* An allowlist, not a denylist: an unrecognized flag returns false and keeps\n* today's deny, so a spelling missed here costs a prompt rather than an\n* unintended write.\n*/\nfunction isGitReadOnlyForm(tokens) {\n const sub = getGitSubcommand(tokens);\n if (!sub) return false;\n if (hasGitPreSubcommandDangerousFlag(tokens, sub)) return false;\n const after = tokens.slice(tokens.indexOf(sub) + 1);\n if (after[0] === \"--help\") return true;\n if (sub === \"tag\") return isGitTagListing(after);\n if (sub === \"clean\") return isGitCleanDryRun(after);\n if (sub === \"branch\") return isGitBranchListing(after);\n return false;\n}\n/** pnpm built-in subcommands (not user-defined scripts) */\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]);\n/** Read-only pnpm commands that are always safe */\nconst PNPM_SAFE_BUILTINS = /* @__PURE__ */ new Set([\n \"list\",\n \"ls\",\n \"ll\",\n \"la\",\n \"why\",\n \"view\",\n \"info\",\n \"show\",\n \"outdated\"\n]);\nfunction isPnpmSafe(tokens) {\n const subTokens = tokens.slice(1);\n let subcommand;\n for (const t of subTokens) {\n if (t.startsWith(\"-\") && !PNPM_BUILTINS.has(t)) continue;\n subcommand = t;\n break;\n }\n if (!subcommand) return false;\n if (PNPM_SAFE_BUILTINS.has(subcommand)) return true;\n if (subcommand === \"install\" || subcommand === \"i\") return subTokens.slice(subTokens.indexOf(subcommand) + 1).every((t) => t.startsWith(\"-\"));\n if (subcommand === \"store\") return subTokens.slice(subTokens.indexOf(subcommand) + 1).find((t) => !t.startsWith(\"-\")) === \"status\";\n return false;\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}\n/** Map npm subcommand to a pnpm redirect message */\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}\n/**\n* Commands whose write destination is the *last* positional argument\n* (`cp src dst`, `mv src dst`, `rsync -a src/ dst/`, `install -m 755 src dst`,\n* `ln -f src dst`).\n*/\nconst LAST_POSITIONAL_DEST = /* @__PURE__ */ new Set([\n \"cp\",\n \"mv\",\n \"rsync\",\n \"install\",\n \"ln\"\n]);\n/** Commands where *every* positional is written to (`tee a b`, `truncate -s 0 f`). */\nconst ALL_POSITIONAL_DEST = /* @__PURE__ */ new Set([\"tee\", \"truncate\"]);\n/** `-t DIR` / `--target-directory[=DIR]` -- an explicit destination directory. */\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}\n/**\n* Destination paths named by shell redirects (`> f`, `>> f`, `2> f`, `>f`).\n*\n* Checked for *every* command, not just known write tools: the shell splitter\n* leaves redirect tokens inside the segment, so without this an unrecognized\n* command (`some-generator > node_modules/pkg/index.js`) would sail past a\n* command-keyed rule. Over-capture here is harmless -- a bogus \"destination\"\n* like `&1` from `2>&1` simply doesn't resolve into node_modules.\n*/\nfunction redirectDestinations(tokens) {\n const dests = [];\n for (const [i, t] of tokens.entries()) {\n if (/^(?:\\d*|&)>{1,2}$/.test(t)) {\n const next = tokens[i + 1];\n if (next !== void 0) dests.push(next);\n continue;\n }\n const attached = /^(?:\\d*|&)>{1,2}(.+)$/.exec(t);\n if (attached?.[1] !== void 0) dests.push(attached[1]);\n }\n return dests;\n}\n/**\n* Every path this command would *write to*. Sources are deliberately excluded:\n* reading out of `node_modules` (`cat node_modules/pkg/package.json`,\n* `cp node_modules/pkg/README.md docs/`) is routine and stays allowed. Only the\n* destination side can corrupt anything.\n*/\nfunction writeDestinations(tokens) {\n const cmd = path.basename(tokens[0] ?? \"\");\n const rest = tokens.slice(1);\n const dests = redirectDestinations(tokens);\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}\n/**\n* True when the command writes anything into a `node_modules/` tree.\n*\n* See `targetsNodeModules` for why this is a hard deny rather than a prompt:\n* `node_modules` is hardlinked into pnpm's global content-addressable store, so\n* an in-place overwrite silently corrupts that package for every project on the\n* machine -- and the directory doing the writing looks perfectly fine afterward.\n* The 2026-07-13 store poisoning was exactly this, and it was *human-approved*\n* at the prompt, which is why a passthrough would not have stopped it.\n*/\nfunction writesIntoNodeModules(tokens, ctx) {\n return writeDestinations(tokens).some((d) => targetsNodeModules(d, ctx.cwd));\n}\n/** Shared by the `node-modules-write` deny rule and the early check in `evaluate.ts`. */\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 arePathArgsSafe(tokens, ctx) {\n for (const t of tokens.slice(1)) {\n if (t.startsWith(\"-\")) {\n if (t === \"-t\" || t.startsWith(\"--target-directory\")) return false;\n continue;\n }\n if (path.basename(t).startsWith(\".\")) return false;\n if (t.startsWith(\"~/\") || t === \"~\") return false;\n if (targetsGitDir(t, ctx.cwd)) return false;\n if (!isWithin(ctx.cwd, resolveIn(ctx.cwd, t))) return false;\n }\n return true;\n}\nconst FIND_ALWAYS_DANGEROUS = /* @__PURE__ */ new Set([\n \"-ok\",\n \"-okdir\",\n \"-delete\",\n \"-fprint\",\n \"-fprint0\",\n \"-fprintf\",\n \"-fls\"\n]);\nconst FIND_EXEC_SAFE_COMMANDS = /* @__PURE__ */ new Set([\n \"cat\",\n \"head\",\n \"tail\",\n \"grep\",\n \"egrep\",\n \"fgrep\",\n \"ls\",\n \"wc\",\n \"file\",\n \"stat\",\n \"basename\",\n \"dirname\",\n \"realpath\",\n \"readlink\",\n \"sha256sum\",\n \"sha1sum\",\n \"md5sum\",\n \"echo\"\n]);\n/**\n* Extracts the command name from a `-exec` or `-execdir` block.\n* Returns the basename of the first token after `-exec`/`-execdir`,\n* or undefined if the block is malformed.\n*/\nfunction findExecCommand(tokens, startIdx) {\n const cmdToken = tokens[startIdx + 1];\n if (!cmdToken || cmdToken === \"{}\" || cmdToken === \";\" || cmdToken === \"+\") return void 0;\n return path.basename(cmdToken);\n}\nfunction isFindSafe(tokens) {\n for (let i = 0; i < tokens.length; i++) {\n const t = tokens[i];\n if (t === void 0) continue;\n if (FIND_ALWAYS_DANGEROUS.has(t)) return false;\n if (t === \"-exec\" || t === \"-execdir\") {\n const cmd = findExecCommand(tokens, i);\n if (!cmd || !FIND_EXEC_SAFE_COMMANDS.has(cmd)) return false;\n }\n }\n return true;\n}\nfunction isSortSafe(tokens) {\n return !tokens.includes(\"-o\") && !tokens.some((t) => t.startsWith(\"--output\"));\n}\nfunction isRmRecursive(tokens) {\n const rmIdx = tokens.findIndex((t) => path.basename(t) === \"rm\");\n if (rmIdx === -1) return false;\n const afterRm = tokens.slice(rmIdx + 1);\n const shortFlags = afterRm.filter((t) => t.startsWith(\"-\") && !t.startsWith(\"--\"));\n const longFlags = afterRm.filter((t) => t.startsWith(\"--\"));\n const hasRecursiveShort = shortFlags.some((f) => /[rR]/.test(f));\n const hasRecursiveLong = longFlags.includes(\"--recursive\");\n return hasRecursiveShort || hasRecursiveLong;\n}\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}\nfunction isGitResetHardOnMain(tokens) {\n if (!(tokens.includes(\"reset\") && tokens.includes(\"--hard\"))) return false;\n if (tokens.some((t) => t === \"main\" || t === \"master\")) return true;\n const positionalArgs = tokens.filter((t) => t !== \"git\" && t !== \"reset\" && !t.startsWith(\"-\"));\n return positionalArgs.length === 0 || positionalArgs.includes(\"HEAD\");\n}\n/** bst subcommands that are safe to auto-approve (read-only or local-only). */\nconst BST_SAFE_SUBCOMMANDS = /* @__PURE__ */ new Set([\n \"checks:run\",\n \"lint:types\",\n \"lint:report\",\n \"hooks:audit\",\n \"hooks:sync\",\n \"docker:build\",\n \"docker:check\",\n \"repo:sync\",\n \"coverage:check\",\n \"coverage:summary\",\n \"coverage:status\",\n \"health:check\",\n \"health:summary\",\n \"health:status\",\n \"ascii:check\",\n \"docs:check\",\n \"docs:delink\",\n \"docs:relocate\",\n \"docs:retire\",\n \"pr:create\",\n \"pr:update\",\n \"pr:checks\",\n \"pr:list\",\n \"commit:status\",\n \"issue:view\",\n \"issue:list\",\n \"issue:download\",\n \"findings:add\",\n \"findings:list\",\n \"findings:resolve\"\n]);\n/**\n* Flags that disqualify an otherwise-safe bst subcommand from auto-approval.\n*\n* `issue:download` is a remote read whose local write is confined to --out, sanitized\n* to a basename, and never overwrites. `--force` is exactly the power to overwrite an\n* existing local file with remote-controlled bytes, so it prompts.\n*\n* `pr:create`/`pr:update` are safe because an update PATCHes only the fields it was given and\n* refuses to drop a closing keyword the PR's body already carries. `--allow-unlink` is the one\n* flag that turns that refusal off, and what it permits -- an issue silently staying open after\n* its PR merges -- is the exact failure this whole command pair was split apart to prevent, and\n* one no PR review catches (the diff looks fine; the link is just gone). A guard an agent can\n* step over unattended is not a guard, so passing it prompts.\n*\n* Matching is deliberately conservative: any occurrence of the flag token anywhere in\n* the command (as a bare flag, `--flag=value`, after a `--` separator, or even as the\n* value of another flag) disqualifies. A flag that *might* be present is treated the\n* same as one that definitely is -- fail closed rather than try to model citty's\n* argument parsing (positional vs. flag-value) here.\n*/\nconst BST_UNSAFE_FLAGS = {\n \"issue:download\": [\"--force\"],\n \"pr:create\": [\"--allow-unlink\"],\n \"pr:update\": [\"--allow-unlink\"]\n};\nfunction hasDisqualifyingFlag(sub, tokens) {\n const flags = BST_UNSAFE_FLAGS[sub];\n if (!flags) return false;\n return tokens.some((token) => flags.some((flag) => token === flag || token.startsWith(`${flag}=`)));\n}\n/**\n* Safe under `bst-agent` only. All eight are read-only, and they only exist where the agent\n* runs, so the published CLI has no such command to classify.\n*\n* `agent:start` is deliberately absent: it spawns containers and holds two credentials, so it\n* prompts. `agent:pause` and `agent:resume` are deliberately absent too: they mutate a live\n* daemon's behavior -- halting it, or re-arming autonomous claiming and automerging an operator\n* paused on purpose -- and neither write flows through a PR where a review could catch it, so an\n* unattended agent must not be able to do either without a human answering a prompt.\n* `bst-agent` is a workspace-linked bin of a private package, the same trust shape as\n* `bst` (a name pnpm resolves, not a filesystem path an agent can manufacture), so it inherits\n* `BST_SAFE_SUBCOMMANDS` and `BST_UNSAFE_FLAGS` unchanged.\n*\n* The three `egress:*` read-only verbs join the set for the same reason the `agent:*` ones did:\n* each only ever reads the drained proxy decision logs (or, for `egress:status`, the orphan\n* history branch) and writes nothing. `egress:record` is deliberately absent -- it pushes to\n* that orphan branch, a remote write no PR review catches, so it stays a passthrough prompt like\n* `coverage:record`/`health:record`.\n*\n* `agent:token-rate` joins for the same reason `agent:review-rounds` did: it only reads the\n* forge's own comment trail and computes a distribution over it, writing nothing.\n*\n* `agent:models-check` joins too: like `agent:calibrate`, it reaches the network, but only to\n* read each configured endpoint's own health and effort words -- it touches no forge, so there is\n* still no remote write a PR review could miss.\n*/\nconst BST_AGENT_ONLY_SAFE_SUBCOMMANDS = /* @__PURE__ */ new Set([\n \"agent:calibrate\",\n \"agent:context-usage\",\n \"agent:models-check\",\n \"agent:repair-candidates\",\n \"agent:review-rounds\",\n \"agent:status\",\n \"agent:token-rate\",\n \"agent:transcript\",\n \"egress:check\",\n \"egress:status\",\n \"egress:summary\"\n]);\nfunction isBstSafe(tokens, extraSafe) {\n const sub = tokens[1];\n if (!sub) return false;\n if (!BST_SAFE_SUBCOMMANDS.has(sub) && !extraSafe?.has(sub)) return false;\n return !hasDisqualifyingFlag(sub, tokens);\n}\n/** True when `token` resolves to a `.md` file inside the project's `docs/` directory. */\nfunction isDocsMarkdownPath(token, ctx) {\n if (token.startsWith(\"~\")) return false;\n if (!token.endsWith(\".md\")) return false;\n const docsRoot = resolveIn(ctx.cwd, \"docs\");\n return isWithin(docsRoot, resolveIn(ctx.cwd, token));\n}\n/** True when a plain `mv` only moves `.md` files within `docs/`. */\nfunction isMvDocsSafe(tokens, ctx) {\n const paths = [];\n for (const t of tokens.slice(1)) {\n if (t.startsWith(\"-\")) {\n if (t === \"-t\" || t.startsWith(\"--target-directory\")) return false;\n continue;\n }\n paths.push(t);\n }\n return paths.length >= 2 && paths.every((p) => isDocsMarkdownPath(p, ctx));\n}\n/** Short sed flags that take no argument and may be bundled (e.g. `-ne`). */\nconst SED_NOARG_SHORT_CHARS = /* @__PURE__ */ new Set([\n \"n\",\n \"E\",\n \"r\",\n \"s\",\n \"u\",\n \"z\"\n]);\n/** Long sed flags that take no argument (an optional `=value` is still self-contained). */\nconst SED_KNOWN_LONG_NOARG = /* @__PURE__ */ new Set([\n \"--quiet\",\n \"--silent\",\n \"--debug\",\n \"--follow-symlinks\",\n \"--in-place\",\n \"--posix\",\n \"--regexp-extended\",\n \"--separate\",\n \"--sandbox\",\n \"--unbuffered\",\n \"--null-data\",\n \"--help\",\n \"--version\"\n]);\n/**\n* True when `sed` targets only `.md` files under `docs/`.\n*\n* Parses sed's argument grammar enough to tell the script token from file\n* tokens: when neither `-e` nor `-f` is present the first positional is the\n* script, otherwise every positional is a file. Unknown or ambiguous flags\n* make the check bail (-> passthrough) rather than guess.\n*/\nfunction isSedDocsSafe(tokens, ctx) {\n let sawScriptOption = false;\n let skipNext = false;\n const positionals = [];\n for (const t of tokens.slice(1)) {\n if (skipNext) {\n skipNext = false;\n continue;\n }\n if (t === \"-\" || !t.startsWith(\"-\")) {\n positionals.push(t);\n continue;\n }\n if (t.startsWith(\"--\")) {\n const eq = t.indexOf(\"=\");\n const base = eq === -1 ? t : t.slice(0, eq);\n if (base === \"--expression\" || base === \"--file\") {\n sawScriptOption = true;\n if (eq === -1) skipNext = true;\n continue;\n }\n if (base === \"--line-length\") {\n if (eq === -1) skipNext = true;\n continue;\n }\n if (SED_KNOWN_LONG_NOARG.has(base)) continue;\n return false;\n }\n const body = t.slice(1);\n for (let c = 0; c < body.length; c++) {\n const ch = body[c];\n if (ch === \"i\") break;\n if (ch === \"e\" || ch === \"f\") {\n sawScriptOption = true;\n if (c === body.length - 1) skipNext = true;\n break;\n }\n if (ch === \"l\") {\n if (c === body.length - 1) skipNext = true;\n break;\n }\n if (!SED_NOARG_SHORT_CHARS.has(ch)) return false;\n }\n }\n const files = sawScriptOption ? positionals : positionals.slice(1);\n return files.length > 0 && files.every((f) => isDocsMarkdownPath(f, ctx));\n}\nconst denyRules = [\n {\n name: \"rm-recursive\",\n match: /^rm$/,\n action: \"deny\",\n when: (tokens) => isRmRecursive(tokens),\n reason: \"Don't use recursive `rm`. Use more targeted deletes, or remove specific files/directories.\"\n },\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-reset-hard\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens, ctx) => isGitResetHardOnMain(tokens) && !isGitFeatureBranchWrite(tokens, ctx),\n reason: \"Don't `reset --hard` on main. Create a branch first.\"\n },\n {\n name: \"git-discard-changes\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens) => isGitDiscardChanges(tokens),\n reason: \"`-f` / `--discard-changes` throws uncommitted changes away, and nothing reports what was lost. Save them first -- `git stash push` is auto-approved -- then `git switch <branch>` and `git stash pop`. If the changes belong on the target branch, plain `git switch <branch>` carries them over. Ask the user before discarding anything.\"\n },\n {\n name: \"git-bare-checkout\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens) => isGitBareCheckout(tokens) && !isGitCheckoutPathRestore(tokens),\n reason: \"Use `git switch <branch>` to change branches -- unlike `checkout` it cannot discard uncommitted file changes, and it is auto-approved. To create a branch: `git switch -c <branch>` (or `git checkout -b <branch>`). To restore a file, name the path after a `--` separator (`git restore [--source <ref>] -- <path>`, or `git checkout [<ref>] -- <path>`); both prompt rather than being auto-approved.\"\n },\n {\n name: \"git-protected-branch-write\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens, ctx) => isGitProtectedBranchWrite(tokens, ctx),\n reason: \"Not auto-approved on a protected branch. Create a feature branch first -- `git switch -c <branch>` is auto-approved, and commit/merge/rebase/cherry-pick/revert/reset are auto-approved once you are on it. To push from there, `git push origin <branch>` with an explicit refspec.\"\n },\n {\n name: \"git-protected-branch-force-create\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens, ctx) => isGitProtectedBranchForceCreate(tokens, ctx),\n reason: \"Force-create (`switch -C`/`--force-create`, `checkout -B`, `worktree add -B`) doesn't just create a branch -- when the branch already exists it resets it to the start point (HEAD by default) and checks it out. To switch to this branch, plain `git switch <branch>` is auto-approved. To create a work branch, pick a non-protected name (`git switch -C <feature>` stays auto-approved). Resetting a protected branch to its origin state is the user's to run.\"\n },\n {\n name: \"git-protected-branch-ref-write\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens, ctx) => isGitProtectedBranchRefWrite(tokens, ctx),\n reason: \"Not auto-approved on a protected branch: `branch -d`/`-D` deleting one, `branch -f <protected> <start>` overwriting one, `branch -m`/`-M` renaming one away, or `branch -M`/`-C` renaming or copying onto one. To inspect branches, `git branch --list` is auto-approved. To create a work branch, pick a non-protected name (`git branch <feature>` stays auto-approved). Deleting, resetting or renaming a protected branch is the user's to run.\"\n },\n {\n name: \"git-write\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens, ctx) => isGitWriteOp(tokens) && !isGitSafeWriteOp(tokens, ctx) && !isGitFeatureBranchWrite(tokens, ctx) && !isGitSafePush(tokens, ctx) && !isGitPushWithoutExplicitRefspec(tokens, ctx) && !isGitUnrecognizedBranchFlag(tokens) && !isGitProtectedBranchCreate(tokens, ctx) && !isGitWorktreeMoveOrRemove(tokens) && !isGitCheckoutPathRestore(tokens) && !isGitReadOnlyForm(tokens),\n reason: \"This git write isn't auto-approved. Working-tree and history operations are auto-approved on a feature branch (`git switch -c <branch>` first); a push needs an explicit refspec from one (`git push origin <branch>`). Writing forms of `tag` and `clean` have no auto-approved form (their listing and dry-run forms do -- `git tag -l`, `git clean -n`); `stash drop`, `git rm -r`, and anything carrying a config/exec-injection or repo-redirect flag have none at all. All of these need the user to run them.\"\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: /^(cat|head|tail|less|more|grep)$/,\n action: \"deny\",\n when: (tokens) => tokensTargetEnvFile(tokens),\n reason: \"Don't read .env files. These contain secrets that should not appear in the conversation.\"\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];\nconst allowRules = [\n {\n name: \"vitest\",\n match: /^vitest$/,\n action: \"allow\"\n },\n {\n name: \"jest\",\n match: /^jest$/,\n action: \"allow\"\n },\n {\n name: \"tsc\",\n match: /^tsc$/,\n action: \"allow\"\n },\n {\n name: \"tsdown\",\n match: /^tsdown$/,\n action: \"allow\"\n },\n {\n name: \"bst\",\n match: /^bst$/,\n action: \"allow\",\n when: (tokens) => isBstSafe(tokens)\n },\n {\n name: \"bst-agent\",\n match: /^bst-agent$/,\n action: \"allow\",\n when: (tokens) => isBstSafe(tokens, BST_AGENT_ONLY_SAFE_SUBCOMMANDS)\n },\n {\n name: \"oxlint\",\n match: /^oxlint$/,\n action: \"allow\"\n },\n {\n name: \"oxfmt\",\n match: /^oxfmt$/,\n action: \"allow\"\n },\n {\n name: \"eslint\",\n match: /^eslint$/,\n action: \"allow\"\n },\n {\n name: \"prettier\",\n match: /^prettier$/,\n action: \"allow\"\n },\n {\n name: \"biome\",\n match: /^biome$/,\n action: \"allow\"\n },\n {\n name: \"knip\",\n match: /^knip$/,\n action: \"allow\"\n },\n {\n name: \"cat\",\n match: /^cat$/,\n action: \"allow\"\n },\n {\n name: \"head\",\n match: /^head$/,\n action: \"allow\"\n },\n {\n name: \"tail\",\n match: /^tail$/,\n action: \"allow\"\n },\n {\n name: \"grep\",\n match: /^grep$/,\n action: \"allow\"\n },\n {\n name: \"ls\",\n match: /^ls$/,\n action: \"allow\"\n },\n {\n name: \"wc\",\n match: /^wc$/,\n action: \"allow\"\n },\n {\n name: \"uniq\",\n match: /^uniq$/,\n action: \"allow\"\n },\n {\n name: \"diff\",\n match: /^diff$/,\n action: \"allow\"\n },\n {\n name: \"echo\",\n match: /^echo$/,\n action: \"allow\"\n },\n {\n name: \"pwd\",\n match: /^pwd$/,\n action: \"allow\"\n },\n {\n name: \"which\",\n match: /^which$/,\n action: \"allow\"\n },\n {\n name: \"dirname\",\n match: /^dirname$/,\n action: \"allow\"\n },\n {\n name: \"basename\",\n match: /^basename$/,\n action: \"allow\"\n },\n {\n name: \"realpath\",\n match: /^realpath$/,\n action: \"allow\"\n },\n {\n name: \"npm\",\n match: /^npm$/,\n action: \"allow\",\n when: (tokens) => isNpmReadOp(tokens)\n },\n {\n name: \"test\",\n match: /^test$/,\n action: \"allow\"\n },\n {\n name: \"bracket-test\",\n match: /^\\[$/,\n action: \"allow\"\n },\n {\n name: \"cd\",\n match: /^cd$/,\n action: \"allow\"\n },\n {\n name: \"true\",\n match: /^true$/,\n action: \"allow\"\n },\n {\n name: \"colon\",\n match: /^:$/,\n action: \"allow\"\n },\n {\n name: \"find\",\n match: /^find$/,\n action: \"allow\",\n when: (tokens) => isFindSafe(tokens)\n },\n {\n name: \"sort\",\n match: /^sort$/,\n action: \"allow\",\n when: (tokens) => isSortSafe(tokens)\n },\n {\n name: \"git\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens) => isGitReadOp(tokens)\n },\n {\n name: \"git-read-only-form\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens) => isGitReadOnlyForm(tokens)\n },\n {\n name: \"git-safe-write\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens, ctx) => isGitSafeWriteOp(tokens, ctx),\n advise: (tokens, ctx) => isGitBranchCreation(tokens) && isPrimaryWorktree(ctx.cwd) ? WORKTREE_ADVICE : void 0\n },\n {\n name: \"git-feature-branch-write\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens, ctx) => isGitFeatureBranchWrite(tokens, ctx)\n },\n {\n name: \"git-safe-push\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens, ctx) => isGitSafePush(tokens, ctx)\n },\n {\n name: \"pnpm\",\n match: /^pnpm$/,\n action: \"allow\",\n when: (tokens) => isPnpmSafe(tokens)\n },\n {\n name: \"cp\",\n match: /^cp$/,\n action: \"allow\",\n when: (tokens, ctx) => arePathArgsSafe(tokens, ctx)\n },\n {\n name: \"mkdir\",\n match: /^mkdir$/,\n action: \"allow\",\n when: (tokens, ctx) => arePathArgsSafe(tokens, ctx)\n },\n {\n name: \"touch\",\n match: /^touch$/,\n action: \"allow\",\n when: (tokens, ctx) => arePathArgsSafe(tokens, ctx)\n },\n {\n name: \"mv-docs\",\n match: /^mv$/,\n action: \"allow\",\n when: (tokens, ctx) => isMvDocsSafe(tokens, ctx)\n },\n {\n name: \"sed-docs\",\n match: /^sed$/,\n action: \"allow\",\n when: (tokens, ctx) => isSedDocsSafe(tokens, ctx)\n }\n];\n\n//#endregion\n//#region src/lib/workspace-root.ts\n/**\n* Walk up from `cwd` to the directory holding `pnpm-workspace.yaml`.\n*\n* Its own module rather than a `script-resolver.ts` export because\n* `settings.ts` needs it and `script-resolver.ts` needs `settings.ts`,\n* which is a cycle the health gate reports.\n*/\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/lib/settings.ts\nconst SETTINGS_PATH = \".claude/hooks/bst-hook.settings.json\";\nconst BstHookSettingsSchema = object({\n allow: array(string().min(1)).default([]),\n protectedBranches: array(string().min(1)).default([])\n});\n/**\n* Check whether a file at `relativePath` inside `cwd` is tracked and\n* unmodified in git (no staged or unstaged changes vs HEAD).\n*\n* Returns false on any error -- safe default is to not trust the file.\n*/\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}\n/**\n* Load repo-level settings from `.claude/hooks/bst-hook.settings.json`.\n*\n* Walks up from `cwd` to find the workspace root (or uses `cwd` directly),\n* reads the settings file, validates it, and checks that the file is\n* git-tracked and unmodified before trusting it.\n*\n* Returns `{ allow: [], protectedBranches: [] }` if the file is missing,\n* invalid, or untrusted.\n*/\nfunction loadRepoSettings(cwd) {\n const root = findWorkspaceRoot(cwd) ?? cwd;\n const settingsFile = join(root, SETTINGS_PATH);\n const empty = {\n allow: [],\n protectedBranches: []\n };\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 {\n allow: result.data.allow,\n protectedBranches: result.data.protectedBranches\n };\n } catch {\n return empty;\n }\n}\n/**\n* Check whether a command matches a repo-level allow pattern.\n*\n* Pattern format: `\"<command> [arg1] [arg2] ...\"`\n* - First token is matched exactly against the command basename\n* - Remaining tokens (if any) must match positionally as prefixes of the actual arguments\n*\n* Examples:\n* - `\"tooling\"` matches `tooling repo:sync --yes`\n* - `\"node packages/foo/dist/bin.mjs\"` matches `node packages/foo/dist/bin.mjs run`\n* - `\"node packages/foo/dist/bin.mjs\"` does NOT match `node packages/bar/dist/bin.mjs`\n*/\nfunction matchesRepoPattern(cmdName, tokens, pattern) {\n const parts = pattern.split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return false;\n if (parts[0] !== cmdName) return false;\n const patternArgs = parts.slice(1);\n const actualArgs = tokens.slice(1);\n for (let i = 0; i < patternArgs.length; i++) {\n if (i >= actualArgs.length) return false;\n if (actualArgs[i] !== patternArgs[i]) return false;\n }\n return true;\n}\n\n//#endregion\n//#region src/lib/script-resolver.ts\nconst PackageJsonSchema = object({ scripts: record(string(), unknown()) });\n/**\n* Resolve a pnpm script name to its actual command(s) by reading package.json.\n*\n* Returns null if the script cannot be resolved (missing package.json,\n* missing script name, parse error).\n*/\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}\n/**\n* Resolve a script in a specific workspace package identified by --filter.\n*\n* Reads pnpm-workspace.yaml, scans workspace directories to match filterPkg\n* against package.json#name fields, then resolves the script in that package's directory.\n*\n* Returns null if the package or script cannot be resolved.\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}\n/**\n* Resolve a script name across all workspace packages (for `pnpm -r <script>`).\n*\n* Returns an array of resolved scripts (one per workspace package that defines the script),\n* each with a `pkgDir` field. Returns null if workspace patterns can't be read.\n* Packages that don't define the script are silently skipped.\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}\n/**\n* List all workspace package directories.\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}\n/**\n* Find the directory of a workspace package by name.\n* Reads pnpm-workspace.yaml for glob patterns, then scans matching directories.\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}\n/**\n* Read workspace glob patterns from pnpm-workspace.yaml.\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() });\n/**\n* Read the name field from package.json in the given directory.\n*/\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}\n/**\n* Read the scripts field from package.json in the given directory.\n* Returns null on any failure (missing file, parse error, no scripts field).\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* Check whether the package.json in the given directory is clean in git\n* (no staged or unstaged changes compared to HEAD).\n*\n* Returns true only when the file is tracked and unmodified. Returns false\n* on any error (not a git repo, untracked file, no HEAD, etc.) -- safe default\n* is to not trust the scripts.\n*/\nfunction isPackageJsonClean(pkgDir) {\n return isFileClean(pkgDir, \"package.json\");\n}\n\n//#endregion\n//#region src/evaluate.ts\nconst MAX_EVAL_DEPTH = 5;\n/** Environment variables that enable code injection */\nconst DANGEROUS_ENV_VARS = /* @__PURE__ */ new Set([\n \"LD_PRELOAD\",\n \"LD_LIBRARY_PATH\",\n \"DYLD_INSERT_LIBRARIES\",\n \"NODE_OPTIONS\",\n \"PYTHONPATH\",\n \"PYTHONSTARTUP\",\n \"PERL5OPT\",\n \"PERL5LIB\",\n \"RUBYOPT\",\n \"RUBYLIB\",\n \"BASH_ENV\",\n \"ENV\",\n \"npm_config_prefix\",\n \"npm_config_registry\",\n \"EDITOR\",\n \"VISUAL\"\n]);\n/** Git config env vars use prefix matching */\nconst DANGEROUS_GIT_ENV_PREFIXES = [\n \"GIT_CONFIG_\",\n \"GIT_PAGER\",\n \"GIT_SSH_COMMAND\",\n \"GIT_EDITOR\",\n \"GIT_EXEC_PATH\"\n];\nfunction isDangerousEnvVar(name) {\n if (DANGEROUS_ENV_VARS.has(name)) return true;\n return DANGEROUS_GIT_ENV_PREFIXES.some((prefix) => name.startsWith(prefix));\n}\n/**\n* Extract the target directory from a `cd <dir>` segment.\n* Returns the resolved absolute path, or null if the segment isn't a simple `cd`.\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}\n/**\n* Evaluate a full command string. Splits on operators and evaluates each segment.\n*\n* This is the single reentrant entry point. Called at depth 0 for top-level\n* commands and at depth 1+ for derived commands (resolved scripts, exec tails, etc.).\n*/\nfunction evaluateCommand(command, ctx) {\n if (ctx.depth >= MAX_EVAL_DEPTH) return {\n action: \"passthrough\",\n rule: null,\n reason: \"max evaluation depth exceeded\"\n };\n const trimmed = command.trim();\n if (!trimmed) return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n if (trimmed.startsWith(\"(\") && trimmed.endsWith(\")\")) return {\n action: \"passthrough\",\n rule: null,\n reason: \"subshell\"\n };\n const segments = splitCommand(trimmed);\n if (segments.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n let firstPassthrough = null;\n let lastAllow = null;\n const currentCtx = { ...ctx };\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 (result.action === \"passthrough\") {\n if (!firstPassthrough) firstPassthrough = result;\n }\n if (result.action === \"allow\") lastAllow = result;\n }\n if (firstPassthrough) return firstPassthrough;\n if (segments.length === 1 && lastAllow) return lastAllow;\n return {\n action: \"allow\",\n rule: \"all-segments\",\n reason: null\n };\n}\n/**\n* Helper to evaluate a resolved script with its pre and post hooks.\n*\n* When the source package.json is clean in git, sets `insideResolvedScript`\n* on the context so downstream evaluation trusts file redirects and\n* unmatched commands (user-authored scripts). Deny rules still apply.\n*/\nfunction evaluateResolvedScript(resolved, ctx) {\n const pkgDir = resolved.pkgDir ?? ctx.cwd;\n const innerCtx = ctx.insideResolvedScript ?? isPackageJsonClean(pkgDir) ? {\n ...ctx,\n insideResolvedScript: true\n } : ctx;\n if (resolved.pre) {\n const preResult = evaluateCommand(resolved.pre, innerCtx);\n if (preResult.action !== \"allow\") return preResult;\n }\n const mainResult = evaluateCommand(resolved.main, innerCtx);\n if (mainResult.action !== \"allow\") return mainResult;\n if (resolved.post) {\n const postResult = evaluateCommand(resolved.post, innerCtx);\n if (postResult.action !== \"allow\") return postResult;\n }\n return \"allow\";\n}\n/**\n* Evaluate a single command segment (no operators).\n* May recurse into evaluateCommand() for indirection constructs.\n*/\nfunction evaluateSegment(rawSegment, ctx) {\n const trimmed = rawSegment.trim();\n if (trimmed.startsWith(\"(\") && trimmed.endsWith(\")\")) return {\n action: \"passthrough\",\n rule: null,\n reason: \"subshell\"\n };\n const tokenResult = tokenize(rawSegment);\n if (!tokenResult.ok) return {\n action: \"passthrough\",\n rule: null,\n reason: tokenResult.error\n };\n let tokens = tokenResult.tokens;\n if (tokens.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n let envSkipped = 0;\n while (envSkipped < tokens.length && /^\\w+=/.test(tokens[envSkipped])) {\n const varName = tokens[envSkipped].split(\"=\")[0];\n if (isDangerousEnvVar(varName)) return {\n action: \"passthrough\",\n rule: null,\n reason: `dangerous env var: ${varName}`\n };\n envSkipped++;\n }\n if (envSkipped >= tokens.length) return {\n action: \"allow\",\n rule: \"env-only\",\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, ctx.insideResolvedScript);\n if (!injection.safe) return {\n action: \"passthrough\",\n rule: null,\n reason: injection.reason\n };\n if (!ctx.insideResolvedScript) for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i];\n if (token === \"2>&1\" || token === \"2>/dev/null\" || token === \">/dev/null\" || token === \"1>/dev/null\") continue;\n if (token === \">\" || token === \">>\") {\n if (tokens[i + 1] === \"/dev/null\") continue;\n return {\n action: \"passthrough\",\n rule: null,\n reason: \"output redirect to file\"\n };\n }\n if (token === \"<\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"input redirect\"\n };\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])) {\n const varName = tokens[envIdx].split(\"=\")[0];\n if (isDangerousEnvVar(varName)) return {\n action: \"passthrough\",\n rule: null,\n reason: `dangerous env var: ${varName}`\n };\n envIdx++;\n }\n if (envIdx >= tokens.length) return {\n action: \"allow\",\n rule: \"env-only\",\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 === \"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: \"passthrough\",\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 for (const rule of allowRules) if (rule.match.test(cmdName)) {\n if (!rule.when || rule.when(tokens, ctx)) {\n const advice = rule.advise?.(tokens, ctx);\n return {\n action: \"allow\",\n rule: rule.name,\n reason: null,\n ...advice ? { advice } : {}\n };\n }\n }\n if (ctx.repoAllowPatterns) {\n for (const pattern of ctx.repoAllowPatterns) if (matchesRepoPattern(cmdName, tokens, pattern)) return {\n action: \"allow\",\n rule: `repo:${cmdName}`,\n reason: null\n };\n }\n if (ctx.insideResolvedScript) return {\n action: \"allow\",\n rule: \"resolved-script\",\n reason: null\n };\n return {\n action: \"passthrough\",\n rule: null,\n reason: `no rule matched: ${cmdName}`\n };\n}\n/**\n* Scan pnpm subTokens starting at `from`, skipping and accumulating flags\n* (--filter / -F, -r, -w, -C, and generic --flags). Returns the first non-flag\n* token as `name`, or `undefined` if none found. Returns `\"filter\"` as a\n* sentinel when --filter is encountered (caller must handle).\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}\n/**\n* Evaluate a pnpm command. Classifies the subcommand as built-in, exec, or script.\n* Script commands are resolved via package.json and recursively evaluated.\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: \"passthrough\",\n rule: null,\n reason: null\n };\n if (subcommand === \"dlx\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm dlx\"\n };\n if (subcommand === \"add\") return {\n action: \"passthrough\",\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: \"passthrough\",\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: \"passthrough\",\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 {\n for (const rule of allowRules) if (rule.match.test(\"pnpm\")) {\n if (!rule.when || rule.when(tokens, ctx)) return {\n action: \"allow\",\n rule: rule.name,\n reason: null\n };\n }\n return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n }\n if (isRecursive) return evaluatePnpmRecursive(scriptName, ctx);\n if (isWorkspaceRoot) {\n const root = findWorkspaceRoot(ctx.cwd);\n if (!root) return {\n action: \"passthrough\",\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}\n/**\n* Resolve a pnpm script name via package.json and recursively evaluate\n* the resolved command(s) including pre/post scripts.\n*/\nfunction evaluatePnpmScript(scriptName, ctx) {\n const resolved = resolveScript(scriptName, ctx.cwd);\n if (!resolved) return {\n action: \"passthrough\",\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 !== \"allow\") return res;\n return {\n action: \"allow\",\n rule: `pnpm-script:${scriptName}`,\n reason: null\n };\n}\n/**\n* Evaluate `pnpm -r <script>` by resolving the script in each workspace package.\n*\n* Unlike `evaluatePnpmScript` (which resolves from the root package.json and would\n* cycle back to `pnpm -r <script>`), this resolves against each workspace package's\n* own package.json, breaking the recursion.\n*/\nfunction evaluatePnpmRecursive(scriptName, ctx) {\n const resolved = resolveRecursiveScripts(scriptName, ctx.cwd);\n if (!resolved || resolved.length === 0) return {\n action: \"passthrough\",\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 !== \"allow\") return res;\n }\n return {\n action: \"allow\",\n rule: `pnpm-recursive:${scriptName}`,\n reason: null\n };\n}\n/**\n* Evaluate an npm command. Read-only ops (view/info/show) are allowed.\n* Everything else is denied with a redirect message to the pnpm equivalent.\n*/\nfunction evaluateNpm(tokens) {\n for (const rule of allowRules) if (rule.match.test(\"npm\")) {\n if (!rule.when || rule.when(tokens, {\n cwd: \"\",\n depth: 0\n })) return {\n action: \"allow\",\n rule: rule.name,\n reason: null\n };\n }\n return {\n action: \"deny\",\n rule: \"npm-redirect\",\n reason: getNpmRedirectReason(tokens)\n };\n}\n/**\n* Evaluate `pnpm --filter <pkg> [--filter <pkg> ...] <subcommand>`.\n*\n* Collects every `--filter` / `-F` flag (long, equals, and shorthand forms),\n* resolves each against pnpm-workspace.yaml, then classifies the subcommand as\n* built-in or script. Script commands are resolved and evaluated in each\n* filtered package -- all must allow.\n*\n* Falls back to passthrough if workspace resolution fails.\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: \"passthrough\",\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: \"passthrough\",\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: \"passthrough\",\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: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter without subcommand\"\n };\n if (filterSubcommand === \"install\" || filterSubcommand === \"i\") {\n if (!subTokens.slice(subcmdIdx + 1).every((t) => t.startsWith(\"-\"))) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter install with package arg\"\n };\n return {\n action: \"allow\",\n rule: \"pnpm\",\n reason: null\n };\n }\n if (filterSubcommand === \"dlx\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm dlx\"\n };\n if (filterSubcommand === \"add\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm add\"\n };\n if ((/* @__PURE__ */ new Set([\n \"list\",\n \"ls\",\n \"ll\",\n \"la\",\n \"why\",\n \"view\",\n \"info\",\n \"show\",\n \"outdated\",\n \"licenses\",\n \"audit\"\n ])).has(filterSubcommand)) return {\n action: \"allow\",\n rule: \"pnpm\",\n reason: null\n };\n if (filterSubcommand === \"exec\") {\n const execArgs = subTokens.slice(subcmdIdx + 1);\n if (execArgs.length === 0) return {\n action: \"passthrough\",\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: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter run without script name\"\n };\n if (PNPM_BUILTINS.has(scriptName) && scriptName !== \"run\") return {\n action: \"passthrough\",\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: \"passthrough\",\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 !== \"allow\") return res;\n }\n return {\n action: \"allow\",\n rule: `pnpm-filter:${filterPkgs.join(\",\")}:${scriptName}`,\n reason: null\n };\n}\n/** Flags that modify shell behavior beyond simple command execution */\nconst SHELL_UNSAFE_FLAGS = /* @__PURE__ */ new Set([\n \"-i\",\n \"--interactive\",\n \"-l\",\n \"--login\",\n \"--init-file\",\n \"--rcfile\",\n \"--norc\",\n \"--noprofile\",\n \"-s\",\n \"--stdin\"\n]);\n/**\n* Evaluate `bash -c` / `sh -c` / `zsh -c` commands.\n*\n* Only unwraps when the tokens are exactly: `<shell> -c <command-string>`.\n* Any additional flags, extra positional args, or missing -c -> passthrough.\n*/\nfunction evaluateShellC(tokens, ctx) {\n const cIdx = tokens.indexOf(\"-c\");\n if (cIdx === -1) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell without -c\"\n };\n for (let i = 1; i < tokens.length; i++) {\n if (i === cIdx) continue;\n if (i === cIdx + 1) continue;\n const t = tokens[i];\n if (t.startsWith(\"-\") || SHELL_UNSAFE_FLAGS.has(t)) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c with extra flags\"\n };\n }\n const cmdArg = tokens[cIdx + 1];\n if (!cmdArg) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c without command\"\n };\n if (tokens.length > cIdx + 2) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c with positional args\"\n };\n if (cIdx !== 1) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c with extra flags\"\n };\n return evaluateCommand(cmdArg, {\n ...ctx,\n depth: ctx.depth + 1\n });\n}\n/** xargs flags that consume the next token as an argument */\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]);\n/** xargs flags that are boolean (no argument) */\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]);\n/**\n* Evaluate `xargs <cmd>` commands.\n*\n* Skips xargs flags to find the inner command, then recursively evaluates it.\n* Passthrough if no command follows the flags or if unrecognised flags are found.\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: \"passthrough\",\n rule: null,\n reason: \"xargs with unrecognised flags\"\n };\n }\n if (i >= tokens.length) return {\n action: \"passthrough\",\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\"]);\n/**\n* The harness directs every temporary file to a per-session scratchpad under\n* the system temp directory, so denying it puts the hook at odds with the\n* prompt it ships beside.\n*\n* Matching the session id as a path segment rather than reconstructing the\n* documented full path is deliberate: the reconstruction encodes several\n* harness conventions, and when one changes it fails silently back to denying\n* every scratchpad write. The id arrives in the same payload as the path, so\n* the reach is one session's own temp tree.\n*\n* The payload schema only requires a string, so a degenerate id (empty, or a\n* couple of characters) would match as a path segment of nearly every path\n* under the temp directory and widen the allow to all of it. Real ids are\n* UUIDs; the length floor bounds the blast radius by construction rather than\n* trusting the harness to always produce one.\n*\n* `isWithin` compares lexically and does not resolve symlinks, so a symlink\n* planted inside the scratchpad pointing outside it is not caught. That\n* residual is accepted deliberately, the same as the cwd-scoped rules below --\n* the hook is a guardrail against accidents and prompt fatigue, not a sandbox.\n*/\nfunction isSessionScratch(filePath, ctx) {\n const { sessionId } = ctx;\n if (!sessionId || sessionId.length < MIN_SESSION_ID_LENGTH) return false;\n const resolved = resolveIn(ctx.cwd, filePath);\n if (!isWithin(os.tmpdir(), resolved)) return false;\n return resolved.split(path.sep).includes(sessionId);\n}\n/** Real session ids are UUIDs; see the length-floor rationale above. */\nconst MIN_SESSION_ID_LENGTH = 16;\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: \"outside-cwd-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx) => {\n const resolved = resolveIn(ctx.cwd, filePath);\n if (isWithin(ctx.cwd, resolved)) return false;\n const homeClaudeDir = path.join(os.homedir(), \".claude\");\n if (isWithin(homeClaudeDir, resolved)) return false;\n if (isSessionScratch(filePath, ctx)) return false;\n return true;\n },\n reason: \"Don't write files outside the project directory.\"\n }\n];\nconst fileAllowRules = [\n {\n name: \"session-scratchpad-read\",\n tools: READ_TOOLS,\n action: \"allow\",\n when: (filePath, ctx) => isSessionScratch(filePath, ctx)\n },\n {\n name: \"session-scratchpad-write\",\n tools: WRITE_TOOLS,\n action: \"allow\",\n when: (filePath, ctx) => isSessionScratch(filePath, ctx)\n },\n {\n name: \"cwd-read\",\n tools: READ_TOOLS,\n action: \"allow\",\n when: (filePath, ctx) => isWithin(ctx.cwd, resolveIn(ctx.cwd, filePath))\n },\n {\n name: \"cwd-write\",\n tools: WRITE_TOOLS,\n action: \"allow\",\n when: (filePath, ctx) => isWithin(ctx.cwd, resolveIn(ctx.cwd, filePath))\n }\n];\n\n//#endregion\n//#region src/evaluate-tool.ts\n/** Tools whose input contains a `file_path` field. */\nconst FILE_PATH_TOOLS = /* @__PURE__ */ new Set([\n \"Read\",\n \"Write\",\n \"Edit\"\n]);\n/** Tools whose input contains a `path` field (search root, optional). */\nconst SEARCH_TOOLS = /* @__PURE__ */ new Set([\"Glob\", \"Grep\"]);\n/**\n* Extract the relevant file path from tool input based on tool name.\n* Returns null if the input shape doesn't match expectations.\n*/\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}\n/**\n* Evaluate a non-Bash tool invocation against file-tool rules.\n* Returns allow, deny, or passthrough.\n*/\nfunction evaluateToolUse(toolName, toolInput, ctx) {\n const filePath = extractPath(toolName, toolInput, ctx.cwd);\n if (filePath === null) return {\n action: \"passthrough\",\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)) return {\n action: \"deny\",\n rule: rule.name,\n reason: rule.reason ?? null\n };\n for (const rule of fileAllowRules) if (rule.tools.has(toolName) && rule.when(filePath, ctx)) return {\n action: \"allow\",\n rule: rule.name,\n reason: null\n };\n return {\n action: \"passthrough\",\n rule: null,\n reason: \"no file rule matched\"\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: \"passthrough\",\n rule: null,\n reason: `validation failed: ${inputResult.error}`\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: \"passthrough\",\n rule: null,\n reason: \"Bash tool_input missing or invalid 'command' field\"\n });\n process.exit(0);\n }\n decision = evaluateCommand(bashInput.command, {\n cwd: input.cwd,\n depth: 0,\n repoAllowPatterns: repoSettings.allow,\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});\nif (decision.action === \"allow\") writeDecision(\"allow\", decision.advice);\nelse if (decision.action === \"deny\") writeDecision(\"deny\", decision.reason);\n\n//#endregion\nexport { };", "bst-hook.mjs"), writeIfChanged(ctx, GUIDE_PATH, "# bst-hook Permission Guide\n\nThis project uses a PreToolUse hook (`bst-hook`) that evaluates Bash commands\nand file tool operations before execution. Commands fall into three tiers:\n\n- **Denied** -- blocked with a redirect message\n- **Allowed** -- auto-approved, no prompt\n- **Passthrough** -- falls through to Claude Code's normal permission prompt\n\nAn allow may additionally carry a non-blocking advisory -- e.g. creating a feature branch\nin the primary worktree suggests using a `git worktree` instead. It never blocks; the\ntiers above are unchanged.\n\n## Denied commands\n\nThese are blocked. The hook tells you what to do instead.\n\n### Bash\n\n- **rm-recursive**: Don't use recursive `rm`. Use more targeted deletes, or remove specific files/directories.\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-reset-hard**: Don't `reset --hard` on main. Create a branch first.\n- **git-discard-changes**: `-f` / `--discard-changes` throws uncommitted changes away, and nothing reports what was lost. Save them first -- `git stash push` is auto-approved -- then `git switch <branch>` and `git stash pop`. If the changes belong on the target branch, plain `git switch <branch>` carries them over. Ask the user before discarding anything.\n- **git-bare-checkout**: Use `git switch <branch>` to change branches -- unlike `checkout` it cannot discard uncommitted file changes, and it is auto-approved. To create a branch: `git switch -c <branch>` (or `git checkout -b <branch>`). To restore a file, name the path after a `--` separator (`git restore [--source <ref>] -- <path>`, or `git checkout [<ref>] -- <path>`); both prompt rather than being auto-approved.\n- **git-protected-branch-write**: Not auto-approved on a protected branch. Create a feature branch first -- `git switch -c <branch>` is auto-approved, and commit/merge/rebase/cherry-pick/revert/reset are auto-approved once you are on it. To push from there, `git push origin <branch>` with an explicit refspec.\n- **git-protected-branch-force-create**: Force-create (`switch -C`/`--force-create`, `checkout -B`, `worktree add -B`) doesn't just create a branch -- when the branch already exists it resets it to the start point (HEAD by default) and checks it out. To switch to this branch, plain `git switch <branch>` is auto-approved. To create a work branch, pick a non-protected name (`git switch -C <feature>` stays auto-approved). Resetting a protected branch to its origin state is the user's to run.\n- **git-protected-branch-ref-write**: Not auto-approved on a protected branch: `branch -d`/`-D` deleting one, `branch -f <protected> <start>` overwriting one, `branch -m`/`-M` renaming one away, or `branch -M`/`-C` renaming or copying onto one. To inspect branches, `git branch --list` is auto-approved. To create a work branch, pick a non-protected name (`git branch <feature>` stays auto-approved). Deleting, resetting or renaming a protected branch is the user's to run.\n- **git-write**: This git write isn't auto-approved. Working-tree and history operations are auto-approved on a feature branch (`git switch -c <branch>` first); a push needs an explicit refspec from one (`git push origin <branch>`). Writing forms of `tag` and `clean` have no auto-approved form (their listing and dry-run forms do -- `git tag -l`, `git clean -n`); `stash drop`, `git rm -r`, and anything carrying a config/exec-injection or repo-redirect flag have none at all. All of these need the user to run them.\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- **npx-redirect**: This project uses pnpm. Use `pnpm dlx <pkg>` or `pnpm exec <cmd>` instead of npx.\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- **outside-cwd-write**: Don't write files outside the project directory.\n\n## Auto-approved commands\n\nThese run without prompting.\n\n### Bash\n\n- **Test runners**: vitest, jest\n- **Type checking**: tsc\n- **Build tools**: tsdown\n- **Tooling CLI**: bst (checks:run, hooks:audit, docker:build, docker:check, repo:sync), bst-agent (the agent package's bin: every safe bst subcommand, plus agent:repair-candidates, agent:review-rounds, agent:status, agent:token-rate and agent:transcript. agent:start, agent:pause and agent:resume prompt)\n- **Linters & formatters**: oxlint, oxfmt, eslint, prettier, biome, knip\n- **Read-only commands**: cat, head, tail, grep, ls, wc, uniq, diff, echo, pwd, which, dirname, basename, realpath\n- **Package managers (read-only)**: npm (view, info, show only)\n- **Shell builtins**: test, bracket-test\n- **Navigation**: cd, true, colon\n- **Conditional commands**: find (without -exec, -delete), sort (without -o / --output)\n- **Git read operations**: git (status, log, diff, show, blame, remote, fetch, rev-parse, etc.)\n- **Git read-only forms of write verbs**: git-read-only-form (`--help` immediately after any subcommand (not `-h` -- an alias still runs with it); `git tag` listing forms (bare, -l/--list, --points-at, --contains, --merged, --sort, --format); `git clean -n`/`--dry-run` without -f; `git branch` listing forms (bare, -a/-r, -l/--list, --show-current, -v, --contains/--merged/--points-at, --sort=/--format=). A positional beside a branch flag that isn't one of those selectors is a branch name, not a pattern -- `git branch -v main` creates and prompts. Writing forms of tag and clean stay denied)\n- **Git safe writes**: git-safe-write (add, `switch <branch>` (existing branch), switch -c/-C (new branch), checkout -b/-B (new branch), `branch <name> [<start-point>]` (new branch, no checkout, with the same protected-name rule as switch -c), stash push/pop/apply/show, mv (docs/*.md only), worktree list/add (remove/prune/move prompt -- they can delete uncommitted work). -C/-B/--force-create onto a protected branch name (main/master/default, or repo-configured) is denied instead of created, since it resets an existing branch rather than merely creating one; -c/-b/--create onto one prompts instead of auto-approving, since with no local ref of that name it succeeds and parks the session on a protected branch (`switch <branch>` stays auto-approved). switch/checkout otherwise auto-approve only with recognized non-discarding flags: -f/--discard-changes discards uncommitted work and is denied, any other flag prompts)\n- **Git writes (feature branches)**: git-feature-branch-write (commit, merge, rebase, cherry-pick, revert, reset (only when current branch is not main/master/default; push is never auto-approved))\n- **Git push (feature branches)**: git-safe-push (push with an explicit refspec (e.g. `git push origin feat/x`) from a non-protected branch to a non-protected destination on a configured remote (--force-with-lease ok; --force, --tags, --delete, --mirror, and pushes to main are not). A push with no refspec (bare `git push` / `git push <remote>`) is never auto-approved -- its destination comes from push.default/upstream config, which is not resolved here)\n- **pnpm (safe built-ins)**: pnpm (list, why, view, info, show, outdated, install (bare only))\n- **File operations (within project)**: cp (paths must be within project directory), mkdir (paths must be within project directory), touch (paths must be within project directory), mv-docs (mv only between docs/*.md paths (plan-lifecycle moves)), sed-docs (sed only on docs/*.md files (plan-lifecycle edits))\n\n### File tools\n\n- **Read/Glob/Grep**: the per-session scratchpad under the system temp directory\n- **Write/Edit**: the per-session scratchpad under the system temp directory\n- **Read/Glob/Grep**: Allowed within project directory\n- **Write/Edit**: Allowed within project directory\n\n## Passthrough\n\nEverything not listed above triggers Claude Code's normal permission prompt.\nCommon examples:\n\n- `pnpm add <pkg>`, `pnpm dlx <pkg>` (dependency changes)\n- `mv`, `git mv` (file moves outside `docs/*.md`)\n- `git rm` (without `-r`)\n- `node`, `npx` scripts\n- Commands with output redirection (`> file`)\n- Any unrecognized command\n\n## How commands are evaluated\n\n- **Pipelines and chains**: every segment must be allowed; any deny 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- **Audit log**: all decisions are logged to `.claude/hooks/bst-hook.audit.local.jsonl`.\n", "bst-hook-guide.md")];
8906
+ return [writeIfChanged(ctx, HOOK_PATH, "/**\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\";\nimport * as os from \"node:os\";\n\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/util.js\nfunction getEnumValues(entries) {\n const numericValues = Object.values(entries).filter((v) => typeof v === \"number\");\n return Object.entries(entries).filter(([k, _]) => numericValues.indexOf(+k) === -1).map(([_, v]) => v);\n}\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}\n/**\n* Whichever object a def's `shape` currently answers from: the one the caller passed until the first read, the frozen copy after it.\n*\n* Its keys and descriptors read without invoking anything, which is what lets a discriminated union check its discriminator, and the cycle walk read a shape, without resolving a getter that references the schema being constructed. A def that answers `shape` from an accessor of its own has none.\n*/\nfunction rawShape(def) {\n const desc = Object.getOwnPropertyDescriptor(def, \"shape\");\n return desc?.get ? desc.get.raw : desc?.value;\n}\nfunction sourceShape(schema) {\n return rawShape(schema._zod.def) ?? schema._zod.def.shape;\n}\nfunction deferProp(target, key, getter) {\n Object.defineProperty(target, key, {\n get() {\n const value = getter();\n assignProp(this, key, value);\n return value;\n },\n enumerable: true,\n configurable: true\n });\n}\nfunction putProp(target, key, value) {\n if (key in target) assignProp(target, key, value);\n else target[key] = value;\n}\n/**\n* Copies `keys` of `source`'s shape onto `target`, each value passed through `wrap`.\n*\n* A key the source has resolved is copied through now, so the derived shape states it outright and nothing has to resolve it to learn what it holds. A key the source still defers stays deferred, and reads back through the source's own `shape`, so it resolves once and both shapes get that one schema.\n*/\nfunction mirrorShape(target, source, keys, wrap) {\n const raw = sourceShape(source);\n for (const key of keys) {\n const desc = Object.getOwnPropertyDescriptor(raw, key);\n if (!desc.enumerable) continue;\n if (desc.get) deferProp(target, key, () => {\n const value = source._zod.def.shape[key];\n return wrap ? wrap(value, key) : value;\n });\n else putProp(target, key, wrap ? wrap(desc.value, key) : desc.value);\n }\n}\nfunction mirrorProps(target, source) {\n for (const key of Reflect.ownKeys(source)) {\n const desc = Object.getOwnPropertyDescriptor(source, key);\n if (!desc.enumerable) continue;\n if (desc.get) deferProp(target, key, () => source[key]);\n else putProp(target, key, desc.value);\n }\n}\nfunction mergeDefs(...defs) {\n const mergedDescriptors = {};\n for (const def of defs) {\n const descriptors = Object.getOwnPropertyDescriptors(def);\n Object.assign(mergedDescriptors, descriptors);\n }\n return Object.defineProperties({}, mergedDescriptors);\n}\nfunction esc(str) {\n return JSON.stringify(str);\n}\nfunction slugify(input) {\n return input.toLowerCase().trim().replace(/[^\\w\\s-]/g, \"\").replace(/[\\s_-]+/g, \"-\").replace(/^-+|-+$/g, \"\");\n}\nconst captureStackTrace = \"captureStackTrace\" in Error ? Error.captureStackTrace : (..._args) => {};\nfunction isObject(data) {\n return typeof data === \"object\" && data !== null && !Array.isArray(data);\n}\nconst allowsEval = /* @__PURE__*/ cached(() => {\n if (globalConfig.jitless) return false;\n if (typeof navigator !== \"undefined\" && navigator?.userAgent?.includes(\"Cloudflare\")) return false;\n try {\n new Function(\"\");\n return true;\n } catch (_) {\n return false;\n }\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}\nconst propertyKeyTypes = /* @__PURE__*/ new Set([\n \"string\",\n \"number\",\n \"symbol\"\n]);\nfunction escapeRegex(str) {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\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}\nconst NUMBER_FORMAT_RANGES = /*@__PURE__*/ (() => ({\n safeint: [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],\n int32: [-2147483648, 2147483647],\n uint32: [0, 4294967295],\n float32: [-34028234663852886e22, 34028234663852886e22],\n float64: [-Number.MAX_VALUE, Number.MAX_VALUE]\n}))();\nconst BIGINT_FORMAT_RANGES = {\n int64: [/* @__PURE__*/ BigInt(\"-9223372036854775808\"), /* @__PURE__*/ BigInt(\"9223372036854775807\")],\n uint64: [/* @__PURE__*/ BigInt(0), /* @__PURE__*/ BigInt(\"18446744073709551615\")]\n};\nfunction pick(schema, mask) {\n const currDef = schema._zod.def;\n const checks = currDef.checks;\n if (checks && checks.length > 0) throw new Error(\".pick() cannot be used on object schemas containing refinements\");\n const newShape = {};\n mirrorShape(newShape, schema, maskedKeys(schema, mask));\n return clone(schema, mergeDefs(currDef, {\n shape: newShape,\n checks: []\n }));\n}\nfunction maskedKeys(schema, mask) {\n const raw = sourceShape(schema);\n const keys = [];\n for (const key of Reflect.ownKeys(mask)) {\n if (!Object.getOwnPropertyDescriptor(raw, key)?.enumerable) throw new Error(`Unrecognized key: \"${String(key)}\"`);\n if (mask[key]) keys.push(key);\n }\n return keys;\n}\nfunction omit(schema, mask) {\n const currDef = schema._zod.def;\n const checks = currDef.checks;\n if (checks && checks.length > 0) throw new Error(\".omit() cannot be used on object schemas containing refinements\");\n const omitted = new Set(maskedKeys(schema, mask));\n const newShape = {};\n mirrorShape(newShape, schema, Reflect.ownKeys(sourceShape(schema)).filter((key) => !omitted.has(key)));\n return clone(schema, mergeDefs(currDef, {\n shape: newShape,\n checks: []\n }));\n}\nfunction extend(schema, shape) {\n if (!isPlainObject(shape)) throw new Error(\"Invalid input to extend: expected a plain object\");\n const checks = schema._zod.def.checks;\n if (checks && checks.length > 0) {\n const existingShape = sourceShape(schema);\n for (const key of Reflect.ownKeys(shape)) if (Object.getOwnPropertyDescriptor(existingShape, key) !== void 0) throw new Error(\"Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.\");\n }\n return clone(schema, mergeDefs(schema._zod.def, { shape: extended(schema, shape) }));\n}\nfunction extended(schema, shape) {\n const newShape = {};\n mirrorShape(newShape, schema, Reflect.ownKeys(sourceShape(schema)));\n mirrorProps(newShape, shape);\n return newShape;\n}\nfunction safeExtend(schema, shape) {\n if (!isPlainObject(shape)) throw new Error(\"Invalid input to safeExtend: expected a plain object\");\n return clone(schema, mergeDefs(schema._zod.def, { shape: extended(schema, shape) }));\n}\nfunction merge(a, b) {\n if (!b?._zod?.def) throw new Error(\"Invalid input to merge: expected an object schema. To merge a plain shape, use `.extend()`.\");\n if (a._zod.def.checks?.length) throw new Error(\".merge() cannot be used on object schemas containing refinements. Use .safeExtend() instead.\");\n const newShape = {};\n mirrorShape(newShape, a, Reflect.ownKeys(sourceShape(a)));\n mirrorShape(newShape, b, Reflect.ownKeys(sourceShape(b)));\n return clone(a, mergeDefs(a._zod.def, {\n shape: newShape,\n get catchall() {\n return b._zod.def.catchall;\n },\n checks: b._zod.def.checks ?? []\n }));\n}\nfunction partial(Class, schema, mask, name = \"partial\") {\n const checks = schema._zod.def.checks;\n if (checks && checks.length > 0) throw new Error(`.${name}() cannot be used on object schemas containing refinements`);\n const selected = mask ? new Set(maskedKeys(schema, mask)) : void 0;\n const newShape = {};\n mirrorShape(newShape, schema, Reflect.ownKeys(sourceShape(schema)), Class && ((value, key) => selected && !selected.has(key) ? value : new Class({\n type: \"optional\",\n innerType: value\n })));\n return clone(schema, mergeDefs(schema._zod.def, {\n shape: newShape,\n checks: []\n }));\n}\nfunction required(Class, schema, mask) {\n const selected = mask ? new Set(maskedKeys(schema, mask)) : void 0;\n const newShape = {};\n mirrorShape(newShape, schema, Reflect.ownKeys(sourceShape(schema)), (value, key) => selected && !selected.has(key) ? value : new Class({\n type: \"nonoptional\",\n innerType: value\n }));\n return clone(schema, mergeDefs(schema._zod.def, { shape: newShape }));\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 issue(...args) {\n const [iss, input, inst] = args;\n if (typeof iss === \"string\") return {\n message: iss,\n code: \"custom\",\n input,\n inst\n };\n return { ...iss };\n}\n/**\n* Installs a trait's members on its prototype. Each value builds that member for the instance on first read; the built value shadows the accessor as an own property, so a detached `const { parse } = schema` keeps working.\n*\n* Call this from a `proto` initializer, which runs once per prototype — never per instance.\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}\n/** Shadows a prototype member with an own value, so a getter that builds from the instance runs once. */\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}\n/** Like {@link own}, for a member that was never an own data property and has to stay out of `Object.keys`. */\nfunction hide(inst, key, value) {\n return own(inst, key, value, false);\n}\n/** Adds members a table derives from the instance: each builds on first read and shadows as own data, and assignment shadows the same way, as when these were own properties. */\nfunction derived(computes, table) {\n for (const key in computes) {\n const compute = computes[key];\n Object.defineProperty(table, key, {\n configurable: true,\n enumerable: true,\n get() {\n return own(this, key, compute(this));\n },\n set(value) {\n own(this, key, value);\n }\n });\n }\n return table;\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}\n/** Returns the prototype to install on, or `undefined` if this group is already installed on it. */\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};\n/**\n* Installs a lazily-derived internal on the `_zod` prototype of `inst`'s\n* constructor, computed from the internals object itself and cached there on\n* first read. One accessor per constructor rather than one per instance.\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}\n/**\n* Installs `key` on `inst`'s prototype, computed by `make` on first read and cached there as an own\n* data property. One accessor per constructor rather than one per instance, because an own accessor\n* puts every instance after the first into v8 dictionary mode. The key doubles as the sentinel.\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/** Marks the thunk `_catch` synthesises for a constant catch value. `Function.length` cannot tell that thunk from a user callback — rest and defaulted parameters both report arity 0 — and a user callback reads `ctx.error`, whose issues only finalize correctly against the caller's per-parse error map. Provenance can say what arity cannot. A plain string key rather than `Symbol.for`, whose call at module scope no bundler can prove pure — the same shape that anchored `urlCanParse` into every build. */\nconst CONSTANT_CATCH = \"~constantCatch\";\n/** Wraps a constant catch value in a thunk tagged with {@link CONSTANT_CATCH}. */\nfunction constantCatch(value) {\n const fn = () => value;\n fn[CONSTANT_CATCH] = true;\n return fn;\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/core.js\nvar _a$1;\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};\nvar $ZodEncodeError = class extends Error {\n constructor(name) {\n super(`Encountered unidirectional transform during encode: ${name}`);\n this.name = \"ZodEncodeError\";\n }\n};\n(_a$1 = globalThis).__zod_globalConfig ?? (_a$1.__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$1 = (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$1);\nconst $ZodRealError = $constructor(\"$ZodError\", initializer$1, void 0, { Parent: Error });\n/** Get-or-create `obj[key]` as an own data property. A path segment naming an inherited member\n* (\"toString\", \"constructor\") would otherwise read through to the prototype, and assigning\n* \"__proto__\" would hit the setter instead of creating a key. */\nfunction node(obj, key, make) {\n if (!Object.prototype.hasOwnProperty.call(obj, key)) {\n if (key === \"__proto__\") Object.defineProperty(obj, key, {\n value: make(),\n writable: true,\n enumerable: true,\n configurable: true\n });\n else obj[key] = make();\n }\n return obj[key];\n}\nfunction flattenError(error, mapper = (issue) => issue.message) {\n const fieldErrors = {};\n const formErrors = [];\n for (const sub of error.issues) if (sub.path.length > 0) node(fieldErrors, sub.path[0], () => []).push(mapper(sub));\n else formErrors.push(mapper(sub));\n return {\n formErrors,\n fieldErrors\n };\n}\nfunction formatError(error, mapper = (issue) => issue.message) {\n const fieldErrors = { _errors: [] };\n const processError = (error, path = []) => {\n for (const issue of error.issues) if (issue.code === \"invalid_union\" && issue.errors.length) issue.errors.map((issues) => processError({ issues }, [...path, ...issue.path]));\n else if (issue.code === \"invalid_key\") processError({ issues: issue.issues }, [...path, ...issue.path]);\n else if (issue.code === \"invalid_element\") processError({ issues: issue.issues }, [...path, ...issue.path]);\n else {\n const fullpath = [...path, ...issue.path];\n if (fullpath.length === 0) fieldErrors._errors.push(mapper(issue));\n else {\n let curr = fieldErrors;\n let i = 0;\n while (i < fullpath.length) {\n const el = fullpath[i];\n const terminal = i === fullpath.length - 1;\n if (el === \"_errors\") {\n if (terminal) curr._errors.push(mapper(issue));\n i++;\n continue;\n }\n if (!Object.prototype.hasOwnProperty.call(curr, el)) Object.defineProperty(curr, el, {\n value: { _errors: [] },\n enumerable: true,\n writable: true,\n configurable: true\n });\n const node = curr[el];\n if (terminal) node._errors.push(mapper(issue));\n curr = node;\n i++;\n }\n }\n }\n };\n processError(error);\n return fieldErrors;\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/parse.js\nfunction finalizeParams(callee, params) {\n return {\n callee: params?.callee ?? callee,\n Err: params?.Err\n };\n}\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 _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 _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};\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 COMPILE_INVALID = /* @__PURE__ */ Symbol.for(\"zod.compile.invalid\");\nconst COMPILE_FALLBACK = /* @__PURE__ */ Symbol.for(\"zod.compile.fallback\");\nconst validate = ((schema, value, _ctx) => {\n const validator = schema._zod.bag.validator;\n if (validator !== void 0) {\n if (validator(value) !== COMPILE_INVALID) return true;\n if (validator.definite === true && _ctx === void 0) return false;\n }\n return validateFallback(schema, value, _ctx);\n});\nfunction validateFallback(schema, value, _ctx) {\n const ctx = _ctx ? {\n ..._ctx,\n async: false,\n abortEarly: true\n } : {\n async: false,\n abortEarly: true\n };\n const fallbackRun = schema._zod.bag.fallbackRun;\n let result;\n if (fallbackRun) {\n ctx[COMPILE_FALLBACK] = true;\n result = fallbackRun({\n value,\n issues: []\n }, ctx);\n } else result = schema._zod.run({\n value,\n issues: []\n }, ctx);\n if (result instanceof Promise) throw new $ZodAsyncError();\n return result.issues.length === 0;\n}\nconst validateAsync$1 = async (schema, value, _ctx) => {\n const ctx = _ctx ? {\n ..._ctx,\n async: true,\n abortEarly: true\n } : {\n async: true,\n abortEarly: true\n };\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 === 0;\n};\nconst _encode = (_Err) => {\n const parse = _parse(_Err);\n const fn = (schema, value, _ctx, _params) => {\n const ctx = _ctx ? {\n ..._ctx,\n direction: \"backward\"\n } : { direction: \"backward\" };\n return parse(schema, value, ctx, finalizeParams(fn, _params));\n };\n return fn;\n};\nconst _decode = (_Err) => {\n const parse = _parse(_Err);\n const fn = (schema, value, _ctx, _params) => {\n return parse(schema, value, _ctx, finalizeParams(fn, _params));\n };\n return fn;\n};\nconst _encodeAsync = (_Err) => {\n const parseAsync = _parseAsync(_Err);\n const fn = async (schema, value, _ctx, _params) => {\n const ctx = _ctx ? {\n ..._ctx,\n direction: \"backward\"\n } : { direction: \"backward\" };\n return await parseAsync(schema, value, ctx, finalizeParams(fn, _params));\n };\n return fn;\n};\nconst _decodeAsync = (_Err) => {\n const parseAsync = _parseAsync(_Err);\n const fn = async (schema, value, _ctx, _params) => {\n return await parseAsync(schema, value, _ctx, finalizeParams(fn, _params));\n };\n return fn;\n};\nconst _safeEncode = (_Err) => (schema, value, _ctx) => {\n const ctx = _ctx ? {\n ..._ctx,\n direction: \"backward\"\n } : { direction: \"backward\" };\n return _safeParse(_Err)(schema, value, ctx);\n};\nconst _safeDecode = (_Err) => (schema, value, _ctx) => {\n return _safeParse(_Err)(schema, value, _ctx);\n};\nconst _safeEncodeAsync = (_Err) => async (schema, value, _ctx) => {\n const ctx = _ctx ? {\n ..._ctx,\n direction: \"backward\"\n } : { direction: \"backward\" };\n return _safeParseAsync(_Err)(schema, value, ctx);\n};\nconst _safeDecodeAsync = (_Err) => async (schema, value, _ctx) => {\n return _safeParseAsync(_Err)(schema, value, _ctx);\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/regexes.js\n/**\n* @deprecated CUID v1 is deprecated by its authors due to information leakage\n* (timestamps embedded in the id). Use {@link cuid2} instead.\n* See https://github.com/paralleldrive/cuid.\n*/\nconst cuid = /^[cC][0-9a-z]{6,}$/;\nconst cuid2 = /^[0-9a-z]+$/;\nconst ulid = /^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$/;\nconst xid = /^[0-9a-vA-V]{20}$/;\nconst ksuid = /^[A-Za-z0-9]{27}$/;\nconst nanoid = /^[a-zA-Z0-9_-]{21}$/;\nfunction nanoidOfLength(length) {\n return new RegExp(`^[a-zA-Z0-9_-]{${length}}$`);\n}\n/** ISO 8601-1 duration regex. Does not support the 8601-2 extensions like negative durations or fractional/negative components. */\nconst duration = /^P(?:(\\d+W)|(?!.*W)(?=\\d|T\\d)(\\d+Y)?(\\d+M)?(\\d+D)?(T(?=\\d)(\\d+H)?(\\d+M)?(\\d+([.,]\\d+)?S)?)?)$/;\n/** A regex for any UUID-like identifier: 8-4-4-4-12 hex pattern */\nconst guid = /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/;\n/** Returns a regex for validating an RFC 9562/4122 UUID.\n*\n* @param version Optionally specify a version 1-8. If no version is specified, all versions are supported. */\nconst uuid = (version) => {\n if (!version) return /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/;\n return new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${version}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`);\n};\n/** Practical email validation */\nconst email = /^(?:[A-Za-z0-9_'+\\-]+\\.)*[A-Za-z0-9_'+\\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\\-]*\\.)+[A-Za-z]{2,}$/;\nconst _emoji$1 = `^(?=[\\\\s\\\\S]*[\\\\p{Extended_Pictographic}\\\\p{Regional_Indicator}\\\\u20E3])[\\\\p{Extended_Pictographic}\\\\p{Emoji_Component}]+$`;\nfunction emoji() {\n return new RegExp(_emoji$1, \"u\");\n}\nconst ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;\nconst ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;\nconst cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/([0-9]|[1-2][0-9]|3[0-2])$/;\nconst cidrv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/;\nconst base64 = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;\nconst base64url = /^(?:[A-Za-z0-9_-]{4})*(?:[A-Za-z0-9_-]{2,3})?$/;\nconst httpProtocol = /^https?$/;\nconst e164 = /^\\+[1-9]\\d{6,14}$/;\nconst dateSource = `(?:(?:\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\\\d|30)|(?:02)-(?:0[1-9]|1\\\\d|2[0-8])))`;\n/** Anchors a pattern source. The interpolation lives here rather than at the call site because\n* esbuild will not drop a `@__PURE__` call whose own argument interpolates a variable, but it\n* will drop `anchor(dateSource)`. Keeping it inline pinned `date` into every bundle. */\nfunction anchor(source) {\n return new RegExp(`^${source}$`);\n}\nconst date = /*@__PURE__*/ anchor(dateSource);\nfunction timeSource(args) {\n const hhmm = `(?:[01]\\\\d|2[0-3]):[0-5]\\\\d`;\n return typeof args.precision === \"number\" ? args.precision === -1 ? `${hhmm}` : args.precision === 0 ? `${hhmm}:[0-5]\\\\d` : `${hhmm}:[0-5]\\\\d\\\\.\\\\d{${args.precision}}` : args.seconds ? `${hhmm}:[0-5]\\\\d(?:\\\\.\\\\d+)?` : `${hhmm}(?::[0-5]\\\\d(?:\\\\.\\\\d+)?)?`;\n}\nfunction time(args) {\n return new RegExp(`^${timeSource(args)}$`);\n}\nfunction datetime(args) {\n const opts = [\"Z\"];\n if (args.offset) opts.push(`([+-](?:[01]\\\\d|2[0-3]):[0-5]\\\\d)`);\n const qualified = `${timeSource({\n precision: args.precision,\n seconds: true\n })}(?:${opts.join(\"|\")})`;\n const timeRegex = args.local ? `${qualified}|${timeSource({ precision: args.precision })}` : qualified;\n return new RegExp(`^${dateSource}T(?:${timeRegex})$`);\n}\nconst anyString = /^[\\s\\S]{0,}$/;\nconst integer = /^-?\\d+$/;\nconst number = /^-?\\d+(?:\\.\\d+)?$/;\nconst lowercase = /^[^A-Z]*$/;\nconst uppercase = /^[^a-z]*$/;\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});\n/** Default `when` for length-based checks: run only on non-nullish values with a `length`. */\nconst _whenHasLength = (payload) => {\n const val = payload.value;\n return !nullish(val) && val.length !== void 0;\n};\nconst $ZodCheckMaxLength = /*@__PURE__*/ $constructor(\"$ZodCheckMaxLength\", (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.maximum ? codePointLength(input) : units) <= def.maximum) return;\n const origin = getLengthableOrigin(input);\n payload.issues.push({\n origin,\n code: \"too_big\",\n maximum: def.maximum,\n inclusive: true,\n input,\n inst,\n continue: !def.abort\n });\n };\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});\nconst $ZodCheckLengthEquals = /*@__PURE__*/ $constructor(\"$ZodCheckLengthEquals\", (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 const length = typeof input === \"string\" && units >= def.length && units <= def.length * 2 ? codePointLength(input) : units;\n if (length === def.length) return;\n const origin = getLengthableOrigin(input);\n const tooBig = length > def.length;\n payload.issues.push({\n origin,\n ...tooBig ? {\n code: \"too_big\",\n maximum: def.length\n } : {\n code: \"too_small\",\n minimum: def.length\n },\n inclusive: true,\n exact: true,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckStringFormat = /*@__PURE__*/ $constructor(\"$ZodCheckStringFormat\", (inst, def) => {\n var _a, _b;\n $ZodCheck.init(inst, def);\n if (def.pattern) (_a = inst._zod).check ?? (_a.check = (payload) => {\n def.pattern.lastIndex = 0;\n if (def.pattern.test(payload.value)) return;\n payload.issues.push({\n origin: \"string\",\n code: \"invalid_format\",\n format: def.format,\n input: payload.value,\n ...def.pattern ? { pattern: def.pattern.toString() } : {},\n inst,\n continue: !def.abort\n });\n });\n else (_b = inst._zod).check ?? (_b.check = () => {});\n});\nconst $ZodCheckRegex = /*@__PURE__*/ $constructor(\"$ZodCheckRegex\", (inst, def) => {\n $ZodCheckStringFormat.init(inst, def);\n inst._zod.check = (payload) => {\n def.pattern.lastIndex = 0;\n if (def.pattern.test(payload.value)) return;\n payload.issues.push({\n origin: \"string\",\n code: \"invalid_format\",\n format: \"regex\",\n input: payload.value,\n pattern: def.pattern.toString(),\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckLowerCase = /*@__PURE__*/ $constructor(\"$ZodCheckLowerCase\", (inst, def) => {\n def.pattern ?? (def.pattern = lowercase);\n $ZodCheckStringFormat.init(inst, def);\n});\nconst $ZodCheckUpperCase = /*@__PURE__*/ $constructor(\"$ZodCheckUpperCase\", (inst, def) => {\n def.pattern ?? (def.pattern = uppercase);\n $ZodCheckStringFormat.init(inst, def);\n});\nconst $ZodCheckIncludes = /*@__PURE__*/ $constructor(\"$ZodCheckIncludes\", (inst, def) => {\n $ZodCheck.init(inst, def);\n const escapedRegex = escapeRegex(def.includes);\n def.pattern = new RegExp(typeof def.position === \"number\" ? `^.{${def.position},}${escapedRegex}` : escapedRegex);\n inst._zod.check = (payload) => {\n if (payload.value.includes(def.includes, def.position)) return;\n payload.issues.push({\n origin: \"string\",\n code: \"invalid_format\",\n format: \"includes\",\n includes: def.includes,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckStartsWith = /*@__PURE__*/ $constructor(\"$ZodCheckStartsWith\", (inst, def) => {\n $ZodCheck.init(inst, def);\n const pattern = new RegExp(`^${escapeRegex(def.prefix)}.*`);\n def.pattern ?? (def.pattern = pattern);\n inst._zod.check = (payload) => {\n if (payload.value.startsWith(def.prefix)) return;\n payload.issues.push({\n origin: \"string\",\n code: \"invalid_format\",\n format: \"starts_with\",\n prefix: def.prefix,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckEndsWith = /*@__PURE__*/ $constructor(\"$ZodCheckEndsWith\", (inst, def) => {\n $ZodCheck.init(inst, def);\n const pattern = new RegExp(`.*${escapeRegex(def.suffix)}$`);\n def.pattern ?? (def.pattern = pattern);\n inst._zod.check = (payload) => {\n if (payload.value.endsWith(def.suffix)) return;\n payload.issues.push({\n origin: \"string\",\n code: \"invalid_format\",\n format: \"ends_with\",\n suffix: def.suffix,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCheckOverwrite = /*@__PURE__*/ $constructor(\"$ZodCheckOverwrite\", (inst, def) => {\n $ZodCheck.init(inst, def);\n inst._zod.check = (payload) => {\n payload.value = def.tx(payload.value);\n };\n});\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/doc.js\nvar Doc = class {\n constructor(args = [], closed = {}) {\n this.content = [];\n this.indent = 0;\n this.args = args;\n this.closed = closed;\n }\n indented(fn) {\n this.indent += 1;\n try {\n fn(this);\n } finally {\n this.indent -= 1;\n }\n }\n write(arg) {\n if (typeof arg === \"function\") {\n arg(this, { execution: \"sync\" });\n arg(this, { execution: \"async\" });\n return;\n }\n const lines = arg.split(\"\\n\").filter((x) => x);\n const minIndent = Math.min(...lines.map((x) => x.length - x.trimStart().length));\n const dedented = lines.map((x) => x.slice(minIndent)).map((x) => \" \".repeat(this.indent * 2) + x);\n for (const line of dedented) this.content.push(line);\n }\n compile() {\n const F = Function;\n const content = this?.content ?? [``];\n return new F(...Object.keys(this.closed), `return function (${this.args.join(\", \")}) {\\n${content.join(\"\\n\")}\\n};`)(...Object.values(this.closed));\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});\n/** The Standard Schema surface for `inst`. Shared so wrappers can extend it without forcing it. */\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 $ZodStringFormat = /*@__PURE__*/ $constructor(\"$ZodStringFormat\", (inst, def) => {\n $ZodCheckStringFormat.init(inst, def);\n $ZodString.init(inst, def);\n});\nconst $ZodGUID = /*@__PURE__*/ $constructor(\"$ZodGUID\", (inst, def) => {\n def.pattern ?? (def.pattern = guid);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodUUID = /*@__PURE__*/ $constructor(\"$ZodUUID\", (inst, def) => {\n if (def.version) {\n const v = {\n v1: 1,\n v2: 2,\n v3: 3,\n v4: 4,\n v5: 5,\n v6: 6,\n v7: 7,\n v8: 8\n }[def.version];\n if (v === void 0) throw new Error(`Invalid UUID version: \"${def.version}\"`);\n def.pattern ?? (def.pattern = uuid(v));\n } else def.pattern ?? (def.pattern = uuid());\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodEmail = /*@__PURE__*/ $constructor(\"$ZodEmail\", (inst, def) => {\n def.pattern ?? (def.pattern = email);\n $ZodStringFormat.init(inst, def);\n});\n/** The `://` guard rejected the input before the URL constructor saw it. */\nconst URL_BAD_FORMAT = 1;\n/** The URL parser rejected the input. */\nconst URL_UNPARSEABLE = 2;\nfunction canParseURL(input) {\n try {\n if (typeof URL !== \"undefined\" && typeof URL.canParse === \"function\") return URL.canParse(input);\n new URL(input);\n return true;\n } catch {\n return false;\n }\n}\nfunction validateURL(trimmed, def) {\n if (!(\"normalize\" in def) && !(\"hostname\" in def) && !(\"protocol\" in def)) return canParseURL(trimmed) || 2;\n return parseURLObject(trimmed, def);\n}\n/** Parses a URL while preserving the non-normalizing HTTP guard. */\nfunction parseURLObject(trimmed, def) {\n if (!def.normalize && def.protocol?.source === httpProtocol.source && !/^https?:\\/\\//i.test(trimmed)) return 1;\n try {\n if (typeof URL !== \"undefined\") {\n const URLStatic = URL;\n if (typeof URLStatic.parse === \"function\") return URLStatic.parse(trimmed) ?? 2;\n }\n return new URL(trimmed);\n } catch {\n return 2;\n }\n}\nconst asciiTabOrNewline = /[\\t\\n\\r]/g;\n/** The URL parser deletes every ASCII tab, LF and CR from its input before it parses, so `new URL(\"https://exa\\nmple.com\")` reports on `example.com`. Applying the same deletion to the returned value closes the half of that divergence which can move the host; the parser's other rewrite, stripping C0 controls at the edges, cannot. */\nfunction stripTabAndNewline(value) {\n return value.replace(asciiTabOrNewline, \"\");\n}\nfunction urlHostnameOk(url, hostname) {\n hostname.lastIndex = 0;\n return hostname.test(url.hostname);\n}\nfunction urlProtocolOk(url, protocol) {\n protocol.lastIndex = 0;\n return protocol.test(url.protocol.endsWith(\":\") ? url.protocol.slice(0, -1) : url.protocol);\n}\nconst $ZodURL = /*@__PURE__*/ $constructor(\"$ZodURL\", (inst, def) => {\n $ZodStringFormat.init(inst, def);\n inst._zod.check = (payload) => {\n try {\n const trimmed = payload.value.trim();\n const url = validateURL(trimmed, def);\n if (url === 1) {\n payload.issues.push({\n code: \"invalid_format\",\n format: \"url\",\n note: \"Invalid URL format\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n return;\n }\n if (url === 2) {\n payload.issues.push({\n code: \"invalid_format\",\n format: \"url\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n return;\n }\n if (url === true) {\n payload.value = stripTabAndNewline(trimmed);\n return;\n }\n if (def.hostname && !urlHostnameOk(url, def.hostname)) payload.issues.push({\n code: \"invalid_format\",\n format: \"url\",\n note: \"Invalid hostname\",\n pattern: def.hostname.source,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n if (def.protocol && !urlProtocolOk(url, def.protocol)) payload.issues.push({\n code: \"invalid_format\",\n format: \"url\",\n note: \"Invalid protocol\",\n pattern: def.protocol.source,\n input: payload.value,\n inst,\n continue: !def.abort\n });\n payload.value = def.normalize ? url.href : stripTabAndNewline(trimmed);\n return;\n } catch (_) {\n payload.issues.push({\n code: \"invalid_format\",\n format: \"url\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n }\n };\n});\nconst $ZodEmoji = /*@__PURE__*/ $constructor(\"$ZodEmoji\", (inst, def) => {\n def.pattern ?? (def.pattern = emoji());\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodNanoID = /*@__PURE__*/ $constructor(\"$ZodNanoID\", (inst, def) => {\n if (def.length !== void 0 && (!Number.isInteger(def.length) || def.length < 1)) throw new Error(`Invalid nanoid length: ${def.length}`);\n def.pattern ?? (def.pattern = def.length === void 0 ? nanoid : nanoidOfLength(def.length));\n $ZodStringFormat.init(inst, def);\n});\n/**\n* @deprecated CUID v1 is deprecated by its authors due to information leakage\n* (timestamps embedded in the id). Use {@link $ZodCUID2} instead.\n* See https://github.com/paralleldrive/cuid.\n*/\nconst $ZodCUID = /*@__PURE__*/ $constructor(\"$ZodCUID\", (inst, def) => {\n def.pattern ?? (def.pattern = cuid);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodCUID2 = /*@__PURE__*/ $constructor(\"$ZodCUID2\", (inst, def) => {\n def.pattern ?? (def.pattern = cuid2);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodULID = /*@__PURE__*/ $constructor(\"$ZodULID\", (inst, def) => {\n def.pattern ?? (def.pattern = ulid);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodXID = /*@__PURE__*/ $constructor(\"$ZodXID\", (inst, def) => {\n def.pattern ?? (def.pattern = xid);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodKSUID = /*@__PURE__*/ $constructor(\"$ZodKSUID\", (inst, def) => {\n def.pattern ?? (def.pattern = ksuid);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodISODateTime = /*@__PURE__*/ $constructor(\"$ZodISODateTime\", (inst, def) => {\n def.pattern ?? (def.pattern = datetime(def));\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodISODate = /*@__PURE__*/ $constructor(\"$ZodISODate\", (inst, def) => {\n def.pattern ?? (def.pattern = date);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodISOTime = /*@__PURE__*/ $constructor(\"$ZodISOTime\", (inst, def) => {\n def.pattern ?? (def.pattern = time(def));\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodISODuration = /*@__PURE__*/ $constructor(\"$ZodISODuration\", (inst, def) => {\n def.pattern ?? (def.pattern = duration);\n $ZodStringFormat.init(inst, def);\n});\nconst $ZodIPv4 = /*@__PURE__*/ $constructor(\"$ZodIPv4\", (inst, def) => {\n def.pattern ?? (def.pattern = ipv4);\n $ZodStringFormat.init(inst, def);\n});\n/** An IPv6 address is written with hex digits, colons and dots, and nothing else. The guard is what makes the check below an IPv6 check: `new URL(\"http://[...]\")` parses an authority, not an address, so `@` and `\\` re-delimit it and `\"::@1\\\\\"` validates against the host `0.0.0.1`. The URL parser also deletes ASCII tab, LF and CR rather than failing, which is how `\"::1\\n\"` validated as `::1`. */\nconst ipv6Alphabet = /^[0-9a-fA-F:.]+$/;\nfunction isValidIPv6(value) {\n if (!ipv6Alphabet.test(value)) return false;\n return canParseURL(`http://[${value}]`);\n}\nconst $ZodIPv6 = /*@__PURE__*/ $constructor(\"$ZodIPv6\", (inst, def) => {\n def.pattern ?? (def.pattern = ipv6);\n $ZodStringFormat.init(inst, def);\n inst._zod.check = (payload) => {\n if (!isValidIPv6(payload.value)) payload.issues.push({\n code: \"invalid_format\",\n format: \"ipv6\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodCIDRv4 = /*@__PURE__*/ $constructor(\"$ZodCIDRv4\", (inst, def) => {\n def.pattern ?? (def.pattern = cidrv4);\n $ZodStringFormat.init(inst, def);\n});\nfunction isValidCIDRv6(value) {\n const parts = value.split(\"/\");\n if (parts.length !== 2) return false;\n const [address, prefix] = parts;\n if (!prefix) return false;\n const prefixNum = Number(prefix);\n if (`${prefixNum}` !== prefix) return false;\n if (prefixNum < 0 || prefixNum > 128) return false;\n return isValidIPv6(address);\n}\nconst $ZodCIDRv6 = /*@__PURE__*/ $constructor(\"$ZodCIDRv6\", (inst, def) => {\n def.pattern ?? (def.pattern = cidrv6);\n $ZodStringFormat.init(inst, def);\n inst._zod.check = (payload) => {\n if (!isValidCIDRv6(payload.value)) payload.issues.push({\n code: \"invalid_format\",\n format: \"cidrv6\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nfunction isValidBase64(data) {\n if (data === \"\") return true;\n if (/\\s/.test(data)) return false;\n if (data.length % 4 !== 0) return false;\n try {\n atob(data);\n return true;\n } catch {\n return false;\n }\n}\nconst base64Charset = /^[0-9a-zA-Z+/]*={0,2}$/;\nconst $ZodBase64 = /*@__PURE__*/ $constructor(\"$ZodBase64\", (inst, def) => {\n def.pattern ?? (def.pattern = base64Charset);\n $ZodStringFormat.init(inst, def);\n inst._zod.check = (payload) => {\n if (isValidBase64(payload.value)) return;\n payload.issues.push({\n code: \"invalid_format\",\n format: \"base64\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst base64urlCharset = /^[A-Za-z0-9_-]*$/;\nfunction isValidBase64URL(data) {\n if (!base64urlCharset.test(data)) return false;\n const base64 = data.replace(/[-_]/g, (c) => c === \"-\" ? \"+\" : \"/\");\n return isValidBase64(base64.padEnd(Math.ceil(base64.length / 4) * 4, \"=\"));\n}\nconst $ZodBase64URL = /*@__PURE__*/ $constructor(\"$ZodBase64URL\", (inst, def) => {\n def.pattern ?? (def.pattern = base64urlCharset);\n $ZodStringFormat.init(inst, def);\n inst._zod.check = (payload) => {\n if (isValidBase64URL(payload.value)) return;\n payload.issues.push({\n code: \"invalid_format\",\n format: \"base64url\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodE164 = /*@__PURE__*/ $constructor(\"$ZodE164\", (inst, def) => {\n def.pattern ?? (def.pattern = e164);\n $ZodStringFormat.init(inst, def);\n});\nfunction isValidJWT(token, algorithm = null) {\n try {\n const tokensParts = token.split(\".\");\n if (tokensParts.length !== 3) return false;\n const [header] = tokensParts;\n if (!header) return false;\n const parsedHeader = JSON.parse(atob(header));\n if (\"typ\" in parsedHeader && parsedHeader?.typ !== \"JWT\") return false;\n if (!parsedHeader.alg) return false;\n if (algorithm && (!(\"alg\" in parsedHeader) || parsedHeader.alg !== algorithm)) return false;\n return true;\n } catch {\n return false;\n }\n}\nconst $ZodJWT = /*@__PURE__*/ $constructor(\"$ZodJWT\", (inst, def) => {\n $ZodStringFormat.init(inst, def);\n inst._zod.check = (payload) => {\n if (isValidJWT(payload.value, def.alg)) return;\n payload.issues.push({\n code: \"invalid_format\",\n format: \"jwt\",\n input: payload.value,\n inst,\n continue: !def.abort\n });\n };\n});\nconst $ZodUnknown = /*@__PURE__*/ $constructor(\"$ZodUnknown\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.parse = (payload) => payload;\n});\nconst $ZodNever = /*@__PURE__*/ $constructor(\"$ZodNever\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.parse = (payload, _ctx) => {\n payload.issues.push({\n expected: \"never\",\n code: \"invalid_type\",\n input: payload.value,\n inst\n });\n return payload;\n };\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 $ZodObjectJIT = /*@__PURE__*/ $constructor(\"$ZodObjectJIT\", (inst, def) => {\n $ZodObject.init(inst, def);\n const superParse = inst._zod.parse;\n const _normalized = cached(() => normalizeDef(def));\n const memo = globalConfig.memoizer;\n const generateFastpass = (shape) => {\n const normalized = _normalized.value;\n const syms = normalized.symbolKeys;\n const doc = new Doc([\"payload\", \"ctx\"], {\n shape,\n inst,\n memo,\n syms\n });\n const parseStr = (k) => `shape[${k}]._zod.run({ value: input[${k}], issues: [] }, ctx)`;\n const prefixStr = (id, k) => `\n let ${id}_ab = false;\n for (let i = 0; i < ${id}.issues.length; i++) {\n const iss = ${id}.issues[i];\n iss.path = iss.path ? [${k}, ...iss.path] : [${k}];\n payload.issues.push(iss);\n if (iss.continue !== true) ${id}_ab = true;\n }\n if (${id}_ab && ctx && ctx.abortEarly) {\n payload.value = newResult;\n return payload;\n }`;\n doc.write(`const input = payload.value;`);\n const ids = Object.create(null);\n let counter = 0;\n for (const key of normalized.allKeys) ids[key] = `key_${counter++}`;\n doc.write(memo ? `const newResult = memo.alloc(inst, payload, {}, ctx);` : `const newResult = {};`);\n for (const key of normalized.allKeys) {\n if (key === \"__proto__\") continue;\n const id = ids[key];\n const k = typeof key === \"symbol\" ? `syms[${syms.indexOf(key)}]` : esc(key);\n const isPresent = `${k} in input`;\n const schema = shape[key];\n const optin = schema?._zod?.optin;\n const isOptionalIn = optin !== void 0;\n const isOptionalOut = schema?._zod?.optout === \"optional\";\n doc.write(`const ${id} = ${parseStr(k)};`);\n if (isOptionalIn && isOptionalOut) {\n const assign = optin === \"optional\" ? `${id}_present` : `${id}.value !== undefined || ${id}_present`;\n doc.write(`\n const ${id}_present = ${isPresent};\n if (!${id}.issues.length || ${id}_present) {\n if (${id}.issues.length) {${prefixStr(id, k)}\n }\n\n if (${assign}) {\n newResult[${k}] = ${id}.value;\n }\n }\n\n `);\n } else if (!isOptionalIn) doc.write(`\n const ${id}_present = ${isPresent};\n if (${id}.issues.length) {${prefixStr(id, k)}\n }\n if (!${id}_present && !${id}.issues.length) {\n payload.issues.push({\n code: \"invalid_type\",\n expected: \"nonoptional\",\n input: undefined,\n path: [${k}]\n });\n if (ctx && ctx.abortEarly) {\n payload.value = newResult;\n return payload;\n }\n }\n\n if (${id}_present) {\n newResult[${k}] = ${id}.value;\n }\n\n `);\n else {\n doc.write(`\n if (${id}.issues.length) {${prefixStr(id, k)}\n }\n `);\n if (optin === \"defaulted\") doc.write(`newResult[${k}] = ${id}.value;`);\n else doc.write(`\n if (${id}.value !== undefined || ${isPresent}) {\n newResult[${k}] = ${id}.value;\n }\n `);\n }\n }\n doc.write(`payload.value = newResult;`);\n doc.write(`return payload;`);\n return doc.compile();\n };\n let fastpass;\n const isObject$1 = isObject;\n const jit = !globalConfig.jitless;\n const allowsEval$1 = allowsEval;\n const fastEnabled = jit && allowsEval$1.value;\n const catchall = def.catchall;\n let value;\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 if (jit && fastEnabled && ctx?.async === false && ctx.jitless !== true) {\n if (!fastpass) fastpass = generateFastpass(def.shape);\n payload = fastpass(payload, ctx);\n if (!catchall) return payload;\n return handleCatchall([], input, payload, ctx, value, inst, ctx?.abortEarly === true);\n }\n return superParse(payload, ctx);\n };\n});\nfunction handleUnionResults(results, final, inst, ctx) {\n for (const result of results) if (result.issues.length === 0) {\n final.value = result.value;\n return final;\n }\n const nonaborted = results.filter((r) => !aborted(r));\n if (nonaborted.length === 1) {\n final.value = nonaborted[0].value;\n return nonaborted[0];\n }\n final.issues.push({\n code: \"invalid_union\",\n input: final.value,\n inst,\n errors: results.map((result) => result.issues.map((iss) => finalizeIssue(iss, ctx, config())))\n });\n return final;\n}\nconst $ZodUnion = /*@__PURE__*/ $constructor(\"$ZodUnion\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazyInternal(inst, \"optin\", (zod) => zod.def.options.some((o) => o._zod.optin === \"defaulted\") ? \"defaulted\" : zod.def.options.some((o) => o._zod.optin !== void 0) ? \"optional\" : void 0);\n defineLazyInternal(inst, \"optout\", (zod) => zod.def.options.some((o) => o._zod.optout === \"optional\") ? \"optional\" : void 0);\n defineLazyInternal(inst, \"values\", (zod) => {\n if (zod.def.options.every((o) => o._zod.values)) return new Set(zod.def.options.flatMap((option) => Array.from(option._zod.values)));\n });\n defineLazyInternal(inst, \"pattern\", (zod) => {\n if (zod.def.options.every((o) => o._zod.pattern)) {\n const patterns = zod.def.options.map((o) => o._zod.pattern);\n return new RegExp(`^(${patterns.map((p) => cleanRegex(p.source)).join(\"|\")})$`);\n }\n });\n const first = def.options.length === 1 ? def.options[0]._zod.run : null;\n inst._zod.parse = (payload, ctx) => {\n if (first) return first(payload, ctx);\n let async = false;\n const results = [];\n for (const option of def.options) {\n const result = option._zod.run({\n value: payload.value,\n issues: []\n }, ctx);\n if (result instanceof Promise) {\n results.push(result);\n async = true;\n } else {\n if (result.issues.length === 0) return result;\n results.push(result);\n }\n }\n if (!async) return handleUnionResults(results, payload, inst, ctx);\n return Promise.all(results).then((results) => {\n return handleUnionResults(results, payload, inst, ctx);\n });\n };\n});\nconst $ZodIntersection = /*@__PURE__*/ $constructor(\"$ZodIntersection\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.parse = (payload, ctx) => {\n const input = payload.value;\n const left = def.left._zod.run({\n value: input,\n issues: []\n }, ctx);\n const right = def.right._zod.run({\n value: input,\n issues: []\n }, ctx);\n if (left instanceof Promise || right instanceof Promise) return Promise.all([left, right]).then(([left, right]) => {\n return handleIntersectionResults(payload, left, right);\n });\n return handleIntersectionResults(payload, left, right);\n };\n});\nfunction mergeValues(a, b) {\n if (a === b) return {\n valid: true,\n data: a\n };\n if (a instanceof Date && b instanceof Date && +a === +b) return {\n valid: true,\n data: a\n };\n if (isPlainObject(a) && isPlainObject(b)) {\n const bKeys = Object.keys(b);\n const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);\n const newObj = {\n ...a,\n ...b\n };\n if (Object.prototype.hasOwnProperty.call(newObj, \"__proto__\")) delete newObj.__proto__;\n for (const key of sharedKeys) {\n if (key === \"__proto__\") continue;\n const sharedValue = mergeValues(a[key], b[key]);\n if (!sharedValue.valid) return {\n valid: false,\n mergeErrorPath: [key, ...sharedValue.mergeErrorPath]\n };\n newObj[key] = sharedValue.data;\n }\n return {\n valid: true,\n data: newObj\n };\n }\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return {\n valid: false,\n mergeErrorPath: []\n };\n const newArray = [];\n for (let index = 0; index < a.length; index++) {\n const itemA = a[index];\n const itemB = b[index];\n const sharedValue = mergeValues(itemA, itemB);\n if (!sharedValue.valid) return {\n valid: false,\n mergeErrorPath: [index, ...sharedValue.mergeErrorPath]\n };\n newArray.push(sharedValue.data);\n }\n return {\n valid: true,\n data: newArray\n };\n }\n return {\n valid: false,\n mergeErrorPath: []\n };\n}\nfunction handleIntersectionResults(result, left, right) {\n const unrecKeys = /* @__PURE__ */ new Map();\n let unrecIssue;\n const keyIssues = /* @__PURE__ */ new Map();\n const collect = (iss, side) => {\n let keys;\n if (iss.code === \"unrecognized_keys\" && !iss.path?.length) {\n unrecIssue ?? (unrecIssue = iss);\n keys = iss.keys;\n } else if (iss.code === \"invalid_key\" && iss.origin === \"record\" && iss.path?.length === 1) {\n const k = String(iss.path[0]);\n if (!keyIssues.has(k)) keyIssues.set(k, iss);\n keys = [k];\n } else return false;\n for (const k of keys) {\n if (!unrecKeys.has(k)) unrecKeys.set(k, {});\n unrecKeys.get(k)[side] = true;\n }\n return true;\n };\n for (const iss of left.issues) if (!collect(iss, \"l\")) result.issues.push(iss);\n for (const iss of right.issues) if (!collect(iss, \"r\")) result.issues.push(iss);\n const bothKeys = [...unrecKeys].filter(([, f]) => f.l && f.r).map(([k]) => k);\n if (bothKeys.length) {\n const aggregated = unrecIssue ? bothKeys.filter((k) => unrecIssue.keys.includes(k)) : [];\n if (aggregated.length) result.issues.push({\n ...unrecIssue,\n keys: aggregated\n });\n for (const k of bothKeys) if (!aggregated.includes(k) && keyIssues.has(k)) result.issues.push(keyIssues.get(k));\n }\n const merged = mergeValues(left.value, right.value);\n if (!merged.valid) {\n if (aborted(result)) return result;\n throw new Error(`Unmergable intersection. Error path: ${JSON.stringify(merged.mergeErrorPath)}`);\n }\n result.value = merged.data;\n return result;\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});\nconst $ZodEnum = /*@__PURE__*/ $constructor(\"$ZodEnum\", (inst, def) => {\n $ZodType.init(inst, def);\n const values = getEnumValues(def.entries);\n const valuesSet = new Set(values);\n inst._zod.values = valuesSet;\n defineLazyInternal(inst, \"pattern\", (zod) => {\n const patternValues = getEnumValues(zod.def.entries).filter((k) => propertyKeyTypes.has(typeof k));\n return new RegExp(patternValues.length ? `^(${patternValues.map((o) => escapeRegex(o.toString())).join(\"|\")})$` : \"^[^\\\\s\\\\S]$\");\n });\n inst._zod.parse = (payload, _ctx) => {\n const input = payload.value;\n if (valuesSet.has(input)) return payload;\n payload.issues.push({\n code: \"invalid_value\",\n values,\n input,\n inst\n });\n return payload;\n };\n});\nconst $ZodTransform = /*@__PURE__*/ $constructor(\"$ZodTransform\", (inst, def) => {\n $ZodType.init(inst, def);\n inst._zod.optin = \"optional\";\n globalConfig.memoizer?.guard(inst);\n inst._zod.parse = (payload, ctx) => {\n if (ctx.direction === \"backward\") throw new $ZodEncodeError(inst.constructor.name);\n const _out = def.transform(payload.value, payload);\n if (ctx.async) return (_out instanceof Promise ? _out : Promise.resolve(_out)).then((output) => {\n payload.value = output;\n return payload;\n });\n if (_out instanceof Promise) throw new $ZodAsyncError();\n payload.value = _out;\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 $ZodExactOptional = /*@__PURE__*/ $constructor(\"$ZodExactOptional\", (inst, def) => {\n $ZodOptional.init(inst, def);\n defineLazyInternal(inst, \"values\", (zod) => zod.def.innerType._zod.values);\n defineLazyInternal(inst, \"pattern\", (zod) => zod.def.innerType._zod.pattern);\n inst._zod.parse = (payload, ctx) => {\n return def.innerType._zod.run(payload, ctx);\n };\n});\nconst $ZodNullable = /*@__PURE__*/ $constructor(\"$ZodNullable\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazyInternal(inst, \"optin\", (zod) => zod.def.innerType._zod.optin);\n defineLazyInternal(inst, \"optout\", (zod) => zod.def.innerType._zod.optout);\n defineLazyInternal(inst, \"pattern\", (zod) => {\n const pattern = zod.def.innerType._zod.pattern;\n return pattern ? new RegExp(`^(${cleanRegex(pattern.source)}|null)$`) : void 0;\n });\n defineLazyInternal(inst, \"values\", (zod) => {\n return zod.def.innerType._zod.values ? /* @__PURE__ */ new Set([...zod.def.innerType._zod.values, null]) : void 0;\n });\n inst._zod.parse = (payload, ctx) => {\n if (payload.value === null) return payload;\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 /**\n * $ZodDefault returns the default value immediately in forward direction.\n * It doesn't pass the default value into the validator (\"prefault\"). There's no reason to pass the default value through validation. The validity of the default is enforced by TypeScript statically. Otherwise, it's the responsibility of the user to ensure the default is valid. In the case of pipes with divergent in/out types, you can specify the default on the `in` schema of your ZodPipe to set a \"prefault\" for the pipe. */\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}\nconst $ZodPrefault = /*@__PURE__*/ $constructor(\"$ZodPrefault\", (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) payload.value = def.defaultValue;\n return def.innerType._zod.run(payload, ctx);\n };\n});\nconst $ZodNonOptional = /*@__PURE__*/ $constructor(\"$ZodNonOptional\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazyInternal(inst, \"values\", (zod) => {\n const v = zod.def.innerType._zod.values;\n return v ? new Set([...v].filter((x) => x !== void 0)) : void 0;\n });\n inst._zod.parse = (payload, ctx) => {\n const result = def.innerType._zod.run(payload, ctx);\n if (result instanceof Promise) return result.then((result) => handleNonOptionalResult(result, inst));\n return handleNonOptionalResult(result, inst);\n };\n});\nfunction handleNonOptionalResult(payload, inst) {\n if (!payload.issues.length && payload.value === void 0) payload.issues.push({\n code: \"invalid_type\",\n expected: \"nonoptional\",\n input: payload.value,\n inst\n });\n return payload;\n}\nfunction handleCatchResult(payload, result, def, ctx) {\n if (!result.issues.length) {\n payload.value = result.value;\n if (result.memo) payload.memo = true;\n return payload;\n }\n payload.value = def.catchValue({\n ...result,\n value: payload.value,\n error: { issues: result.issues.map((iss) => finalizeIssue(iss, ctx, config())) },\n input: payload.value\n });\n return payload;\n}\nconst $ZodCatch = /*@__PURE__*/ $constructor(\"$ZodCatch\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazyInternal(inst, \"optin\", (zod) => zod.def.innerType._zod.optin === \"defaulted\" ? \"defaulted\" : \"optional\");\n defineLazyInternal(inst, \"optout\", (zod) => zod.def.innerType._zod.optout);\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 const result = def.innerType._zod.run({\n value: payload.value,\n issues: []\n }, ctx);\n if (result instanceof Promise) return result.then((result) => handleCatchResult(payload, result, def, ctx));\n return handleCatchResult(payload, result, def, ctx);\n };\n});\nconst $ZodPipe = /*@__PURE__*/ $constructor(\"$ZodPipe\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazyInternal(inst, \"values\", (zod) => zod.def.in._zod.values);\n defineLazyInternal(inst, \"optin\", (zod) => zod.def.in._zod.optin);\n defineLazyInternal(inst, \"optout\", (zod) => zod.def.out._zod.optout);\n defineLazyInternal(inst, \"propValues\", (zod) => zod.def.in._zod.propValues);\n inst._zod.parse = (payload, ctx) => {\n if (ctx.direction === \"backward\") {\n const right = def.out._zod.run(payload, ctx);\n if (right instanceof Promise) return right.then((right) => handlePipeResult(right, def.in, ctx));\n return handlePipeResult(right, def.in, ctx);\n }\n const left = def.in._zod.run(payload, ctx);\n if (left instanceof Promise) return left.then((left) => handlePipeResult(left, def.out, ctx));\n return handlePipeResult(left, def.out, ctx);\n };\n});\nfunction handlePipeResult(left, next, ctx) {\n if (left.issues.some((iss) => iss.code !== \"unrecognized_keys\")) {\n left.aborted = true;\n return left;\n }\n return next._zod.run({\n value: left.value,\n issues: left.issues\n }, ctx);\n}\nconst $ZodReadonly = /*@__PURE__*/ $constructor(\"$ZodReadonly\", (inst, def) => {\n $ZodType.init(inst, def);\n defineLazyInternal(inst, \"propValues\", (zod) => zod.def.innerType._zod.propValues);\n defineLazyInternal(inst, \"values\", (zod) => zod.def.innerType._zod.values);\n defineLazyInternal(inst, \"optin\", (zod) => zod.def.innerType?._zod?.optin);\n defineLazyInternal(inst, \"optout\", (zod) => zod.def.innerType?._zod?.optout);\n inst._zod.parse = (payload, ctx) => {\n if (ctx.direction === \"backward\") return def.innerType._zod.run(payload, ctx);\n const result = def.innerType._zod.run(payload, ctx);\n if (result instanceof Promise) return result.then(handleReadonlyResult);\n return handleReadonlyResult(result);\n };\n});\nfunction handleReadonlyResult(payload) {\n if (!payload.memo) payload.value = Object.freeze(payload.value);\n return payload;\n}\nconst $ZodCustom = /*@__PURE__*/ $constructor(\"$ZodCustom\", (inst, def) => {\n $ZodCheck.init(inst, def);\n $ZodType.init(inst, def);\n inst._zod.parse = (payload, _) => {\n return payload;\n };\n inst._zod.check = (payload) => {\n const input = payload.value;\n const r = def.fn(input);\n if (r instanceof Promise) return r.then((r) => handleRefineResult(r, payload, input, inst));\n handleRefineResult(r, payload, input, inst);\n };\n});\nfunction handleRefineResult(result, payload, input, inst) {\n if (!result) {\n const _iss = {\n code: \"custom\",\n input,\n inst,\n path: [...inst._zod.def.path ?? []],\n continue: !inst._zod.def.abort\n };\n if (inst._zod.def.params) _iss.params = inst._zod.def.params;\n payload.issues.push(issue(_iss));\n }\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/memoizer.js\nvar $ZodCyclicError = class extends Error {\n constructor() {\n super(`Cannot parse a reference cycle that closes through a transform`);\n this.name = \"ZodCyclicError\";\n }\n};\n/** Keyed off the context object every schema in one parse call already shares. */\nconst STATE = \"~memo\";\nconst NO_ISSUES = [];\nfunction isRef(value) {\n return value !== null && typeof value === \"object\";\n}\nfunction cloneIssues(issues) {\n return issues.map((iss) => iss.path ? {\n ...iss,\n path: iss.path.slice()\n } : { ...iss });\n}\nconst recursive = /*@__PURE__*/ new WeakMap();\n/** What the walk established, in order of certainty: ordered so the strongest answer among children wins. */\nconst NONE = 0;\nconst ASSUMED = 1;\nconst PROVEN = 2;\n/** Whether this schema's subtree contains a cycle, so one parse can re-enter it. */\nfunction isRecursive(inst, stack, resolve) {\n const cached = recursive.get(inst);\n if (cached !== void 0) return cached ? PROVEN : NONE;\n if (stack.has(inst)) return PROVEN;\n stack.add(inst);\n let result = NONE;\n const check = (child) => {\n if (result !== PROVEN && child?._zod) {\n const answer = isRecursive(child, stack, resolve);\n if (answer > result) result = answer;\n }\n };\n const shape = (sh, spread) => {\n let answer = NONE;\n for (const key of Reflect.ownKeys(sh)) {\n const desc = Object.getOwnPropertyDescriptor(sh, key);\n if (spread && !desc.enumerable) continue;\n const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve) : NONE;\n if (child > answer) answer = child;\n }\n return answer;\n };\n const merge = (answer) => {\n if (answer > result) result = answer;\n };\n const def = inst._zod.def;\n switch (def.type) {\n case \"object\": {\n const raw = rawShape(def);\n merge(raw ? shape(raw, true) : ASSUMED);\n check(def.catchall);\n break;\n }\n case \"array\":\n check(def.element);\n break;\n case \"tuple\":\n for (const el of def.items) check(el);\n check(def.rest);\n break;\n case \"record\":\n case \"map\":\n check(def.keyType);\n check(def.valueType);\n break;\n case \"set\":\n check(def.valueType);\n break;\n case \"union\":\n for (const el of def.options) check(el);\n break;\n case \"intersection\":\n check(def.left);\n check(def.right);\n break;\n case \"optional\":\n case \"nullable\":\n case \"default\":\n case \"prefault\":\n case \"catch\":\n case \"readonly\":\n case \"nonoptional\":\n case \"promise\":\n case \"success\":\n check(def.innerType);\n break;\n case \"pipe\":\n check(def.in);\n check(def.out);\n break;\n case \"function\":\n check(def.input);\n check(def.output);\n break;\n case \"lazy\": {\n const inner = def._cachedInner ?? (resolve ? inst._zod.innerType : void 0);\n merge(inner ? isRecursive(inner, stack, false) : ASSUMED);\n break;\n }\n case \"template_literal\":\n case \"string\":\n case \"number\":\n case \"int\":\n case \"boolean\":\n case \"bigint\":\n case \"symbol\":\n case \"undefined\":\n case \"null\":\n case \"void\":\n case \"never\":\n case \"any\":\n case \"unknown\":\n case \"date\":\n case \"nan\":\n case \"enum\":\n case \"literal\":\n case \"file\":\n case \"transform\":\n case \"custom\": break;\n default: for (const key in def) {\n const desc = Object.getOwnPropertyDescriptor(def, key);\n if (!desc || desc.get) continue;\n const value = desc.value;\n if (!value || typeof value !== \"object\") continue;\n if (value._zod) check(value);\n else if (Array.isArray(value)) for (const el of value) check(el);\n }\n }\n stack.delete(inst);\n return settle(inst, result);\n}\n/** An assumed answer must not outlive the resolution that settles it, so only a certain one is cached. */\nfunction settle(inst, answer) {\n if (answer !== ASSUMED) recursive.set(inst, answer === PROVEN);\n return answer;\n}\nfunction bucketFor(state, inst) {\n let bucket = state.buckets.get(inst);\n if (!bucket) {\n bucket = /* @__PURE__ */ new WeakMap();\n state.buckets.set(inst, bucket);\n }\n return bucket;\n}\nlet handoff;\nconst open = [];\nconst memo = {\n alloc(_inst, payload, empty) {\n const bucket = handoff;\n if (!bucket) return empty;\n handoff = void 0;\n const entry = {\n value: empty,\n issues: null\n };\n bucket.set(payload.value, entry);\n open.push(entry);\n return empty;\n },\n guard(inst) {\n var _a;\n (_a = inst._zod).deferred ?? (_a.deferred = []);\n inst._zod.deferred.push(() => {\n const base = inst._zod.parse;\n const wrapped = (payload, ctx) => {\n if (ctx.direction !== \"backward\" && isBackEdge(ctx, payload.value)) throw new $ZodCyclicError();\n return base(payload, ctx);\n };\n inst._zod.parse = wrapped;\n if (inst._zod.run === base) inst._zod.run = wrapped;\n });\n },\n attach(inst) {\n var _a;\n let isRecursiveInst;\n let rechecked = false;\n let lastCtx;\n let lastBucket;\n (_a = inst._zod).deferred ?? (_a.deferred = []);\n inst._zod.deferred.push(() => {\n const base = inst._zod.parse;\n const wrapped = (payload, ctx) => {\n if (isRecursiveInst === void 0) {\n const walked = isRecursive(inst, /* @__PURE__ */ new Set(), false);\n if (walked === NONE) {\n inst._zod.parse = base;\n if (inst._zod.run === wrapped) inst._zod.run = base;\n return base(payload, ctx);\n }\n if (walked === PROVEN || rechecked) isRecursiveInst = true;\n else rechecked = true;\n }\n const input = payload.value;\n if (!isRef(input)) return base(payload, ctx);\n let state = ctx[STATE];\n if (!state) {\n state = {\n buckets: /* @__PURE__ */ new WeakMap(),\n backEdges: void 0\n };\n ctx[STATE] = state;\n }\n let bucket;\n if (lastCtx === ctx) bucket = lastBucket;\n else {\n bucket = bucketFor(state, inst);\n lastCtx = ctx;\n lastBucket = bucket;\n }\n const hit = bucket.get(input);\n if (hit) {\n payload.value = hit.value;\n if (hit.issues) {\n if (hit.issues.length) payload.issues.push(...cloneIssues(hit.issues));\n } else {\n payload.memo = true;\n state.backEdges ?? (state.backEdges = /* @__PURE__ */ new WeakSet());\n state.backEdges.add(hit.value);\n }\n return payload;\n }\n handoff = bucket;\n const depth = open.length;\n const result = base(payload, ctx);\n handoff = void 0;\n const entry = open.length > depth ? open.pop() : void 0;\n if (result instanceof Promise) return result.then((r) => {\n if (entry) entry.issues = r.issues.length ? cloneIssues(r.issues) : NO_ISSUES;\n return r;\n });\n if (entry) entry.issues = result.issues.length ? cloneIssues(result.issues) : NO_ISSUES;\n return result;\n };\n inst._zod.parse = wrapped;\n if (inst._zod.run === base) inst._zod.run = wrapped;\n });\n }\n};\n/** The memoizer that gives containers cycle support. `zod` installs it by default; `zod/mini` opts in with `config({ memoizer: memoizer() })`. */\nfunction memoizer() {\n return memo;\n}\n/** Whether this value is a node a back-edge resolved to before it finished. */\nfunction isBackEdge(ctx, value) {\n const backEdges = ctx[STATE]?.backEdges;\n return backEdges !== void 0 && isRef(value) && backEdges.has(value);\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/registries.js\nvar _a;\nvar $ZodRegistry = class {\n constructor() {\n this._map = /* @__PURE__ */ new WeakMap();\n this._idmap = /* @__PURE__ */ new Map();\n }\n add(schema, ..._meta) {\n const meta = _meta[0];\n this._map.set(schema, meta);\n if (meta && typeof meta === \"object\" && \"id\" in meta) this._idmap.set(meta.id, schema);\n return this;\n }\n clear() {\n this._map = /* @__PURE__ */ new WeakMap();\n this._idmap = /* @__PURE__ */ new Map();\n return this;\n }\n remove(schema) {\n const meta = this._map.get(schema);\n if (meta && typeof meta === \"object\" && \"id\" in meta) this._idmap.delete(meta.id);\n this._map.delete(schema);\n return this;\n }\n get(schema) {\n const p = schema._zod.parent;\n if (p) {\n const pm = { ...this.get(p) ?? {} };\n delete pm.id;\n const f = {\n ...pm,\n ...this._map.get(schema)\n };\n return Object.keys(f).length ? f : void 0;\n }\n return this._map.get(schema);\n }\n has(schema) {\n return this._map.has(schema);\n }\n};\nfunction registry() {\n return new $ZodRegistry();\n}\n(_a = globalThis).__zod_globalRegistry ?? (_a.__zod_globalRegistry = registry());\nconst globalRegistry = globalThis.__zod_globalRegistry;\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 _email(Class, params) {\n return new Class({\n type: \"string\",\n format: \"email\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _guid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"guid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _uuid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"uuid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _uuidv4(Class, params) {\n return new Class({\n type: \"string\",\n format: \"uuid\",\n check: \"string_format\",\n abort: false,\n version: \"v4\",\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _uuidv6(Class, params) {\n return new Class({\n type: \"string\",\n format: \"uuid\",\n check: \"string_format\",\n abort: false,\n version: \"v6\",\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _uuidv7(Class, params) {\n return new Class({\n type: \"string\",\n format: \"uuid\",\n check: \"string_format\",\n abort: false,\n version: \"v7\",\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _url(Class, params) {\n return new Class({\n type: \"string\",\n format: \"url\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _emoji(Class, params) {\n return new Class({\n type: \"string\",\n format: \"emoji\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _nanoid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"nanoid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n/**\n* @deprecated CUID v1 is deprecated by its authors due to information leakage\n* (timestamps embedded in the id). Use {@link _cuid2} instead.\n* See https://github.com/paralleldrive/cuid.\n*/\n// @__NO_SIDE_EFFECTS__\nfunction _cuid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"cuid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _cuid2(Class, params) {\n return new Class({\n type: \"string\",\n format: \"cuid2\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _ulid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"ulid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _xid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"xid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _ksuid(Class, params) {\n return new Class({\n type: \"string\",\n format: \"ksuid\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _ipv4(Class, params) {\n return new Class({\n type: \"string\",\n format: \"ipv4\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _ipv6(Class, params) {\n return new Class({\n type: \"string\",\n format: \"ipv6\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _cidrv4(Class, params) {\n return new Class({\n type: \"string\",\n format: \"cidrv4\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _cidrv6(Class, params) {\n return new Class({\n type: \"string\",\n format: \"cidrv6\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _base64(Class, params) {\n return new Class({\n type: \"string\",\n format: \"base64\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _base64url(Class, params) {\n return new Class({\n type: \"string\",\n format: \"base64url\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _e164(Class, params) {\n return new Class({\n type: \"string\",\n format: \"e164\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _jwt(Class, params) {\n return new Class({\n type: \"string\",\n format: \"jwt\",\n check: \"string_format\",\n abort: false,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _isoDateTime(Class, params) {\n return new Class({\n type: \"string\",\n format: \"datetime\",\n check: \"string_format\",\n offset: false,\n local: false,\n precision: null,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _isoDate(Class, params) {\n return new Class({\n type: \"string\",\n format: \"date\",\n check: \"string_format\",\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _isoTime(Class, params) {\n return new Class({\n type: \"string\",\n format: \"time\",\n check: \"string_format\",\n precision: null,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _isoDuration(Class, params) {\n return new Class({\n type: \"string\",\n format: \"duration\",\n check: \"string_format\",\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _unknown(Class) {\n return new Class({ type: \"unknown\" });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _never(Class, params) {\n return new Class({\n type: \"never\",\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _maxLength(maximum, params) {\n return new $ZodCheckMaxLength({\n check: \"max_length\",\n ...normalizeParams(params),\n maximum\n });\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// @__NO_SIDE_EFFECTS__\nfunction _length(length, params) {\n return new $ZodCheckLengthEquals({\n check: \"length_equals\",\n ...normalizeParams(params),\n length\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _regex(pattern, params) {\n return new $ZodCheckRegex({\n check: \"string_format\",\n format: \"regex\",\n ...normalizeParams(params),\n pattern\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _lowercase(params) {\n return new $ZodCheckLowerCase({\n check: \"string_format\",\n format: \"lowercase\",\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _uppercase(params) {\n return new $ZodCheckUpperCase({\n check: \"string_format\",\n format: \"uppercase\",\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _includes(includes, params) {\n return new $ZodCheckIncludes({\n check: \"string_format\",\n format: \"includes\",\n ...normalizeParams(params),\n includes\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _startsWith(prefix, params) {\n return new $ZodCheckStartsWith({\n check: \"string_format\",\n format: \"starts_with\",\n ...normalizeParams(params),\n prefix\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _endsWith(suffix, params) {\n return new $ZodCheckEndsWith({\n check: \"string_format\",\n format: \"ends_with\",\n ...normalizeParams(params),\n suffix\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _overwrite(tx) {\n return new $ZodCheckOverwrite({\n check: \"overwrite\",\n tx\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _normalize(form) {\n return /* @__PURE__ */ _overwrite((input) => input.normalize(form));\n}\n// @__NO_SIDE_EFFECTS__\nfunction _trim() {\n return /* @__PURE__ */ _overwrite((input) => input.trim());\n}\n// @__NO_SIDE_EFFECTS__\nfunction _toLowerCase() {\n return /* @__PURE__ */ _overwrite((input) => input.toLowerCase());\n}\n// @__NO_SIDE_EFFECTS__\nfunction _toUpperCase() {\n return /* @__PURE__ */ _overwrite((input) => input.toUpperCase());\n}\n// @__NO_SIDE_EFFECTS__\nfunction _slugify() {\n return /* @__PURE__ */ _overwrite((input) => slugify(input));\n}\n// @__NO_SIDE_EFFECTS__\nfunction _array(Class, element, params) {\n return new Class({\n type: \"array\",\n element,\n ...normalizeParams(params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _refine(Class, fn, _params) {\n return new Class({\n type: \"custom\",\n check: \"custom\",\n fn,\n ...normalizeParams(_params)\n });\n}\n// @__NO_SIDE_EFFECTS__\nfunction _superRefine(fn, params) {\n const ch = /* @__PURE__ */ _check((payload) => {\n payload.addIssue = (issue$2) => {\n if (typeof issue$2 === \"string\") payload.issues.push(issue(issue$2, payload.value, ch._zod.def));\n else {\n const _issue = issue$2;\n if (_issue.fatal) _issue.continue = false;\n _issue.code ?? (_issue.code = \"custom\");\n if (!(\"input\" in _issue)) _issue.input = payload.value;\n _issue.inst ?? (_issue.inst = ch);\n _issue.continue ?? (_issue.continue = !ch._zod.def.abort);\n payload.issues.push(issue(_issue));\n }\n };\n return fn(payload.value, payload);\n }, params);\n return ch;\n}\n// @__NO_SIDE_EFFECTS__\nfunction _check(fn, params) {\n const ch = new $ZodCheck({\n check: \"custom\",\n ...normalizeParams(params)\n });\n ch._zod.check = fn;\n return ch;\n}\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/to-json-schema.js\nfunction assignProps(target, ...sources) {\n for (const source of sources) for (const key of Reflect.ownKeys(source)) if (Object.prototype.propertyIsEnumerable.call(source, key)) assignProp(target, key, source[key]);\n return target;\n}\nfunction initializeContext(params) {\n let target = params?.target ?? \"draft-2020-12\";\n if (target === \"draft-4\") target = \"draft-04\";\n if (target === \"draft-7\") target = \"draft-07\";\n return {\n processors: params.processors ?? {},\n metadataRegistry: params?.metadata ?? globalRegistry,\n target,\n unrepresentable: params?.unrepresentable ?? \"throw\",\n override: params?.override ?? (() => {}),\n io: params?.io ?? \"output\",\n counter: 0,\n seen: /* @__PURE__ */ new Map(),\n sharedDefsExtractedFor: void 0,\n sharedEmitDoneFor: void 0,\n cycles: params?.cycles ?? \"ref\",\n reused: params?.reused ?? \"inline\",\n intersections: [],\n deferred: [],\n external: params?.external ?? void 0\n };\n}\n/**\n* Applies the `unrepresentable` setting at a site that has no JSON Schema equivalent. Throws\n* `message` unless the setting (or the handler's return value) says otherwise. Returns `true` if a\n* custom JSON Schema was written into `json`, in which case the caller must not write its own.\n*/\nfunction handleUnrepresentable(schema, ctx, json, params, message) {\n const result = typeof ctx.unrepresentable === \"function\" ? ctx.unrepresentable({\n zodSchema: schema,\n path: params.path,\n message\n }) : ctx.unrepresentable;\n if (result === \"any\") return false;\n if (result === void 0 || result === \"throw\") throw new Error(message);\n Object.assign(json, result);\n return true;\n}\nfunction processSchema(schema, ctx, _params = {\n path: [],\n schemaPath: []\n}) {\n var _a;\n const def = schema._zod.def;\n const seen = ctx.seen.get(schema);\n if (seen) {\n seen.count++;\n if (_params.schemaPath.includes(schema)) seen.cycle = _params.path;\n return seen.schema;\n }\n const result = {\n schema: {},\n count: 1,\n cycle: void 0,\n path: _params.path\n };\n ctx.seen.set(schema, result);\n ctx.sharedDefsExtractedFor = void 0;\n ctx.sharedEmitDoneFor = void 0;\n const overrideSchema = schema._zod.toJSONSchema?.();\n if (overrideSchema) result.schema = overrideSchema;\n else {\n const params = {\n ..._params,\n schemaPath: [..._params.schemaPath, schema],\n path: _params.path\n };\n if (schema._zod.processJSONSchema) schema._zod.processJSONSchema(ctx, result.schema, params);\n else {\n const _json = result.schema;\n const processor = ctx.processors[def.type];\n if (!processor) throw new Error(`[toJSONSchema]: Non-representable type encountered: ${def.type}`);\n processor(schema, ctx, _json, params);\n }\n const parent = schema._zod.parent;\n if (parent) {\n if (!result.ref) result.ref = parent;\n processSchema(parent, ctx, params);\n ctx.seen.get(parent).isParent = true;\n }\n }\n const meta = ctx.metadataRegistry.get(schema);\n if (meta) assignProps(result.schema, meta);\n if (ctx.io === \"input\" && isTransforming(schema)) {\n delete result.schema.examples;\n delete result.schema.default;\n }\n if (ctx.io === \"input\" && \"_prefault\" in result.schema) (_a = result.schema).default ?? (_a.default = result.schema._prefault);\n delete result.schema._prefault;\n return ctx.seen.get(schema).schema;\n}\nfunction encodeJSONPointerSegment(segment) {\n return segment.replace(/~/g, \"~0\").replace(/\\//g, \"~1\");\n}\nfunction extractDefs(ctx, schema) {\n const root = ctx.seen.get(schema);\n if (!root) throw new Error(\"Unprocessed schema. This is a bug in Zod.\");\n if (ctx.external && ctx.sharedDefsExtractedFor === ctx.external) return;\n const idToSchema = /* @__PURE__ */ new Map();\n for (const entry of ctx.seen.entries()) {\n const id = ctx.metadataRegistry.get(entry[0])?.id;\n if (id) {\n const existing = idToSchema.get(id);\n if (existing && existing !== entry[0]) throw new Error(`Duplicate schema id \"${id}\" detected during JSON Schema conversion. Two different schemas cannot share the same id when converted together.`);\n idToSchema.set(id, entry[0]);\n }\n }\n const makeURI = (entry) => {\n const defsSegment = ctx.target === \"draft-2020-12\" ? \"$defs\" : \"definitions\";\n if (ctx.external) {\n const externalId = ctx.external.registry.get(entry[0])?.id;\n const uriGenerator = ctx.external.uri ?? ((id) => id);\n if (externalId) return { ref: uriGenerator(externalId) };\n const id = entry[1].defId ?? entry[1].schema.id ?? `schema${ctx.counter++}`;\n entry[1].defId = id;\n return {\n defId: id,\n ref: `${uriGenerator(\"__shared\")}#/${defsSegment}/${encodeJSONPointerSegment(id)}`\n };\n }\n const uriPrefix = `#`;\n const defUriPrefix = `${uriPrefix}/${defsSegment}/`;\n if (entry[1] === root && !entry[1].schema.id) return { ref: uriPrefix };\n const defId = entry[1].schema.id ?? `__schema${ctx.counter++}`;\n return {\n defId,\n ref: defUriPrefix + encodeJSONPointerSegment(defId)\n };\n };\n const extractToDef = (entry) => {\n if (entry[1].schema.$ref) return;\n const seen = entry[1];\n const { ref, defId } = makeURI(entry);\n seen.def = { ...seen.schema };\n if (defId) seen.defId = defId;\n const schema = seen.schema;\n for (const key in schema) delete schema[key];\n schema.$ref = ref;\n };\n if (ctx.cycles === \"throw\") for (const entry of ctx.seen.entries()) {\n const seen = entry[1];\n if (seen.cycle) throw new Error(`Cycle detected: #/${seen.cycle?.join(\"/\")}/<root>\n\nSet the \\`cycles\\` parameter to \\`\"ref\"\\` to resolve cyclical schemas with defs.`);\n }\n for (const entry of ctx.seen.entries()) {\n const seen = entry[1];\n if (schema === entry[0]) {\n extractToDef(entry);\n continue;\n }\n if (ctx.external) {\n const ext = ctx.external.registry.get(entry[0])?.id;\n if (schema !== entry[0] && ext) {\n extractToDef(entry);\n continue;\n }\n }\n if (ctx.metadataRegistry.get(entry[0])?.id) {\n extractToDef(entry);\n continue;\n }\n if (seen.cycle) {\n extractToDef(entry);\n continue;\n }\n if (seen.count > 1) {\n if (ctx.reused === \"ref\") extractToDef(entry);\n }\n }\n if (ctx.external) ctx.sharedDefsExtractedFor = ctx.external;\n}\n/** Rewrites `anyOf: [{type: \"a\"}, {type: \"b\"}]` to `type: [\"a\", \"b\"]`, which every JSON Schema draft treats as equivalent and most consumers render far better for the nullable case. Only branches that are a bare type assertion qualify — anything carrying a constraint, `$ref`, `const` or metadata is left alone. Runs after `flattenRef`, so a branch an override decorated or `$defs` extraction turned into a `$ref` is no longer bare and correctly stays in `anyOf`. `oneOf` is excluded: `integer` and `number` overlap, so \"exactly one\" and \"at least one\" are not the same there. OpenAPI 3.0 is excluded: its `type` must be a single string. */\nfunction compactTypeUnion(schema) {\n const options = schema.anyOf;\n if (!Array.isArray(options) || options.length === 0 || schema.type !== void 0) return;\n const types = [];\n for (const option of options) {\n if (!option || typeof option !== \"object\") return;\n compactTypeUnion(option);\n const keys = Object.keys(option);\n if (keys.length !== 1 || keys[0] !== \"type\") return;\n const type = option.type;\n for (const member of Array.isArray(type) ? type : [type]) {\n if (typeof member !== \"string\") return;\n if (!types.includes(member)) types.push(member);\n }\n }\n delete schema.anyOf;\n schema.type = types.length === 1 ? types[0] : types;\n}\n/** Keywords `foldIntersection` knows how to combine. Anything else — `$ref`, `patternProperties`,\n* an annotation like `description` — makes a member unfoldable, so a constraint this does not\n* understand leaves the `allOf` alone instead of being silently dropped or misattributed. */\nconst FOLDABLE_KEYS = /* @__PURE__ */ new Set([\n \"type\",\n \"properties\",\n \"required\",\n \"additionalProperties\"\n]);\nconst UNION_KEYS = [\"oneOf\", \"anyOf\"];\n/** A member's constraint on a key it does not declare itself. A `catchall` states one; `false`, an absent `additionalProperties`, and the empty schema a loose object emits state nothing. */\nfunction undeclaredConstraint(member) {\n const extra = member.additionalProperties;\n if (extra === void 0 || extra === false || typeof extra !== \"object\" || extra === null) return null;\n return Object.keys(extra).length ? extra : null;\n}\n/** Combines object members into the single object they describe together, or returns `null` if any of them carries a keyword outside {@link FOLDABLE_KEYS}. */\nfunction foldObjects(members) {\n const objects = [];\n for (const member of members) {\n if (typeof member !== \"object\" || member.type !== \"object\") return null;\n for (const key in member) if (!FOLDABLE_KEYS.has(key)) return null;\n objects.push(member);\n }\n const properties = {};\n const required = /* @__PURE__ */ new Set();\n for (const object of objects) {\n for (const key in object.properties) {\n if (Object.prototype.hasOwnProperty.call(properties, key)) continue;\n const parts = [];\n for (const other of objects) {\n const part = other.properties?.[key] ?? undeclaredConstraint(other);\n if (part === null || part === void 0) continue;\n if (!parts.some((seen) => JSON.stringify(seen) === JSON.stringify(part))) parts.push(part);\n }\n const merged = parts.length === 1 ? parts[0] : foldObjects(parts) ?? { allOf: parts };\n assignProp(properties, key, merged);\n }\n for (const key of object.required ?? []) required.add(key);\n }\n const folded = {\n type: \"object\",\n properties\n };\n if (required.size) folded.required = [...required];\n if (objects.every((object) => object.additionalProperties === false)) folded.additionalProperties = false;\n else {\n const constraints = [];\n for (const object of objects) {\n const constraint = undeclaredConstraint(object);\n if (constraint && !constraints.some((seen) => JSON.stringify(seen) === JSON.stringify(constraint))) constraints.push(constraint);\n }\n if (constraints.length === 1) folded.additionalProperties = constraints[0];\n else if (constraints.length > 1) folded.additionalProperties = { allOf: constraints };\n }\n return folded;\n}\n/** `additionalProperties` in an `allOf` member sees only that member's own `properties`, so two\n* closed object members reject each other's keys and the schema validates nothing. Zod's parser\n* pools the key sets instead — `handleIntersectionResults` reports a key as unrecognized only when\n* *every* side rejects it — so the emitted schema has to pool them too, and folding the members\n* into one object is the encoding that says so on every target.\n*\n* This runs from `finalize`, after `extractDefs`, which is what keeps it clear of the `$ref`\n* machinery: a member extracted into `$defs` is already a `$ref` by now and declines to fold, so it\n* keeps its reference and its own closedness rather than being inlined as a stale copy. */\nfunction foldIntersection(json) {\n const allOf = json.allOf;\n if (!Array.isArray(allOf) || allOf.length < 2) return;\n for (const key of FOLDABLE_KEYS) if (key in json) return;\n const unions = allOf.filter((m) => UNION_KEYS.some((k) => Array.isArray(m[k])));\n let folded = null;\n if (!unions.length) folded = foldObjects(allOf);\n else {\n const union = unions[0];\n const keyword = UNION_KEYS.find((k) => Array.isArray(union[k]));\n if (Object.keys(union).length !== 1) return;\n const rest = allOf.filter((m) => m !== union);\n const branches = union[keyword].map((branch) => foldObjects([...rest, branch]));\n if (branches.some((b) => !b)) return;\n folded = { [keyword]: branches };\n }\n if (!folded) return;\n delete json.allOf;\n assignProps(json, folded);\n}\nfunction finalize(ctx, schema) {\n const root = ctx.seen.get(schema);\n if (!root) throw new Error(\"Unprocessed schema. This is a bug in Zod.\");\n const flattenRef = (zodSchema) => {\n const seen = ctx.seen.get(zodSchema);\n if (seen.ref === null) return;\n const schema = seen.def ?? seen.schema;\n const _cached = { ...schema };\n const ref = seen.ref;\n seen.ref = null;\n if (ref) {\n flattenRef(ref);\n const refSeen = ctx.seen.get(ref);\n const refSchema = refSeen.schema;\n if (refSchema.$ref && (ctx.target === \"draft-07\" || ctx.target === \"draft-04\" || ctx.target === \"openapi-3.0\")) {\n schema.allOf = schema.allOf ?? [];\n schema.allOf.push(refSchema);\n } else assignProps(schema, refSchema);\n assignProps(schema, _cached);\n if (zodSchema._zod.parent === ref) for (const key in schema) {\n if (key === \"$ref\" || key === \"allOf\") continue;\n if (!(key in _cached)) delete schema[key];\n }\n if (refSchema.$ref && refSeen.def) for (const key in schema) {\n if (key === \"$ref\" || key === \"allOf\") continue;\n if (key in refSeen.def && JSON.stringify(schema[key]) === JSON.stringify(refSeen.def[key])) delete schema[key];\n }\n }\n const parent = zodSchema._zod.parent;\n if (parent && parent !== ref) {\n flattenRef(parent);\n const parentSeen = ctx.seen.get(parent);\n if (parentSeen?.schema.$ref) {\n schema.$ref = parentSeen.schema.$ref;\n if (parentSeen.def) for (const key in schema) {\n if (key === \"$ref\" || key === \"allOf\") continue;\n if (key in parentSeen.def && JSON.stringify(schema[key]) === JSON.stringify(parentSeen.def[key])) delete schema[key];\n }\n }\n }\n ctx.override({\n zodSchema,\n jsonSchema: schema,\n path: seen.path ?? []\n });\n };\n if (!ctx.external || ctx.sharedEmitDoneFor !== ctx.external) {\n for (const entry of [...ctx.seen.entries()].reverse()) flattenRef(entry[0]);\n if (ctx.target !== \"openapi-3.0\") for (const entry of ctx.seen.entries()) compactTypeUnion(entry[1].def ?? entry[1].schema);\n for (const rewrite of ctx.deferred) rewrite();\n if (ctx.intersections.length) {\n const carriers = /* @__PURE__ */ new Map();\n for (const seen of ctx.seen.values()) for (const json of [seen.schema, seen.def]) {\n const allOf = json?.allOf;\n if (!Array.isArray(allOf)) continue;\n const existing = carriers.get(allOf);\n if (existing) existing.push(json);\n else carriers.set(allOf, [json]);\n }\n for (const allOf of ctx.intersections) for (const json of carriers.get(allOf) ?? []) foldIntersection(json);\n }\n }\n const result = {};\n if (ctx.target === \"draft-2020-12\") result.$schema = \"https://json-schema.org/draft/2020-12/schema\";\n else if (ctx.target === \"draft-07\") result.$schema = \"http://json-schema.org/draft-07/schema#\";\n else if (ctx.target === \"draft-04\") result.$schema = \"http://json-schema.org/draft-04/schema#\";\n else if (ctx.target === \"openapi-3.0\") {}\n if (ctx.external?.uri) {\n const id = ctx.external.registry.get(schema)?.id;\n if (!id) throw new Error(\"Schema is missing an `id` property\");\n result.$id = ctx.external.uri(id);\n }\n assignProps(result, root.defId ? root.schema : root.def ?? root.schema);\n const rootMetaId = ctx.metadataRegistry.get(schema)?.id;\n if (rootMetaId !== void 0 && result.id === rootMetaId) delete result.id;\n const defs = ctx.external?.defs ?? {};\n if (!ctx.external || ctx.sharedEmitDoneFor !== ctx.external) for (const entry of ctx.seen.entries()) {\n const seen = entry[1];\n if (seen.def && seen.defId) {\n if (seen.def.id === seen.defId) delete seen.def.id;\n assignProp(defs, seen.defId, seen.def);\n }\n }\n if (ctx.external) ctx.sharedEmitDoneFor = ctx.external;\n if (ctx.external) {} else if (Object.keys(defs).length > 0) {\n if (ctx.target === \"draft-2020-12\") result.$defs = defs;\n else result.definitions = defs;\n }\n try {\n const finalized = JSON.parse(JSON.stringify(result));\n Object.defineProperty(finalized, \"~standard\", {\n value: {\n ...schema[\"~standard\"],\n jsonSchema: {\n input: createStandardJSONSchemaMethod(schema, \"input\", ctx.processors),\n output: createStandardJSONSchemaMethod(schema, \"output\", ctx.processors)\n }\n },\n enumerable: false,\n writable: false\n });\n return finalized;\n } catch (_err) {\n throw new Error(\"Error converting schema to JSON.\");\n }\n}\nfunction isTransforming(_schema, _ctx) {\n const ctx = _ctx ?? { seen: /* @__PURE__ */ new Set() };\n if (ctx.seen.has(_schema)) return false;\n ctx.seen.add(_schema);\n const def = _schema._zod.def;\n if (def.type === \"transform\") return true;\n if (def.type === \"array\") return isTransforming(def.element, ctx);\n if (def.type === \"set\") return isTransforming(def.valueType, ctx);\n if (def.type === \"lazy\") return isTransforming(def.getter(), ctx);\n if (def.type === \"promise\" || def.type === \"optional\" || def.type === \"nonoptional\" || def.type === \"nullable\" || def.type === \"readonly\" || def.type === \"default\" || def.type === \"prefault\" || def.type === \"catch\") return isTransforming(def.innerType, ctx);\n if (def.type === \"intersection\") return isTransforming(def.left, ctx) || isTransforming(def.right, ctx);\n if (def.type === \"record\" || def.type === \"map\") return isTransforming(def.keyType, ctx) || isTransforming(def.valueType, ctx);\n if (def.type === \"pipe\") {\n if (_schema._zod.traits.has(\"$ZodCodec\")) return true;\n return isTransforming(def.in, ctx) || isTransforming(def.out, ctx);\n }\n if (def.type === \"object\") {\n for (const key in def.shape) if (isTransforming(def.shape[key], ctx)) return true;\n return false;\n }\n if (def.type === \"union\") {\n for (const option of def.options) if (isTransforming(option, ctx)) return true;\n return false;\n }\n if (def.type === \"tuple\") {\n for (const item of def.items) if (isTransforming(item, ctx)) return true;\n if (def.rest && isTransforming(def.rest, ctx)) return true;\n return false;\n }\n return false;\n}\n/**\n* Creates a toJSONSchema method for a schema instance.\n* This encapsulates the logic of initializing context, processing, extracting defs, and finalizing.\n*/\nconst createToJSONSchemaMethod = (schema, processors = {}) => (params) => {\n const ctx = initializeContext({\n ...params,\n processors\n });\n processSchema(schema, ctx);\n extractDefs(ctx, schema);\n return finalize(ctx, schema);\n};\nconst createStandardJSONSchemaMethod = (schema, io, processors = {}) => (params) => {\n const { libraryOptions, target } = params ?? {};\n const ctx = initializeContext({\n ...libraryOptions ?? {},\n target,\n io,\n processors\n });\n processSchema(schema, ctx);\n extractDefs(ctx, schema);\n return finalize(ctx, schema);\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/core/json-schema-processors.js\nconst narrowMin = (agg, key, value) => {\n if (agg[key] === void 0 || value > agg[key]) agg[key] = value;\n};\nconst narrowMax = (agg, key, value) => {\n if (agg[key] === void 0 || value < agg[key]) agg[key] = value;\n};\nconst narrowBoth = (agg, value) => {\n narrowMin(agg, \"minimum\", value);\n narrowMax(agg, \"maximum\", value);\n};\nconst addDivisor = (agg, value) => {\n agg.multipleOf ?? (agg.multipleOf = []);\n if (!agg.multipleOf.includes(value)) agg.multipleOf.push(value);\n};\nconst addPattern = (agg, pattern) => {\n agg.patterns ?? (agg.patterns = /* @__PURE__ */ new Set());\n agg.patterns.add(pattern);\n};\nconst intersectMime = (agg, mime) => {\n agg.mime = agg.mime ? agg.mime.filter((m) => mime.includes(m)) : [...mime];\n};\nconst setFormat = (agg, format) => {\n agg.format = format;\n if (format.includes(\"int\")) agg.isInt = true;\n};\nconst minContributor = (agg, def) => narrowMin(agg, \"minimum\", def.minimum);\nconst maxContributor = (agg, def) => narrowMax(agg, \"maximum\", def.maximum);\nconst formatContributor = (ranges) => (agg, def) => {\n setFormat(agg, def.format);\n const [minimum, maximum] = ranges[def.format];\n narrowMin(agg, \"minimum\", minimum);\n narrowMax(agg, \"maximum\", maximum);\n};\nconst contributors = {\n greater_than: (agg, def) => narrowMin(agg, def.inclusive ? \"minimum\" : \"exclusiveMinimum\", def.value),\n less_than: (agg, def) => narrowMax(agg, def.inclusive ? \"maximum\" : \"exclusiveMaximum\", def.value),\n multiple_of: (agg, def) => addDivisor(agg, def.value),\n number_format: formatContributor(NUMBER_FORMAT_RANGES),\n bigint_format: formatContributor(BIGINT_FORMAT_RANGES),\n min_length: minContributor,\n max_length: maxContributor,\n length_equals: (agg, def) => narrowBoth(agg, def.length),\n min_size: minContributor,\n max_size: maxContributor,\n size_equals: (agg, def) => narrowBoth(agg, def.size),\n string_format: (agg, def) => {\n setFormat(agg, def.format);\n if (def.pattern) addPattern(agg, def.pattern);\n if (def.format === \"base64\" || def.format === \"base64url\") agg.contentEncoding = def.format;\n if (def.local || def.precision === -1) agg.laxFormat = true;\n },\n mime_type: (agg, def) => intersectMime(agg, def.mime)\n};\nfunction aggregateChecks(schema) {\n const agg = {};\n const def = schema._zod.def;\n const list = schema._zod.traits.has(\"$ZodCheck\") ? [schema, ...def.checks ?? []] : def.checks ?? [];\n for (const ch of list) contributors[ch._zod.def.check]?.(agg, ch._zod.def);\n const bag = schema._zod.bag;\n if (bag.minimum !== void 0) narrowMin(agg, \"minimum\", bag.minimum);\n if (bag.exclusiveMinimum !== void 0) narrowMin(agg, \"exclusiveMinimum\", bag.exclusiveMinimum);\n if (bag.maximum !== void 0) narrowMax(agg, \"maximum\", bag.maximum);\n if (bag.exclusiveMaximum !== void 0) narrowMax(agg, \"exclusiveMaximum\", bag.exclusiveMaximum);\n if (bag.multipleOf !== void 0) addDivisor(agg, bag.multipleOf);\n if (bag.format !== void 0) {\n agg.format ?? (agg.format = bag.format);\n if (bag.format.includes(\"int\")) agg.isInt = true;\n }\n if (bag.mime) intersectMime(agg, bag.mime);\n for (const pattern of bag.patterns ?? []) addPattern(agg, pattern);\n return agg;\n}\nconst formatMap = {\n guid: \"uuid\",\n url: \"uri\",\n datetime: \"date-time\",\n json_string: \"json-string\",\n regex: \"\"\n};\nconst exactPatterns = /* @__PURE__ */ new Map([[base64Charset, base64], [base64urlCharset, base64url]]);\nconst exactPattern = (p) => exactPatterns.get(p) ?? p;\nconst stringProcessor = (schema, ctx, _json, _params) => {\n const json = _json;\n json.type = \"string\";\n const { minimum, maximum, format, patterns, contentEncoding, laxFormat } = aggregateChecks(schema);\n if (typeof minimum === \"number\") json.minLength = minimum;\n if (typeof maximum === \"number\") json.maxLength = maximum;\n if (format) {\n json.format = formatMap[format] ?? format;\n if (json.format === \"\") delete json.format;\n if (format === \"time\" || laxFormat) delete json.format;\n }\n if (contentEncoding) json.contentEncoding = contentEncoding;\n if (patterns && patterns.size > 0) {\n const patternList = [...patterns].map(exactPattern);\n if (patternList.length === 1) json.pattern = patternList[0].source;\n else if (patternList.length > 1) json.allOf = [...patternList.map((regex) => ({\n ...ctx.target === \"draft-07\" || ctx.target === \"draft-04\" || ctx.target === \"openapi-3.0\" ? { type: \"string\" } : {},\n pattern: regex.source\n }))];\n }\n};\nconst neverProcessor = (_schema, _ctx, json, _params) => {\n json.not = {};\n};\nconst unknownProcessor = (_schema, _ctx, _json, _params) => {};\nconst enumProcessor = (schema, _ctx, json, _params) => {\n const def = schema._zod.def;\n const values = getEnumValues(def.entries);\n if (values.length === 0) {\n json.not = {};\n return;\n }\n if (values.every((v) => typeof v === \"number\")) json.type = \"number\";\n if (values.every((v) => typeof v === \"string\")) json.type = \"string\";\n json.enum = values;\n};\nconst customProcessor = (schema, ctx, json, params) => {\n handleUnrepresentable(schema, ctx, json, params, \"Custom types cannot be represented in JSON Schema\");\n};\nconst transformProcessor = (schema, ctx, json, params) => {\n handleUnrepresentable(schema, ctx, json, params, \"Transforms cannot be represented in JSON Schema\");\n};\nconst arrayProcessor = (schema, ctx, _json, params) => {\n const json = _json;\n const def = schema._zod.def;\n const { minimum, maximum } = aggregateChecks(schema);\n if (typeof minimum === \"number\") json.minItems = minimum;\n if (typeof maximum === \"number\") json.maxItems = maximum;\n json.type = \"array\";\n json.items = processSchema(def.element, ctx, {\n ...params,\n path: [...params.path, \"items\"]\n });\n};\nfunction inputOptin(schema) {\n const def = schema._zod.def;\n if (def.type === \"pipe\" && def.in._zod.traits.has(\"$ZodTransform\")) return inputOptin(def.out);\n if (def.type === \"catch\") return inputOptin(def.innerType);\n return schema._zod.optin;\n}\nconst objectProcessor = (schema, ctx, _json, params) => {\n const json = _json;\n const def = schema._zod.def;\n const shape = def.shape;\n if (Object.getOwnPropertySymbols(shape).length && handleUnrepresentable(schema, ctx, json, params, \"Symbol keys cannot be represented in JSON Schema\")) return;\n json.type = \"object\";\n json.properties = {};\n for (const key in shape) assignProp(json.properties, key, processSchema(shape[key], ctx, {\n ...params,\n path: [\n ...params.path,\n \"properties\",\n key\n ]\n }));\n const requiredKeys = [];\n for (const key of Object.keys(shape)) {\n const field = def.shape[key];\n if (ctx.io === \"input\" ? inputOptin(field) === void 0 : field._zod.optout === void 0) requiredKeys.push(key);\n }\n if (requiredKeys.length > 0) json.required = requiredKeys;\n if (def.catchall?._zod.def.type === \"never\") json.additionalProperties = false;\n else if (!def.catchall) {\n if (ctx.io === \"output\") json.additionalProperties = false;\n } else if (def.catchall) json.additionalProperties = processSchema(def.catchall, ctx, {\n ...params,\n path: [...params.path, \"additionalProperties\"]\n });\n};\nconst unionProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n const isExclusive = def.inclusive === false;\n const options = def.options.map((x, i) => processSchema(x, ctx, {\n ...params,\n path: [\n ...params.path,\n isExclusive ? \"oneOf\" : \"anyOf\",\n i\n ]\n }));\n if (isExclusive) json.oneOf = options;\n else json.anyOf = options;\n};\nconst intersectionProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n const a = processSchema(def.left, ctx, {\n ...params,\n path: [\n ...params.path,\n \"allOf\",\n 0\n ]\n });\n const b = processSchema(def.right, ctx, {\n ...params,\n path: [\n ...params.path,\n \"allOf\",\n 1\n ]\n });\n const isSimpleIntersection = (val) => \"allOf\" in val && Object.keys(val).length === 1;\n const allOf = [...isSimpleIntersection(a) ? a.allOf : [a], ...isSimpleIntersection(b) ? b.allOf : [b]];\n json.allOf = allOf;\n ctx.intersections.push(allOf);\n};\n/** JSON object keys are always strings, so a numeric record key schema is re-expressed over the\n* numeric-string form the record parser matches. Deferred to `finalize`, after the flatten: a key\n* behind a wrapper only carries its own `type` before then, and a union key only has its branches.\n*\n* A numeric bound cannot apply to a property name, so `minimum` and its siblings are dropped rather\n* than carried over: keeping them beside `type: \"string\"` reproduces the match-nothing schema this\n* exists to fix. A key that carries one therefore emits wider than the record parses — `z.record(z.number().min(5), V)`\n* accepts `\"3\"` — which is the deliberate trade, since throwing on it would reject an ordinary schema\n* outright. */\nfunction stringifyKeyNames(bySchema, json, visited) {\n if (json.$ref) {\n if (visited.has(json)) return json;\n visited.add(json);\n const def = bySchema.get(json)?.def;\n if (!def) return json;\n const inlined = stringifyKeyNames(bySchema, def, visited);\n return inlined === def ? json : inlined;\n }\n for (const keyword of [\"anyOf\", \"oneOf\"]) {\n const branches = json[keyword];\n if (!Array.isArray(branches)) continue;\n const mapped = branches.map((branch) => stringifyKeyNames(bySchema, branch, visited));\n if (mapped.some((branch, i) => branch !== branches[i])) json = {\n ...json,\n [keyword]: mapped\n };\n }\n const types = Array.isArray(json.type) ? json.type : [json.type];\n const numericType = !types.includes(\"string\") && types.some((t) => t === \"number\" || t === \"integer\");\n const values = json.enum ?? (json.const !== void 0 ? [json.const] : void 0);\n if (!numericType && !values?.some((v) => typeof v === \"number\")) return json;\n const { minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, format, id, ...rest } = json;\n if (rest.enum) rest.enum = rest.enum.map((v) => typeof v === \"number\" ? String(v) : v);\n else if (typeof rest.const === \"number\") rest.const = String(rest.const);\n if (!numericType) return rest;\n rest.type = \"string\";\n if (!values) rest.pattern = (types.includes(\"number\") ? number : integer).source;\n return rest;\n}\n/** Every record of one conversion, so the carriers are found in a single pass rather than once per record. */\nconst pendingRecords = /* @__PURE__ */ new WeakMap();\nfunction rewriteKeyNames(ctx) {\n const bySchema = /* @__PURE__ */ new Map();\n for (const entry of ctx.seen.values()) if (entry.def && !bySchema.has(entry.schema)) bySchema.set(entry.schema, entry);\n const rewrites = /* @__PURE__ */ new Map();\n for (const record of pendingRecords.get(ctx) ?? []) {\n const seen = ctx.seen.get(record);\n const names = (seen?.def ?? seen?.schema)?.propertyNames;\n if (!names || names === true || rewrites.has(names)) continue;\n const rewritten = stringifyKeyNames(bySchema, names, /* @__PURE__ */ new Set());\n if (rewritten !== names) rewrites.set(names, rewritten);\n }\n if (!rewrites.size) return;\n for (const entry of ctx.seen.values()) for (const carrier of [entry.schema, entry.def]) {\n const rewritten = carrier && rewrites.get(carrier.propertyNames);\n if (rewritten) carrier.propertyNames = rewritten;\n }\n}\nconst recordProcessor = (schema, ctx, _json, params) => {\n const json = _json;\n const def = schema._zod.def;\n json.type = \"object\";\n const keyType = def.keyType;\n const patterns = aggregateChecks(keyType).patterns;\n if (def.mode === \"loose\" && patterns && patterns.size > 0) {\n const valueSchema = processSchema(def.valueType, ctx, {\n ...params,\n path: [\n ...params.path,\n \"patternProperties\",\n \"*\"\n ]\n });\n json.patternProperties = {};\n for (const pattern of patterns) assignProp(json.patternProperties, exactPattern(pattern).source, valueSchema);\n } else {\n if (ctx.target === \"draft-07\" || ctx.target === \"draft-2020-12\") {\n json.propertyNames = processSchema(def.keyType, ctx, {\n ...params,\n path: [...params.path, \"propertyNames\"]\n });\n let pending = pendingRecords.get(ctx);\n if (!pending) {\n pending = [];\n pendingRecords.set(ctx, pending);\n ctx.deferred.push(() => rewriteKeyNames(ctx));\n }\n pending.push(schema);\n }\n json.additionalProperties = processSchema(def.valueType, ctx, {\n ...params,\n path: [...params.path, \"additionalProperties\"]\n });\n }\n const keyValues = keyType._zod.values;\n const omittableOnInput = ctx.io === \"input\" && inputOptin(def.valueType) !== void 0;\n if (keyValues && !def.partial && !omittableOnInput) {\n const validKeyValues = [...keyValues].filter((v) => typeof v === \"string\" || typeof v === \"number\");\n if (validKeyValues.length > 0) json.required = validKeyValues.map(String);\n }\n};\nconst nullableProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n const inner = processSchema(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n if (ctx.target === \"openapi-3.0\") {\n seen.ref = def.innerType;\n json.nullable = true;\n } else json.anyOf = [inner, { type: \"null\" }];\n};\nconst nonoptionalProcessor = (schema, ctx, _json, params) => {\n const def = schema._zod.def;\n processSchema(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n};\n/** Round-trips a default value through JSON so the emitted schema is guaranteed to be valid JSON.\n* A BigInt has no reliable encoding, so it goes through `unrepresentable` like any other\n* unrepresentable value. Returns a sentinel when the caller must not write a default of its own. */\nconst UNREPRESENTABLE_DEFAULT = Symbol();\nfunction serializeDefaultValue(value, schema, ctx, json, params) {\n let unrepresentable = false;\n const serialized = JSON.stringify(value, (_, val) => {\n if (typeof val !== \"bigint\") return val;\n unrepresentable = true;\n return null;\n });\n if (!unrepresentable) return JSON.parse(serialized);\n handleUnrepresentable(schema, ctx, json, params, \"BigInt defaults cannot be represented in JSON Schema\");\n return UNREPRESENTABLE_DEFAULT;\n}\nconst defaultProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n processSchema(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n const value = serializeDefaultValue(def.defaultValue, schema, ctx, json, params);\n if (value !== UNREPRESENTABLE_DEFAULT) json.default = value;\n};\nconst prefaultProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n processSchema(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n if (ctx.io !== \"input\") return;\n const value = serializeDefaultValue(def.defaultValue, schema, ctx, json, params);\n if (value !== UNREPRESENTABLE_DEFAULT) json._prefault = value;\n};\nconst catchProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n processSchema(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n let catchValue;\n try {\n catchValue = def.catchValue(void 0);\n } catch {\n handleUnrepresentable(schema, ctx, json, params, \"Dynamic catch values are not supported in JSON Schema\");\n return;\n }\n json.default = catchValue;\n};\nconst pipeProcessor = (schema, ctx, _json, params) => {\n const def = schema._zod.def;\n const inIsTransform = def.in._zod.traits.has(\"$ZodTransform\");\n const innerType = ctx.io === \"input\" ? inIsTransform ? def.out : def.in : def.out;\n processSchema(innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = innerType;\n};\nconst readonlyProcessor = (schema, ctx, json, params) => {\n const def = schema._zod.def;\n processSchema(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n json.readOnly = true;\n};\nconst optionalProcessor = (schema, ctx, _json, params) => {\n const def = schema._zod.def;\n processSchema(def.innerType, ctx, params);\n const seen = ctx.seen.get(schema);\n seen.ref = def.innerType;\n};\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/classic/errors.js\nconst _installedErrorProtos = /* @__PURE__ */ new WeakSet([Object.prototype, Error.prototype]);\nfunction _lazyMethod(proto, key, make) {\n Object.defineProperty(proto, key, {\n configurable: true,\n enumerable: false,\n get() {\n const value = make(this);\n Object.defineProperty(this, key, {\n value,\n configurable: true,\n writable: true\n });\n return value;\n },\n set(value) {\n Object.defineProperty(this, key, {\n value,\n configurable: true,\n writable: true\n });\n }\n });\n}\nconst initializer = (inst, issues) => {\n $ZodError.init(inst, issues);\n inst.name = \"ZodError\";\n const proto = Object.getPrototypeOf(inst);\n if (_installedErrorProtos.has(proto)) return;\n _installedErrorProtos.add(proto);\n _lazyMethod(proto, \"format\", (self) => (mapper) => formatError(self, mapper));\n _lazyMethod(proto, \"flatten\", (self) => (mapper) => flattenError(self, mapper));\n _lazyMethod(proto, \"addIssue\", (self) => (issue) => {\n self.issues.push(issue);\n self.message = JSON.stringify(self.issues, jsonStringifyReplacer, 2);\n });\n _lazyMethod(proto, \"addIssues\", (self) => (issues) => {\n self.issues.push(...issues);\n self.message = JSON.stringify(self.issues, jsonStringifyReplacer, 2);\n });\n Object.defineProperty(proto, \"isEmpty\", {\n configurable: true,\n enumerable: false,\n get() {\n return this.issues.length === 0;\n }\n });\n};\nconst ZodRealError = /*@__PURE__*/ $constructor(\"ZodError\", initializer, void 0, { Parent: Error });\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/classic/parse.js\nconst parse = /* @__PURE__ */ _parse(ZodRealError);\nconst parseAsync = /* @__PURE__ */ _parseAsync(ZodRealError);\nconst safeParse = /* @__PURE__ */ _safeParse(ZodRealError);\nconst safeParseAsync = /* @__PURE__ */ _safeParseAsync(ZodRealError);\nconst encode = /* @__PURE__ */ _encode(ZodRealError);\nconst decode = /* @__PURE__ */ _decode(ZodRealError);\nconst encodeAsync = /* @__PURE__ */ _encodeAsync(ZodRealError);\nconst decodeAsync = /* @__PURE__ */ _decodeAsync(ZodRealError);\nconst safeEncode = /* @__PURE__ */ _safeEncode(ZodRealError);\nconst safeDecode = /* @__PURE__ */ _safeDecode(ZodRealError);\nconst safeEncodeAsync = /* @__PURE__ */ _safeEncodeAsync(ZodRealError);\nconst safeDecodeAsync = /* @__PURE__ */ _safeDecodeAsync(ZodRealError);\n\n//#endregion\n//#region ../../node_modules/.pnpm/zod@4.6.5/node_modules/zod/v4/classic/schemas.js\nfunction _ensureDefaultLocale() {\n if (!globalConfig.localeError) config(en_default());\n}\nfunction _ensureDefaultMemoizer() {\n if (!globalConfig.memoizer) config({ memoizer: memoizer() });\n}\nconst ZodType = /*@__PURE__*/ $constructor(\"ZodType\", (inst, def) => {\n _ensureDefaultLocale();\n $ZodType.init(inst, def);\n inst.def = def;\n inst.type = def.type;\n return inst;\n}, {\n check(...chks) {\n const def = this.def;\n return this.clone(mergeDefs(def, { checks: [...def.checks ?? [], ...chks.map((ch) => typeof ch === \"function\" ? { _zod: {\n check: ch,\n def: { check: \"custom\" },\n onattach: []\n } } : ch)] }), { parent: true });\n },\n with(...chks) {\n return this.check(...chks);\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 refine(check, params) {\n return this.check(refine(check, params));\n },\n superRefine(refinement, params) {\n return this.check(superRefine(refinement, params));\n },\n overwrite(fn) {\n return this.check(_overwrite(fn));\n },\n optional() {\n return optional(this);\n },\n exactOptional() {\n return exactOptional(this);\n },\n nullable() {\n return nullable(this);\n },\n nullish() {\n return optional(nullable(this));\n },\n nonoptional(params) {\n return nonoptional(this, params);\n },\n array() {\n return array(this);\n },\n or(arg) {\n return union([this, arg]);\n },\n and(arg) {\n return intersection(this, arg);\n },\n transform(tx) {\n return pipe(this, transform(tx));\n },\n default(d) {\n return _default(this, d);\n },\n prefault(d) {\n return prefault(this, d);\n },\n catch(params) {\n return _catch(this, params);\n },\n pipe(target) {\n return pipe(this, target);\n },\n readonly() {\n return readonly(this);\n },\n describe(description) {\n const cl = this.clone();\n globalRegistry.add(cl, { description });\n return cl;\n },\n meta(...args) {\n if (args.length === 0) return globalRegistry.get(this);\n const cl = this.clone();\n globalRegistry.add(cl, args[0]);\n return cl;\n },\n isOptional() {\n return this.safeParse(void 0).success;\n },\n isNullable() {\n return this.safeParse(null).success;\n },\n apply(fn, ...args) {\n return args.length === 0 ? fn(this) : fn(this, ...args);\n },\n get \"~standard\"() {\n return hide(this, \"~standard\", {\n ...standardProps(this),\n jsonSchema: {\n input: createStandardJSONSchemaMethod(this, \"input\"),\n output: createStandardJSONSchemaMethod(this, \"output\")\n }\n });\n },\n set \"~standard\"(value) {\n own(this, \"~standard\", value);\n },\n parse: function _parse(data, params) {\n return parse(this, data, params, { callee: _parse });\n },\n parseAsync: async function _parseAsync(data, params) {\n return await parseAsync(this, data, params, { callee: _parseAsync });\n },\n safeParse(data, params) {\n return safeParse(this, data, params);\n },\n async safeParseAsync(data, params) {\n return safeParseAsync(this, data, params);\n },\n get spa() {\n return this?.safeParseAsync;\n },\n set spa(value) {\n own(this, \"spa\", value);\n },\n validate(data, params) {\n return validate(this, data, params);\n },\n validateAsync(data, params) {\n return validateAsync$1(this, data, params);\n },\n encode: function _encode(data, params) {\n return encode(this, data, params, { callee: _encode });\n },\n decode: function _decode(data, params) {\n return decode(this, data, params, { callee: _decode });\n },\n encodeAsync: async function _encodeAsync(data, params) {\n return await encodeAsync(this, data, params, { callee: _encodeAsync });\n },\n decodeAsync: async function _decodeAsync(data, params) {\n return await decodeAsync(this, data, params, { callee: _decodeAsync });\n },\n safeEncode(data, params) {\n return safeEncode(this, data, params);\n },\n safeDecode(data, params) {\n return safeDecode(this, data, params);\n },\n async safeEncodeAsync(data, params) {\n return safeEncodeAsync(this, data, params);\n },\n async safeDecodeAsync(data, params) {\n return safeDecodeAsync(this, data, params);\n },\n toJSONSchema(params) {\n return createToJSONSchemaMethod(this, {})(params);\n },\n get description() {\n return globalRegistry.get(this)?.description;\n },\n get _def() {\n return this._zod.def;\n }\n});\n/** @internal */\nconst _ZodString = /*@__PURE__*/ $constructor(\"_ZodString\", (inst, def) => {\n $ZodString.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => stringProcessor(inst, ctx, json, params);\n}, /*@__PURE__*/ derived({\n format: (inst) => aggregateChecks(inst).format ?? null,\n minLength: (inst) => aggregateChecks(inst).minimum ?? null,\n maxLength: (inst) => aggregateChecks(inst).maximum ?? null\n}, {\n regex(...args) {\n return this.check(_regex(...args));\n },\n includes(...args) {\n return this.check(_includes(...args));\n },\n startsWith(...args) {\n return this.check(_startsWith(...args));\n },\n endsWith(...args) {\n return this.check(_endsWith(...args));\n },\n min(...args) {\n return this.check(_minLength(...args));\n },\n max(...args) {\n return this.check(_maxLength(...args));\n },\n length(...args) {\n return this.check(_length(...args));\n },\n nonempty(...args) {\n return this.check(_minLength(1, ...args));\n },\n lowercase(params) {\n return this.check(_lowercase(params));\n },\n uppercase(params) {\n return this.check(_uppercase(params));\n },\n trim() {\n return this.check(_trim());\n },\n normalize(...args) {\n return this.check(_normalize(...args));\n },\n toLowerCase() {\n return this.check(_toLowerCase());\n },\n toUpperCase() {\n return this.check(_toUpperCase());\n },\n slugify() {\n return this.check(_slugify());\n }\n}));\nconst ZodString = /*@__PURE__*/ $constructor(\"ZodString\", (inst, def) => {\n $ZodString.init(inst, def);\n _ZodString.init(inst, def);\n}, {\n email(params) {\n return this.check(_email(ZodEmail, params));\n },\n url(params) {\n return this.check(_url(ZodURL, params));\n },\n jwt(params) {\n return this.check(_jwt(ZodJWT, params));\n },\n emoji(params) {\n return this.check(_emoji(ZodEmoji, params));\n },\n guid(params) {\n return this.check(_guid(ZodGUID, params));\n },\n uuid(params) {\n return this.check(_uuid(ZodUUID, params));\n },\n uuidv4(params) {\n return this.check(_uuidv4(ZodUUID, params));\n },\n uuidv6(params) {\n return this.check(_uuidv6(ZodUUID, params));\n },\n uuidv7(params) {\n return this.check(_uuidv7(ZodUUID, params));\n },\n nanoid(params) {\n return this.check(_nanoid(ZodNanoID, params));\n },\n cuid(params) {\n return this.check(_cuid(ZodCUID, params));\n },\n cuid2(params) {\n return this.check(_cuid2(ZodCUID2, params));\n },\n ulid(params) {\n return this.check(_ulid(ZodULID, params));\n },\n base64(params) {\n return this.check(_base64(ZodBase64, params));\n },\n base64url(params) {\n return this.check(_base64url(ZodBase64URL, params));\n },\n xid(params) {\n return this.check(_xid(ZodXID, params));\n },\n ksuid(params) {\n return this.check(_ksuid(ZodKSUID, params));\n },\n ipv4(params) {\n return this.check(_ipv4(ZodIPv4, params));\n },\n ipv6(params) {\n return this.check(_ipv6(ZodIPv6, params));\n },\n cidrv4(params) {\n return this.check(_cidrv4(ZodCIDRv4, params));\n },\n cidrv6(params) {\n return this.check(_cidrv6(ZodCIDRv6, params));\n },\n e164(params) {\n return this.check(_e164(ZodE164, params));\n },\n datetime(params) {\n return this.check(_isoDateTime(ZodISODateTime, params));\n },\n date(params) {\n return this.check(_isoDate(ZodISODate, params));\n },\n time(params) {\n return this.check(_isoTime(ZodISOTime, params));\n },\n duration(params) {\n return this.check(_isoDuration(ZodISODuration, params));\n }\n});\nfunction string(params) {\n return _string(ZodString, params);\n}\nconst ZodStringFormat = /*@__PURE__*/ $constructor(\"ZodStringFormat\", (inst, def) => {\n $ZodStringFormat.init(inst, def);\n _ZodString.init(inst, def);\n});\nconst ZodISODateTime = /*@__PURE__*/ $constructor(\"ZodISODateTime\", (inst, def) => {\n $ZodISODateTime.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodISODate = /*@__PURE__*/ $constructor(\"ZodISODate\", (inst, def) => {\n $ZodISODate.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodISOTime = /*@__PURE__*/ $constructor(\"ZodISOTime\", (inst, def) => {\n $ZodISOTime.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodISODuration = /*@__PURE__*/ $constructor(\"ZodISODuration\", (inst, def) => {\n $ZodISODuration.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodEmail = /*@__PURE__*/ $constructor(\"ZodEmail\", (inst, def) => {\n $ZodEmail.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodGUID = /*@__PURE__*/ $constructor(\"ZodGUID\", (inst, def) => {\n $ZodGUID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodUUID = /*@__PURE__*/ $constructor(\"ZodUUID\", (inst, def) => {\n $ZodUUID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodURL = /*@__PURE__*/ $constructor(\"ZodURL\", (inst, def) => {\n $ZodURL.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodEmoji = /*@__PURE__*/ $constructor(\"ZodEmoji\", (inst, def) => {\n $ZodEmoji.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodNanoID = /*@__PURE__*/ $constructor(\"ZodNanoID\", (inst, def) => {\n $ZodNanoID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\n/**\n* @deprecated CUID v1 is deprecated by its authors due to information leakage\n* (timestamps embedded in the id). Use {@link ZodCUID2} instead.\n* See https://github.com/paralleldrive/cuid.\n*/\nconst ZodCUID = /*@__PURE__*/ $constructor(\"ZodCUID\", (inst, def) => {\n $ZodCUID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodCUID2 = /*@__PURE__*/ $constructor(\"ZodCUID2\", (inst, def) => {\n $ZodCUID2.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodULID = /*@__PURE__*/ $constructor(\"ZodULID\", (inst, def) => {\n $ZodULID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodXID = /*@__PURE__*/ $constructor(\"ZodXID\", (inst, def) => {\n $ZodXID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodKSUID = /*@__PURE__*/ $constructor(\"ZodKSUID\", (inst, def) => {\n $ZodKSUID.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodIPv4 = /*@__PURE__*/ $constructor(\"ZodIPv4\", (inst, def) => {\n $ZodIPv4.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodIPv6 = /*@__PURE__*/ $constructor(\"ZodIPv6\", (inst, def) => {\n $ZodIPv6.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodCIDRv4 = /*@__PURE__*/ $constructor(\"ZodCIDRv4\", (inst, def) => {\n $ZodCIDRv4.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodCIDRv6 = /*@__PURE__*/ $constructor(\"ZodCIDRv6\", (inst, def) => {\n $ZodCIDRv6.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodBase64 = /*@__PURE__*/ $constructor(\"ZodBase64\", (inst, def) => {\n $ZodBase64.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodBase64URL = /*@__PURE__*/ $constructor(\"ZodBase64URL\", (inst, def) => {\n $ZodBase64URL.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodE164 = /*@__PURE__*/ $constructor(\"ZodE164\", (inst, def) => {\n $ZodE164.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodJWT = /*@__PURE__*/ $constructor(\"ZodJWT\", (inst, def) => {\n $ZodJWT.init(inst, def);\n ZodStringFormat.init(inst, def);\n});\nconst ZodUnknown = /*@__PURE__*/ $constructor(\"ZodUnknown\", (inst, def) => {\n $ZodUnknown.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => unknownProcessor(inst, ctx, json, params);\n});\nfunction unknown() {\n return _unknown(ZodUnknown);\n}\nconst ZodNever = /*@__PURE__*/ $constructor(\"ZodNever\", (inst, def) => {\n $ZodNever.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => neverProcessor(inst, ctx, json, params);\n});\nfunction never(params) {\n return _never(ZodNever, params);\n}\nconst ZodArray = /*@__PURE__*/ $constructor(\"ZodArray\", (inst, def) => {\n _ensureDefaultMemoizer();\n $ZodArray.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => arrayProcessor(inst, ctx, json, params);\n inst.element = def.element;\n}, {\n min(n, params) {\n return this.check(_minLength(n, params));\n },\n nonempty(params) {\n return this.check(_minLength(1, params));\n },\n max(n, params) {\n return this.check(_maxLength(n, params));\n },\n length(n, params) {\n return this.check(_length(n, params));\n },\n unwrap() {\n return this.element;\n }\n});\nfunction array(element, params) {\n return _array(ZodArray, element, params);\n}\nconst ZodObject = /*@__PURE__*/ $constructor(\"ZodObject\", (inst, def) => {\n _ensureDefaultMemoizer();\n $ZodObjectJIT.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => objectProcessor(inst, ctx, json, params);\n installLazyProp(inst, \"shape\", (self) => self._zod.def.shape, false);\n}, {\n keyof() {\n return _enum(Object.keys(this._zod.def.shape));\n },\n catchall(catchall) {\n return this.clone(mergeDefs(this._zod.def, { catchall }));\n },\n passthrough() {\n return this.clone(mergeDefs(this._zod.def, { catchall: unknown() }));\n },\n loose() {\n return this.clone(mergeDefs(this._zod.def, { catchall: unknown() }));\n },\n strict() {\n return this.clone(mergeDefs(this._zod.def, { catchall: never() }));\n },\n strip() {\n return this.clone(mergeDefs(this._zod.def, { catchall: void 0 }));\n },\n extend(incoming) {\n return extend(this, incoming);\n },\n safeExtend(incoming) {\n return safeExtend(this, incoming);\n },\n merge(other) {\n return merge(this, other);\n },\n pick(mask) {\n return pick(this, mask);\n },\n omit(mask) {\n return omit(this, mask);\n },\n partial(...args) {\n return partial(ZodOptional, this, args[0]);\n },\n exactPartial(...args) {\n return partial(ZodExactOptional, this, args[0], \"exactPartial\");\n },\n required(...args) {\n return required(ZodNonOptional, this, args[0]);\n }\n});\nfunction object(shape, params) {\n const def = {\n type: \"object\",\n shape: shape ?? {},\n ...normalizeParams(params)\n };\n return new ZodObject(def);\n}\nconst ZodUnion = /*@__PURE__*/ $constructor(\"ZodUnion\", (inst, def) => {\n $ZodUnion.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => unionProcessor(inst, ctx, json, params);\n inst.options = def.options;\n});\nfunction union(options, params) {\n return new ZodUnion({\n type: \"union\",\n options,\n ...normalizeParams(params)\n });\n}\nconst ZodIntersection = /*@__PURE__*/ $constructor(\"ZodIntersection\", (inst, def) => {\n $ZodIntersection.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => intersectionProcessor(inst, ctx, json, params);\n});\nfunction intersection(left, right) {\n return new ZodIntersection({\n type: \"intersection\",\n left,\n right\n });\n}\nconst ZodRecord = /*@__PURE__*/ $constructor(\"ZodRecord\", (inst, def) => {\n _ensureDefaultMemoizer();\n $ZodRecord.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => recordProcessor(inst, ctx, json, params);\n inst.keyType = def.keyType;\n inst.valueType = def.valueType;\n});\nfunction record(keyType, valueType, params) {\n if (!valueType || !valueType._zod) return new ZodRecord({\n type: \"record\",\n keyType: string(),\n valueType: keyType,\n ...normalizeParams(valueType)\n });\n return new ZodRecord({\n type: \"record\",\n keyType,\n valueType,\n ...normalizeParams(params)\n });\n}\nconst ZodEnum = /*@__PURE__*/ $constructor(\"ZodEnum\", (inst, def) => {\n $ZodEnum.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => enumProcessor(inst, ctx, json, params);\n inst.enum = def.entries;\n inst.options = [...inst._zod.values];\n const keys = new Set(Object.keys(def.entries));\n inst.extract = (values, params) => {\n const newEntries = {};\n for (const value of values) if (keys.has(value)) newEntries[value] = def.entries[value];\n else throw new Error(`Key ${value} not found in enum`);\n return new ZodEnum({\n ...def,\n checks: [],\n ...normalizeParams(params),\n entries: newEntries\n });\n };\n inst.exclude = (values, params) => {\n const newEntries = { ...def.entries };\n for (const value of values) if (keys.has(value)) delete newEntries[value];\n else throw new Error(`Key ${value} not found in enum`);\n return new ZodEnum({\n ...def,\n checks: [],\n ...normalizeParams(params),\n entries: newEntries\n });\n };\n});\nfunction _enum(values, params) {\n const entries = Array.isArray(values) ? Object.fromEntries(values.map((v) => [v, v])) : values;\n return new ZodEnum({\n type: \"enum\",\n entries,\n ...normalizeParams(params)\n });\n}\nconst ZodTransform = /*@__PURE__*/ $constructor(\"ZodTransform\", (inst, def) => {\n _ensureDefaultMemoizer();\n $ZodTransform.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => transformProcessor(inst, ctx, json, params);\n inst._zod.parse = (payload, _ctx) => {\n if (_ctx.direction === \"backward\") throw new $ZodEncodeError(inst.constructor.name);\n payload.addIssue = (issue$1) => {\n if (typeof issue$1 === \"string\") payload.issues.push(issue(issue$1, payload.value, def));\n else {\n const _issue = issue$1;\n if (_issue.fatal) _issue.continue = false;\n _issue.code ?? (_issue.code = \"custom\");\n if (!(\"input\" in _issue)) _issue.input = payload.value;\n _issue.inst ?? (_issue.inst = inst);\n payload.issues.push(issue(_issue));\n }\n };\n const output = def.transform(payload.value, payload);\n if (output instanceof Promise) return output.then((output) => {\n payload.value = output;\n return payload;\n });\n payload.value = output;\n return payload;\n };\n});\nfunction transform(fn) {\n return new ZodTransform({\n type: \"transform\",\n transform: fn\n });\n}\nconst ZodOptional = /*@__PURE__*/ $constructor(\"ZodOptional\", (inst, def) => {\n $ZodOptional.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => optionalProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction optional(innerType) {\n return new ZodOptional({\n type: \"optional\",\n innerType\n });\n}\nconst ZodExactOptional = /*@__PURE__*/ $constructor(\"ZodExactOptional\", (inst, def) => {\n $ZodExactOptional.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => optionalProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction exactOptional(innerType) {\n return new ZodExactOptional({\n type: \"optional\",\n innerType\n });\n}\nconst ZodNullable = /*@__PURE__*/ $constructor(\"ZodNullable\", (inst, def) => {\n $ZodNullable.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => nullableProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction nullable(innerType) {\n return new ZodNullable({\n type: \"nullable\",\n innerType\n });\n}\nconst ZodDefault = /*@__PURE__*/ $constructor(\"ZodDefault\", (inst, def) => {\n $ZodDefault.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => defaultProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n inst.removeDefault = inst.unwrap;\n});\nfunction _default(innerType, defaultValue) {\n return new ZodDefault({\n type: \"default\",\n innerType,\n get defaultValue() {\n return typeof defaultValue === \"function\" ? defaultValue() : shallowClone(defaultValue);\n }\n });\n}\nconst ZodPrefault = /*@__PURE__*/ $constructor(\"ZodPrefault\", (inst, def) => {\n $ZodPrefault.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => prefaultProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction prefault(innerType, defaultValue) {\n return new ZodPrefault({\n type: \"prefault\",\n innerType,\n get defaultValue() {\n return typeof defaultValue === \"function\" ? defaultValue() : shallowClone(defaultValue);\n }\n });\n}\nconst ZodNonOptional = /*@__PURE__*/ $constructor(\"ZodNonOptional\", (inst, def) => {\n $ZodNonOptional.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => nonoptionalProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction nonoptional(innerType, params) {\n return new ZodNonOptional({\n type: \"nonoptional\",\n innerType,\n ...normalizeParams(params)\n });\n}\nconst ZodCatch = /*@__PURE__*/ $constructor(\"ZodCatch\", (inst, def) => {\n $ZodCatch.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => catchProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n inst.removeCatch = inst.unwrap;\n});\nfunction _catch(innerType, catchValue) {\n return new ZodCatch({\n type: \"catch\",\n innerType,\n catchValue: typeof catchValue === \"function\" ? catchValue : constantCatch(catchValue)\n });\n}\nconst ZodPipe = /*@__PURE__*/ $constructor(\"ZodPipe\", (inst, def) => {\n $ZodPipe.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => pipeProcessor(inst, ctx, json, params);\n inst.in = def.in;\n inst.out = def.out;\n});\nfunction pipe(in_, out) {\n return new ZodPipe({\n type: \"pipe\",\n in: in_,\n out\n });\n}\nconst ZodReadonly = /*@__PURE__*/ $constructor(\"ZodReadonly\", (inst, def) => {\n $ZodReadonly.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => readonlyProcessor(inst, ctx, json, params);\n inst.unwrap = () => inst._zod.def.innerType;\n});\nfunction readonly(innerType) {\n return new ZodReadonly({\n type: \"readonly\",\n innerType\n });\n}\nconst ZodCustom = /*@__PURE__*/ $constructor(\"ZodCustom\", (inst, def) => {\n $ZodCustom.init(inst, def);\n ZodType.init(inst, def);\n inst._zod.processJSONSchema = (ctx, json, params) => customProcessor(inst, ctx, json, params);\n});\nfunction refine(fn, _params = {}) {\n return _refine(ZodCustom, fn, _params);\n}\nfunction superRefine(fn, params) {\n return _superRefine(fn, params);\n}\n\n//#endregion\n//#region src/lib/hook-io.ts\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});\n/**\n* Read and parse the full stdin as JSON.\n* Returns a result with the validated HookInput, or the raw data and error on failure.\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});\n/** Parse tool_input as Bash input. Returns null if invalid. */\nfunction parseBashInput(toolInput) {\n const result = BashInputSchema.safeParse(toolInput);\n return result.success ? result.data : null;\n}\n/** Parse tool_input as file-path input (Read, Write, Edit). Returns null if invalid. */\nfunction parseFilePathInput(toolInput) {\n const result = FilePathInputSchema.safeParse(toolInput);\n return result.success ? result.data : null;\n}\n/** Parse tool_input as search input (Glob, Grep). Returns null if invalid. */\nfunction parseSearchInput(toolInput) {\n const result = SearchInputSchema.safeParse(toolInput);\n return result.success ? result.data : null;\n}\nfunction writeDecision(action, detail) {\n const decision = { hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: action,\n ...action === \"deny\" && detail ? { permissionDecisionReason: detail } : {},\n ...action === \"allow\" && detail ? { additionalContext: detail } : {}\n } };\n process.stdout.write(JSON.stringify(decision));\n}\n\n//#endregion\n//#region src/lib/audit.ts\nconst MAX_LOG_SIZE = 1048576;\n/**\n* Resolve the audit log path relative to the hook's own location.\n* Co-located at `.claude/hooks/bst-hook.audit.local.jsonl`.\n*/\nfunction getLogPath() {\n const hookDir = path.dirname(fileURLToPath(import.meta.url));\n return path.join(hookDir, \"bst-hook.audit.local.jsonl\");\n}\n/**\n* Rotate the log file if it exceeds MAX_LOG_SIZE.\n* Renames current log to `.1` and starts fresh.\n* Safe against concurrent rotation -- ENOENT on rename is expected and ignored.\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}\n/** Whether rotation has been checked this session */\nlet rotationChecked = false;\n/**\n* Append an audit entry to the JSONL log.\n* Never throws -- audit failures log to stderr and continue.\n*/\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\n/**\n* Scan a double-quoted string starting at `start` (the char after the opening\n* quote). Returns the unescaped content and the index just past the closing\n* quote, or `null` if the quote is never closed.\n*/\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}\n/**\n* Characters safe to leave unquoted when re-emitting a token. Anything outside\n* this set could be interpreted by the shell-splitter or tokenizer on a\n* subsequent pass (operators, whitespace, quotes, globs, vars, etc.).\n*/\nconst SAFE_UNQUOTED_RE = /^[A-Za-z0-9_\\-./=:@%+,]+$/;\n/** Single-quote-wrap a token unless it only contains safe characters. */\nfunction quoteToken(token) {\n if (token.length > 0 && SAFE_UNQUOTED_RE.test(token)) return token;\n return \"'\" + token.replace(/'/g, \"'\\\\''\") + \"'\";\n}\n/**\n* Inverse of `tokenize`: join tokens into a command string that survives\n* round-tripping through `splitCommand` and `tokenize`.\n*\n* Naive `tokens.join(\" \")` is lossy: any token that was originally quoted\n* loses its quote wrapper and may be mis-parsed when split/tokenized again\n* (e.g. `--testPathPattern=a|b` would split on the embedded `|`).\n*/\nfunction joinTokens(tokens) {\n return tokens.map(quoteToken).join(\" \");\n}\n\n//#endregion\n//#region src/lib/shell-splitter.ts\n/**\n* Split a raw command string into segments on unquoted &&, ||, ;, and | operators.\n* Quote-aware: operators inside single or double quotes are not split points.\n*\n* Returns segments with the operator that follows each one (null for the last segment).\n*/\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\n/** Safe redirect patterns that don't constitute file writes */\nconst SAFE_REDIRECTS = /* @__PURE__ */ new Set([\n \"2>&1\",\n \"2>/dev/null\",\n \">/dev/null\",\n \"1>/dev/null\"\n]);\n/**\n* Bash brace expansion: a `{...}` group containing a comma (list form,\n* `{a,b}`/`{a,}`/`{,a}`) or `..` (range form, `{1..3}`/`{a..z}`) is expanded\n* into multiple words *before* the command runs -- so `--for{c,}e` reaches the\n* program as argv `--force --fore` and `r{m,}m` as `rmm rm`. Braces without a\n* comma or `..` (`a{b}`, `HEAD@{1}`, `{}`) are left literal by bash and must\n* not be flagged.\n*\n* Bash brace expansion is *recursive*: a comma/range list expands even when a\n* branch of it contains its own nested `{...}`, so `--for{c,{}}e` still yields\n* `--force`. A regex cannot express \"a `,` or `..` at balanced brace depth\"\n* (the earlier `/\\{[^{}]*(?:,|\\.\\.)[^{}]*\\}/` required the separator to be\n* flanked by brace-free text out to the enclosing braces, which nested braces\n* defeat while bash still expands). So this is a depth-aware scan: increment on\n* `{`, decrement on `}` (never below 0), and flag the token if a `,` or `..`\n* appears while depth >= 1 -- i.e. anywhere inside any brace group, no matter\n* how deeply nested. Fail-closed: unbalanced braces are flagged too.\n*\n* As with `$`-expansion, the tokenizer has already stripped single quotes by\n* the time `checkInjection` runs, so a genuinely-quoted `'{a,b}'` or\n* `'{\"a\":1,\"b\":2}'` is indistinguishable from an unquoted brace-expansion here\n* and is flagged too -- an accepted fail-closed false positive (passthrough\n* only prompts a human).\n*/\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}\n/**\n* An output-redirect token is safe only when it targets `/dev/null` -- either as\n* a combined token (`>/dev/null`) or a bare operator whose next token is `/dev/null`.\n*/\nfunction isSafeOutputRedirect(token, next) {\n if (token.endsWith(\">/dev/null\")) return true;\n return token.match(/^(&>>|&>|>>|>|1>|2>)$/) !== null && next === \"/dev/null\";\n}\n/**\n* Detect dangerous patterns in tokenized command segments.\n*\n* Checks for:\n* - Command substitution (`$(...)`, backticks)\n* - Any other unresolved `$`-expansion (`${...}`, `$'...'`, `$\"...\"`, `$VAR`, `$1`, ...)\n* - Brace expansion (`{a,b}` list form, `{1..3}` range form)\n* - Process substitution (`<(...)`, `>(...)`)\n* - File redirects (`>`, `>>`, `<` to non-/dev/null targets) -- skipped when `skipRedirects` is true\n* - Newlines in raw segment (injection via line splitting)\n*\n* The tokenizer (`shell-tokenizer.ts`) resolves single-quoted and\n* backslash-escaped content into plain characters before this function ever\n* sees it -- so a token's surviving `$` is indistinguishable between \"bash\n* will expand this\" (`$(...)`, `${...}`, `$'...'`, `$\"...\"`, `$VAR`, `$1`,\n* `$@`, `$?`, ...) and \"this `$` was always a literal character\" (e.g. from\n* `'cost $5'` or `\\$5`). Rather than trying to reconstruct quote context\n* after the fact, any token still containing a `$` is treated as unsafe.\n* This is deliberately fail-closed: it routes the segment to passthrough\n* (a human is prompted) rather than risking an unresolved expansion silently\n* auto-approving or auto-denying based on the wrong argv.\n*\n* ## Expansions this guard knowingly does NOT model\n*\n* Two weaker members of the same \"unmodeled expansion\" family are left open. Both were\n* surfaced by the adversarial review of the issue-attachments hardening and verified\n* empirically against real bash plus this evaluator. **Decision (2026-07-13): accept as\n* documented limitations.** They are recorded here, not as a todo, because the next\n* person to touch this function is the person who needs to know.\n*\n* **Glob reconstruction.** `bst issue:download 273 --forc[e]` (also `--forc?`, `--for*`,\n* `--*`) expands to a real `--force` argv and this returns `safe` -- but *only when a file\n* literally named `--force` already exists in the working directory*. Creating that file\n* is itself hook-gated (`> --force` is caught as a redirect; `touch -- --force` falls to\n* passthrough), so it is a two-step, precondition-gated exploit -- strictly weaker than the\n* unconditional `$`/brace bypasses that were fixed. The same class defeats deny rules\n* (`r[m] -rf`), again only with a pre-existing matching file.\n*\n* *Why not fixed:* closing it means routing any token containing an unquoted glob\n* metacharacter (`*`, `?`, `[`) to passthrough -- which prompts on `ls *.md`, `find . -name\n* '*.ts'`, and nearly every globbing command an agent legitimately runs. Unlike `$` and\n* brace expansion (rare in real commands), globs are everywhere, so the prompt-fatigue\n* cost is real and daily, and a guard people learn to click through protects nobody.\n* Revisit only if a cheaper predicate emerges -- the candidate is to flag a glob metachar\n* **only in a token that also begins with `-`** (a flag position, which is where the\n* reconstruction attack lives), leaving bare-argument globs like `*.md` allowed. That\n* would live right here, beside {@link hasBraceExpansion}.\n*\n* **Tilde expansion.** `~` is unmodeled. Bash only expands it to a home *path* at word\n* start, so it cannot reconstruct a flag or a command name -- it is a path-scope concern,\n* not the flag/command-reconstruction vector this guard defends against. Lowest priority;\n* noted for completeness.\n*\n* If either is ever closed, pin it with regression fixtures the way the nested-brace cases\n* were pinned in `tests/fixtures.ts`.\n*\n* @param skipRedirects When true, skip redirect analysis (used for trusted resolved scripts)\n*/\nfunction checkInjection(tokens, rawSegment, skipRedirects = false) {\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(\">(\") && !SAFE_REDIRECTS.has(token)) return {\n safe: false,\n reason: \"process substitution\"\n };\n }\n if (!skipRedirects) for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i];\n if (SAFE_REDIRECTS.has(token)) continue;\n if (token.includes(\"<\") && !token.includes(\"<(\")) return {\n safe: false,\n reason: \"input redirect\"\n };\n if (token.includes(\">\")) {\n if (isSafeOutputRedirect(token, tokens[i + 1])) continue;\n return {\n safe: false,\n reason: \"output redirect to file\"\n };\n }\n }\n return { safe: true };\n}\n\n//#endregion\n//#region src/lib/env-file.ts\n/**\n* File extensions considered safe documentation templates.\n* Files ending with these suffixes are NOT treated as secret .env files.\n*/\nconst ENV_SAFE_SUFFIXES = [\n \".sample\",\n \".example\",\n \".template\"\n];\n/**\n* Env files owned by a bst command rather than by the developer's secrets.\n*\n* `docker-compose.check.env` is the file `bst docker:check` auto-detects and\n* passes to compose as `--env-file`. It is committed, and it holds placeholder\n* values for a throwaway check stack -- yet it is also the *only* way to feed\n* compose interpolation, so denying it left an agent unable to create or read\n* the one file the check needs. Two bst features blocking each other is worse\n* than the marginal secret risk of a file whose purpose is to hold non-secrets.\n*/\nconst ENV_TOOL_OWNED_SUFFIXES = [\".check.env\"];\n/**\n* Pattern matching .env files: `.env`, `.env.local`, `.env.production`, etc.\n* Does NOT match `.env.sample`, `.env.example`, `.env.template`.\n*/\nconst ENV_FILE_PATTERN = /(^|[/\\\\])(\\.env(\\..*)?|.*\\.env(\\..*)?|\\.env)$/;\n/** Check if a filename is a safe .env documentation template, or a bst-owned env file. */\nfunction isEnvSafeSuffix(filename) {\n return [...ENV_SAFE_SUFFIXES, ...ENV_TOOL_OWNED_SUFFIXES].some((suffix) => filename.endsWith(suffix));\n}\n/**\n* Check if a path targets a .env file (excluding safe templates).\n* Works with both bare filenames and full paths.\n*/\nfunction isEnvFile(filePath) {\n if (isEnvSafeSuffix(path.basename(filePath))) return false;\n return ENV_FILE_PATTERN.test(filePath);\n}\n/**\n* Check if any non-flag token in a Bash command targets a .env file.\n* Used by Bash deny rules for cat, head, tail, grep, etc.\n*/\nfunction tokensTargetEnvFile(tokens) {\n return tokens.slice(1).some((t) => !t.startsWith(\"-\") && isEnvFile(t));\n}\n\n//#endregion\n//#region src/lib/git-state.ts\n/**\n* Return the current branch name, or null when:\n* - cwd is not a git repository,\n* - HEAD is detached,\n* - any git invocation fails.\n*\n* Null is the safe protected default -- callers treat it as \"protected\".\n*/\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 }).toString().trim();\n if (!out || out === \"HEAD\") return null;\n return out;\n } catch {\n return null;\n }\n}\n/**\n* Return the remote default branch (from refs/remotes/origin/HEAD), stripped\n* of the \"origin/\" prefix. Returns null when the symbolic ref isn't set\n* (common for fresh clones with no remote).\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 }).toString().trim();\n if (!out) return null;\n return out.startsWith(\"origin/\") ? out.slice(7) : out;\n } catch {\n return null;\n }\n}\nconst ALWAYS_PROTECTED = [\"main\", \"master\"];\n/**\n* Compute the full protected set: hard-coded {main, master}, plus the remote\n* default branch when available, plus any caller-supplied extras (e.g. from\n* .claude/hooks/bst-hook.settings.json#protectedBranches).\n*\n* Known limitation: if a repo's real default branch is neither \"main\" nor\n* \"master\" AND refs/remotes/origin/HEAD is unset (getRemoteDefaultBranch()\n* returns null -- e.g. a shallow or partial clone that never fetched the\n* symbolic ref), that default branch is absent from this set. A push whose\n* refspec destination is that branch would then pass the protected-branch\n* check in isGitSafePush. This is not treated as a reason to block pushes\n* whenever origin/HEAD is unset, since that would break the far more common\n* case (default branch already covered by {main, master} or by\n* protectedBranches); repos with a non-standard default branch name should\n* set it explicitly via .claude/hooks/bst-hook.settings.json#protectedBranches.\n*/\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}\n/**\n* Configured remote names (`git remote`). Empty when cwd is not a repo or git\n* fails -- the safe default, since callers require membership to auto-approve.\n*/\nfunction getRemotes(cwd) {\n try {\n const out = execFileSync(\"git\", [\"remote\"], {\n cwd,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ]\n }).toString().trim();\n if (!out) return /* @__PURE__ */ new Set();\n return new Set(out.split(\"\\n\").map((line) => line.trim()).filter(Boolean));\n } catch {\n return /* @__PURE__ */ new Set();\n }\n}\n/**\n* True when `cwd` is the repository's *primary* worktree (not a linked one\n* created by `git worktree add`). In the primary worktree the git dir and the\n* common git dir are the same directory; in a linked worktree the git dir is\n* `.../.git/worktrees/<name>` while the common dir is the shared `.../.git`.\n*\n* Fail-safe: any git/filesystem error returns `false` (treated as \"not primary\",\n* i.e. no advisory) so a hiccup never disturbs the decision. Mirrors the\n* \"safe default\" convention of getCurrentBranch.\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 }).toString().trim();\n const commonRaw = execFileSync(\"git\", [\"rev-parse\", \"--git-common-dir\"], {\n cwd,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ]\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\n/** Per-subcommand required-value short option letters. Deliberately excludes\n* `-t` (`--track`): see the note below the value-taking sets.\n*/\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};\n/**\n* Decompose a short-option cluster token for `sub`. Returns `undefined` for\n* anything that is not a short-option cluster: a token that does not start\n* with `-`, the bare `-`, and anything starting with `--`. Every caller keeps\n* handling those tokens exactly as it does today.\n*\n* Otherwise walks the characters after the leading `-` left to right,\n* collecting each into `letters`, and stops at the first character that is a\n* required-value option letter for `sub` -- that letter is the last one\n* collected, and the remainder of the token (possibly empty) becomes\n* `attachedValue`, never inspected as flags again.\n*\n* Worked cases:\n* - `-fc` (switch or checkout) -> letters `[\"f\", \"c\"]`, no attached value --\n* `f` never stops the scan, so `c`/`C` is just collected as another letter\n* unless the subcommand's own value-taking set includes it earlier.\n* - `-cf` (switch) -> letters `[\"c\"]`, attached value `\"f\"`.\n* - `-cfeat/x` (switch) -> letters `[\"c\"]`, attached value `\"feat/x\"`.\n* - `-c` (switch) -> letters `[\"c\"]`, attached value `\"\"` (git reads the\n* branch name from the next argv element).\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\n/**\n* Path-containment helpers that are correct across operating systems.\n*\n* The hook receives a `cwd` from its host. On Windows the same location can be\n* spelled with either drive-letter case (`D:\\proj` vs `d:\\proj`) -- the IDE is\n* inconsistent -- and NTFS treats them as identical. Plain `===` / `startsWith`\n* comparisons on the resolved path therefore misfire whenever the drive-letter\n* case differs, falling through to passthrough (reads) or a false\n* \"outside the project\" deny (writes).\n*\n* The fix: pick the path flavor (win32 vs posix) from the *trusted* base path --\n* `cwd` -- and run every resolve/compare through that flavor. win32's\n* `relative()` is case-insensitive and separator-aware, so drive-letter case no\n* longer matters; posix stays case-sensitive, as that filesystem demands.\n* Deriving the flavor from the base (not the incoming target) means a crafted\n* target can't flip the comparison mode.\n*/\n/** Select the path flavor implied by a base path (a leading drive letter => win32). */\nfunction pathFlavorFor(base) {\n return /^[a-zA-Z]:[\\\\/]/.test(base) ? path.win32 : path.posix;\n}\n/** Resolve `target` against `base` using the flavor implied by `base`. */\nfunction resolveIn(base, target) {\n return pathFlavorFor(base).resolve(base, target);\n}\n/**\n* Whether `target` is `dir` itself or nested beneath it, compared with `dir`'s\n* flavor. `target` is expected to be already resolved (absolute); pass it through\n* `resolveIn` first when it may be relative or contain `..`.\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}\n/** Whether two paths refer to the same location, compared with `a`'s flavor. */\nfunction samePath(a, b) {\n return pathFlavorFor(a).relative(a, b) === \"\";\n}\n/**\n* Whether `filePath` (resolved against `cwd`) is a `.git` directory\n* anywhere under `cwd` -- top-level or nested inside a subdirectory repo --\n* or anything beneath one. Covers both the directory in a normal clone and\n* the gitdir-pointer *file* git writes at the root of a worktree checkout\n* (both cases have a path segment exactly equal to `.git`).\n*\n* A nested repo's git dir (`<cwd>/sub/.git/config`) is just as sensitive as\n* the top-level one: an unguarded write there can add a malicious remote or\n* rewrite push config that a later `cd sub && git push` trusts. So this\n* checks every path segment of `filePath` resolved against `cwd`, not just\n* `<cwd>/.git` -- matching `.git` at *any* depth, as long as the resolved\n* path stays within `cwd` (paths outside `cwd` are handled by the\n* outside-cwd-write rule instead).\n*\n* Matches on a resolved path *segment* exactly equal to `.git`, not a\n* substring, so it does not mistake `.gitignore`, `.gitkeep`, `.github/`,\n* `src/foo.git.ts`, or a directory merely named `mygit`/`git` for a `.git/`\n* target.\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}\n/**\n* Whether `filePath` (resolved against `cwd`) has a path segment exactly equal\n* to `node_modules` -- i.e. it names a location inside *any* installed package\n* tree, at any depth, including pnpm's virtual store\n* (`node_modules/.pnpm/<pkg>/node_modules/<pkg>/...`).\n*\n* **Deliberately not scoped to `cwd`**, unlike `targetsGitDir`. pnpm populates\n* `node_modules` with *hardlinks into the global content-addressable store*, so\n* a single in-place overwrite of an installed file (a plain `cp`, a `>`\n* redirect -- anything that opens the destination `O_WRONLY|O_TRUNC` rather than\n* unlinking it first) writes *through* the link and mutates the store's own\n* content-addressed file. That corrupts the package for the global store, every\n* other project on the machine that shares the inode, and every future install\n* or `pnpm dlx` that hardlinks it -- with no error at the scene, because the\n* directory that did the writing ends up perfectly consistent.\n*\n* This is exactly how a local `@bensandee/tooling` build landed on top of the\n* published 0.64.2 in the store on 2026-07-13: `cp -r <local>/dist/*` over a\n* scratch consumer's `node_modules/@bensandee/tooling/dist/`. The destination\n* was outside `cwd` -- so a cwd-scoped check would not have seen it -- and the\n* store stayed broken until a consumer's `pnpm dlx` failed days later with\n* `ERR_MODULE_NOT_FOUND` for a chunk that no published tarball has ever\n* contained.\n*\n* The lesson encoded here: **cwd-containment is not a safety boundary for\n* `node_modules`.** A path inside it is not \"in the project\" in any meaningful\n* sense -- it is a shared, machine-global resource wearing a local path. Hence\n* every segment of the *resolved* path is checked, wherever it lands.\n*\n* Matches on a path *segment*, not a substring, so `src/node_modules_helper.ts`\n* and `docs/node_modules.md` are not mistaken for the real thing.\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_READ_OPS = /* @__PURE__ */ new Set([\n \"status\",\n \"log\",\n \"diff\",\n \"show\",\n \"blame\",\n \"grep\",\n \"shortlog\",\n \"range-diff\",\n \"describe\",\n \"remote\",\n \"fetch\",\n \"ls-files\",\n \"ls-tree\",\n \"ls-remote\",\n \"show-ref\",\n \"for-each-ref\",\n \"rev-parse\",\n \"rev-list\",\n \"cat-file\",\n \"diff-tree\",\n \"name-rev\",\n \"merge-base\",\n \"check-ignore\"\n]);\n/** Git subcommands that modify the working tree, index, or history. */\nconst GIT_WRITE_OPS = /* @__PURE__ */ new Set([\n \"push\",\n \"add\",\n \"commit\",\n \"reset\",\n \"merge\",\n \"rebase\",\n \"cherry-pick\",\n \"checkout\",\n \"switch\",\n \"stash\",\n \"tag\",\n \"revert\",\n \"clean\",\n \"rm\",\n \"mv\"\n]);\n/** Flags on git that enable arbitrary code execution */\nconst GIT_DANGEROUS_FLAGS = /* @__PURE__ */ new Set([\n \"-c\",\n \"--exec-path\",\n \"--pager\",\n \"--upload-pack\",\n \"--receive-pack\"\n]);\n/** Git global flags that consume the next token as an argument. */\nconst GIT_FLAGS_WITH_ARG = /* @__PURE__ */ new Set([\n \"-C\",\n \"-c\",\n \"--git-dir\",\n \"--work-tree\"\n]);\n/** Extract the git subcommand (first non-flag token after \"git\"), skipping flag arguments. */\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}\nfunction isGitReadOp(tokens) {\n for (const t of tokens.slice(1)) {\n if (GIT_DANGEROUS_FLAGS.has(t) || t === \"-o\" || t.startsWith(\"--output\")) return false;\n if (t.startsWith(\"--exec-path=\") || t.startsWith(\"--pager=\") || t.startsWith(\"--upload-pack=\") || t.startsWith(\"--receive-pack=\") || t.startsWith(\"-o=\") || t.startsWith(\"--output=\")) return false;\n }\n const sub = getGitSubcommand(tokens);\n if (!sub) return false;\n if (sub === \"stash\") {\n const stashAction = tokens[tokens.indexOf(\"stash\") + 1];\n return stashAction === \"list\" || stashAction === void 0;\n }\n if (sub === \"remote\") {\n let actionIdx = tokens.indexOf(\"remote\") + 1;\n while (tokens[actionIdx] === \"-v\" || tokens[actionIdx] === \"--verbose\") actionIdx++;\n const remoteAction = tokens[actionIdx];\n return remoteAction === void 0 || remoteAction === \"-v\" || remoteAction === \"--verbose\" || remoteAction === \"show\" || remoteAction === \"get-url\";\n }\n return GIT_READ_OPS.has(sub);\n}\nfunction isGitWriteOp(tokens) {\n const sub = getGitSubcommand(tokens);\n if (!sub) return false;\n if (sub === \"stash\") {\n const stashAction = tokens[tokens.indexOf(\"stash\") + 1];\n if (stashAction === \"list\" || stashAction === void 0) return false;\n }\n return GIT_WRITE_OPS.has(sub);\n}\n/** Safe stash actions that don't risk data loss. */\nconst STASH_SAFE_ACTIONS = /* @__PURE__ */ new Set([\n \"push\",\n \"pop\",\n \"apply\",\n \"show\"\n]);\n/**\n* Positional (non-flag) path arguments of a `git mv` command. The caller only\n* invokes this once the subcommand is known to be `mv`, so `mv` is present.\n* `git mv` flags (-f/--force, -k, -n/--dry-run, -v/--verbose) never consume a\n* separate argument, so every non-flag token after `mv` is a path.\n*/\nfunction gitMvPaths(tokens) {\n const mvIdx = tokens.indexOf(\"mv\");\n return tokens.slice(mvIdx + 1).filter((t) => !t.startsWith(\"-\"));\n}\n/**\n* True when a short-option letter appears in any cluster after `sub`, or one of\n* `longFlags` appears verbatim. Neither `git rm` nor `git clean` has a short\n* flag taking an attached value, so a plain character scan is exact for them;\n* `git clean -e<pattern>` does, and a pattern containing the letter only ever\n* produces a false positive, which leaves the caller on the deny side.\n*/\nfunction hasShortLetterOrLongFlag(tokens, sub, letter, longFlags) {\n for (const t of tokens.slice(tokens.indexOf(sub) + 1)) {\n if (!t.startsWith(\"-\")) continue;\n if (t.startsWith(\"--\")) {\n if (longFlags.includes(t)) return true;\n continue;\n }\n if (t.slice(1).includes(letter)) return true;\n }\n return false;\n}\n/**\n* `git mv` and `git rm` are not auto-approvable, but they are not known-unsafe\n* either: the shell spellings they mirror already reach a prompt, and `git rm`\n* additionally refuses a file carrying uncommitted changes unless forced -- so\n* denying it while prompting for `rm` inverts the tiers.\n*\n* `git rm -r` stays denied to mirror the `rm-recursive` rule, which cannot\n* cover it: that rule matches on tokens[0] (`/^rm$/`) and never sees `git`.\n*/\nfunction isGitWorktreeMoveOrRemove(tokens) {\n const sub = getGitSubcommand(tokens);\n if (sub !== \"mv\" && sub !== \"rm\") return false;\n if (hasGitPreSubcommandDangerousFlag(tokens, sub)) return false;\n if (sub === \"rm\" && hasShortLetterOrLongFlag(tokens, sub, \"r\", [\"--recursive\"])) return false;\n return true;\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). But it is the home of a **separate session opened there**: writes to a path outside this session's project directory are denied, so this session cannot edit it. If you cannot open a session there, stay in the primary checkout on this branch and check `git branch --show-current` before each commit.\";\n/**\n* True when the command creates a branch: `git checkout -b/-B` or\n* `git switch -c/-C`, including those letters carried in a short-option\n* cluster with other letters or an attached value (`-bfix-bug`, `-qc\n* feat/x`). A plain `git switch <branch>` is not creation. Long spellings\n* (`--create`, `--force-create`) are deliberately not recognized as creation\n* here -- only the short grammar is in scope.\n*/\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}\n/** Per-kind creation letter: `C`/`c` on switch, `B`/`b` on checkout and `worktree add`. */\nconst GIT_CREATE_LETTERS = {\n force: {\n switch: \"C\",\n checkout: \"B\"\n },\n create: {\n switch: \"c\",\n checkout: \"b\"\n }\n};\n/**\n* The long creation spelling per kind, switch only, with the shortest token the\n* resolver accepts. Force matches every unambiguous prefix down to `--force-`\n* (the shortest that is not the distinct `--force` option) because a prefix the\n* force resolver missed would soften its deny to a prompt; non-force matches\n* `--create` exactly, since an unrecognized abbreviation already reaches\n* passthrough through isGitUnrecognizedBranchFlag -- the same tier this kind\n* routes to -- so prefix handling would change no outcome.\n*/\nconst GIT_CREATE_LONG_FLAGS = {\n force: {\n flag: \"--force-create\",\n shortest: \"--force-\"\n },\n create: {\n flag: \"--create\",\n shortest: \"--create\"\n }\n};\n/**\n* Every branch name a creation spelling of `kind` in `after` (the tokens\n* following the subcommand -- and past `add`, for `worktree add`) targets.\n* Usually zero or one, but git's last-occurrence-wins on a repeated creation\n* flag is not modeled: every occurrence contributes its target, so a repeated\n* flag (`git switch -c main -c feat/x`) is handled conservatively rather than\n* resolving which one wins.\n*\n* Grammar, reusing `splitShortOptionCluster`: a cluster whose letters include\n* the kind's letter contributes its attached value when non-empty, else the\n* next argv token; the kind's long spelling contributes the next argv token, or\n* the name attached to it with `=`. A creation flag with nothing after it (a\n* valueless trailing `-C`, or `--force-create` as the last token) resolves no\n* target for that occurrence -- git errors on the command anyway, so today's\n* routing is preserved.\n*/\nfunction gitCreateTargets(after, letterSub, kind) {\n const letter = GIT_CREATE_LETTERS[kind][letterSub];\n const longFlag = GIT_CREATE_LONG_FLAGS[kind];\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}\n/**\n* Every branch name a creation spelling of `kind` targets in a full `git ...`\n* command: `switch -C`/`--force-create` or `switch -c`/`--create`,\n* `checkout -B`/`-b`, or `worktree add -B`/`-b`. Any other subcommand --\n* including `worktree list`/`remove`/`move` -- always resolves no target.\n* `worktree add` reuses checkout's `-b`/`-B` grammar (identical required-value\n* letter), so no new letter set is needed.\n*/\nfunction gitCommandCreateTargets(tokens, kind) {\n const sub = getGitSubcommand(tokens);\n if (sub === \"switch\" || sub === \"checkout\") return gitCreateTargets(tokens.slice(tokens.indexOf(sub) + 1), sub, kind);\n if (sub === \"worktree\") {\n const worktreeIdx = tokens.indexOf(\"worktree\");\n if (tokens[worktreeIdx + 1] !== \"add\") return [];\n return gitCreateTargets(tokens.slice(worktreeIdx + 2), \"checkout\", kind);\n }\n return [];\n}\n/**\n* True when a `switch`/`checkout`/`worktree add` command force-creates a\n* branch under a name in the protected set. Force-create resets an existing\n* branch to the start point (HEAD by default) rather than merely creating\n* it, so this is a claim about the TOKENS, not the current branch -- unlike\n* isGitProtectedBranchWrite (below), this never calls getCurrentBranch and is\n* valid even outside a repository, where main/master are protected\n* unconditionally. Its non-force twin, isGitProtectedBranchCreate, resolves the\n* same positions one letter over and routes to a prompt instead of a deny.\n*/\nfunction isGitProtectedBranchForceCreate(tokens, ctx) {\n const targets = gitCommandCreateTargets(tokens, \"force\");\n if (targets.length === 0) return false;\n const protectedSet = getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []);\n return targets.some((t) => protectedSet.has(t));\n}\n/**\n* True when `git branch` destroys or overwrites a protected ref: a delete\n* (`-d`/`-D`/`--delete`) naming one, a non-force rename (`-m`/`-M`/`--move`)\n* moving one away by name, or any force spelling (`-f`/`--force`, or the\n* force letters `-M`/`-C`) writing onto one. A claim about the TOKENS, never\n* calling getCurrentBranch and valid outside a repository -- like its\n* siblings above -- but unlike `isGitProtectedBranchCreate` it carries **no**\n* pre-subcommand dangerous-flag guard: that guard exists only where a\n* predicate softens a deny into a prompt, and this one feeds a deny and\n* excludes nothing, so `git -c core.hooksPath=/tmp/evil branch -D main`\n* still denies here.\n*\n* The three checks are independent, not mutually exclusive branches of one\n* mode -- `git branch -M junk main` is a force rename whose *source* (`junk`)\n* is not protected, so the rename check alone would miss it; the force check\n* catches it via the *destination* (`main`).\n*\n* The accepted residual: `git branch -m junk` while sitting on a protected\n* branch renames it away with no victim name in the tokens at all, so this\n* returns false and the command only prompts.\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}\n/**\n* True when a `switch`/`checkout`/`worktree add` command creates a branch under\n* a protected name without the force letter. Nothing is reset -- when the ref\n* already exists git refuses -- but when it does not exist the command\n* succeeds, leaving a local `main` pointing at a feature HEAD and the session\n* parked on a protected branch. Creation is not destructive, so this is not a\n* deny; an agent never legitimately needs to create a protected name, so it is\n* not an allow either: it routes to the passthrough prompt. A claim about the\n* TOKENS exactly like isGitProtectedBranchForceCreate, so it needs no current\n* branch and is valid outside a repository.\n*\n* This one carries a guard the force twin does not: the force twin feeds a deny\n* rule, while this feeds a `git-write` exclusion, and a config/exec-injection or\n* repo-redirect flag is caught by no earlier deny rule. Without the same guard\n* isGitUnrecognizedBranchFlag uses, `git -c core.hooksPath=/tmp/evil switch -c\n* main` would soften from that deny to a prompt.\n*/\nfunction isGitProtectedBranchCreate(tokens, ctx) {\n const sub = getGitSubcommand(tokens);\n if (sub !== \"switch\" && sub !== \"checkout\" && sub !== \"worktree\") return false;\n if (hasGitPreSubcommandDangerousFlag(tokens, sub)) return false;\n const targets = gitCommandCreateTargets(tokens, \"create\");\n if (targets.length === 0) return false;\n const protectedSet = getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []);\n return targets.some((t) => protectedSet.has(t));\n}\n/**\n* True when `worktree add <path>` -- with no `-b`/`-B`/`--detach` and no\n* commit-ish positional -- would implicitly create a branch under a\n* protected name: git's own rule is \"as if `-b $(basename <path>)` was\n* specified\" whenever no such branch already exists. Nothing is reset -- when\n* the ref already exists the command merely checks it out, the auto-approved\n* analogue of `git switch <branch>` -- but when it does not exist the command\n* succeeds, leaving a local `main` at a feature HEAD and the new worktree\n* checked out onto it. Creation is not destructive, so this is not a deny;\n* an agent never legitimately needs to create a protected name, so it is not\n* an allow either: it routes to the passthrough prompt, exactly like\n* isGitProtectedBranchCreate's explicit `-b`/`-B` case. A claim about the\n* TOKENS, so it carries the same pre-subcommand dangerous-flag guard that\n* function does, needs no current branch, and is valid outside a repository.\n*/\nfunction isGitWorktreeImplicitProtectedCreate(tokens, ctx) {\n const sub = getGitSubcommand(tokens);\n if (sub !== \"worktree\") return false;\n if (hasGitPreSubcommandDangerousFlag(tokens, sub)) return false;\n const worktreeIdx = tokens.indexOf(\"worktree\");\n if (tokens[worktreeIdx + 1] !== \"add\") return false;\n if (gitCommandCreateTargets(tokens, \"create\").length > 0) return false;\n if (gitCommandCreateTargets(tokens, \"force\").length > 0) return false;\n const after = tokens.slice(worktreeIdx + 2);\n if (after.includes(\"--detach\")) return false;\n const positionals = [];\n for (let i = 0; i < after.length; i++) {\n const token = after[i];\n if (token === \"--reason\") {\n i++;\n continue;\n }\n if (token !== void 0 && !token.startsWith(\"-\")) positionals.push(token);\n }\n if (positionals.length !== 1) return false;\n const worktreePath = positionals[0];\n if (worktreePath === void 0) return false;\n const basename = path.basename(worktreePath);\n return getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []).has(basename);\n}\n/**\n* True when a global flag sits before the subcommand that enables config or\n* executable injection (GIT_DANGEROUS_FLAGS) or repoints git at an arbitrary\n* directory (GIT_REPO_REDIRECT_FLAGS). Global flags sit *before* the\n* subcommand; only that span is inspected so a positional argument that\n* happens to be spelled \"-C\" is never mistaken for the flag. Shared by\n* isGitSafeWriteOp and isGitBareCheckout -- mirrors isGitFeatureBranchWrite's\n* and isGitSafePush's identical scans (same constants, same rationale).\n* Without this, `git -c core.hooksPath=/tmp/evil add file.txt` or\n* `git -C /etc worktree add ../wt feat/x` would auto-approve via the\n* branches below.\n*/\nfunction hasGitPreSubcommandDangerousFlag(tokens, sub) {\n const subIdx = tokens.indexOf(sub);\n for (const t of tokens.slice(1, subIdx)) {\n if (GIT_DANGEROUS_FLAGS.has(t) || GIT_REPO_REDIRECT_FLAGS.has(t)) return true;\n if (t.startsWith(\"--exec-path=\") || t.startsWith(\"--pager=\") || t.startsWith(\"--upload-pack=\") || t.startsWith(\"--receive-pack=\") || t.startsWith(\"--git-dir=\") || t.startsWith(\"--work-tree=\")) return true;\n }\n return false;\n}\n/**\n* True for a `checkout` that isn't branch creation (`-b`/`-B`, which stays\n* auto-approved via isGitSafeWriteOp -- deny rules run before allow rules\n* unconditionally, so a predicate that forgot to exclude them would start\n* denying branch creation) and carries no global config/exec-injection or\n* repo-redirect flag (those keep routing to the git-write catch-all, whose\n* message already covers them).\n*\n* `git checkout <ref>` (branch change) and `git checkout -- <path>` / `git\n* checkout <ref> -- <path>` (file restore) are not distinguishable from\n* syntax alone, so this predicate -- and the single deny rule built on it --\n* deliberately covers both shapes with one message carrying both redirects,\n* rather than two rules guessing at which one a bare `git checkout <arg>` is.\n*/\nfunction isGitBareCheckout(tokens) {\n const sub = getGitSubcommand(tokens);\n if (sub !== \"checkout\") return false;\n if (isGitBranchCreation(tokens)) return false;\n return !hasGitPreSubcommandDangerousFlag(tokens, sub);\n}\n/**\n* `git checkout -- <path>` restores files; the separator makes the argument\n* unambiguously a path rather than a branch. It discards uncommitted work, so\n* it is never auto-approved -- but `git restore -- <path>`, the spelling\n* git-bare-checkout's reason redirects to, only prompts. One risk, one tier.\n*\n* A `-f` spelling still denies: git-discard-changes is evaluated first.\n*\n* A trailing separator with nothing after it (`git checkout main --`, `git\n* checkout --`) is not a restore -- there is no pathspec to redirect to\n* `git restore`, so it keeps the bare-checkout deny.\n*/\nfunction isGitCheckoutPathRestore(tokens) {\n const sub = getGitSubcommand(tokens);\n if (sub !== \"checkout\") return false;\n if (hasGitPreSubcommandDangerousFlag(tokens, sub)) return false;\n const rest = tokens.slice(tokens.indexOf(sub) + 1);\n const separatorIndex = rest.indexOf(\"--\");\n if (separatorIndex === -1) return false;\n return rest.slice(separatorIndex + 1).some((token) => !token.startsWith(\"-\"));\n}\n/**\n* True when a `switch`/`checkout` command carries a discard-changes spelling\n* after the subcommand: a prefix of `--force` or of `--discard-changes` (`--f`\n* and `--di` and longer -- git's parse-options accepts any unambiguous\n* long-option prefix), or a short cluster whose letters (before any\n* required-value option's attached value) contain `f`.\n*\n* Both long tests read `\"--force\".startsWith(token)`, never\n* `token.startsWith(\"--force\")`. `--force-create` shares the `--force` stem\n* and is a safe, auto-approved flag, so the reversed test would start denying\n* branch creation -- deny rules run before allow rules.\n*\n* This predicate has to be exhaustive rather than merely picking the deny\n* message: isGitUnrecognizedBranchFlag routes a flag outside the safe sets to\n* a human prompt, so a discard spelling missed here would soften to a prompt\n* instead of falling through to a deny.\n*/\nfunction isGitDiscardChanges(tokens) {\n const sub = getGitSubcommand(tokens);\n if (sub !== \"switch\" && sub !== \"checkout\") return false;\n return tokens.slice(tokens.indexOf(sub) + 1).some((t) => {\n if (t.length >= 3 && \"--force\".startsWith(t)) return true;\n if (t.length >= 4 && \"--discard-changes\".startsWith(t)) return true;\n return splitShortOptionCluster(t, sub)?.letters.includes(\"f\") ?? false;\n });\n}\n/**\n* Tokens safe after `switch`, exact-match only. An allowlist rather than a\n* denylist of `-f`/`--discard-changes`: git accepts unambiguous prefixes\n* (`--disc`) and short clusters (`-fc`), so a denylist of exact spellings\n* would not hold. `-` (previous-branch shorthand) is flag-shaped and must be\n* listed explicitly, or it would fall through to the deny side.\n*/\nconst GIT_SWITCH_SAFE_FLAGS = /* @__PURE__ */ new Set([\n \"-c\",\n \"-C\",\n \"--create\",\n \"--force-create\",\n \"-d\",\n \"--detach\",\n \"-t\",\n \"--track\",\n \"--track=direct\",\n \"--track=inherit\",\n \"--no-track\",\n \"--guess\",\n \"--no-guess\",\n \"-q\",\n \"--quiet\",\n \"--progress\",\n \"--no-progress\",\n \"-\"\n]);\n/**\n* Tokens safe after `checkout`, kept separate from the switch set: `-c` is a\n* switch-only spelling and `-b`/`-B` are checkout-only, and sharing one set\n* would auto-approve each subcommand's flags on the other.\n*/\nconst GIT_CHECKOUT_SAFE_FLAGS = /* @__PURE__ */ new Set([\n \"-b\",\n \"-B\",\n \"-t\",\n \"--track\",\n \"--track=direct\",\n \"--track=inherit\",\n \"--no-track\",\n \"--guess\",\n \"--no-guess\",\n \"-q\",\n \"--quiet\",\n \"--progress\",\n \"--no-progress\"\n]);\n/**\n* True when a flag-shaped switch/checkout token is safe: it is an exact\n* member of `safe`, or the helper reads it as a cluster whose every letter,\n* spelled `-<letter>`, is a member of `safe`. Exact-set membership is always\n* tried first and is the only test a non-cluster token (`-`, every long\n* flag) ever gets, so those keep matching exactly as they do today -- the\n* cluster reading is a fallback for short clusters only.\n*/\nfunction isSafeSwitchCheckoutFlag(t, sub, safe) {\n if (safe.has(t)) return true;\n const cluster = splitShortOptionCluster(t, sub);\n return cluster !== void 0 && cluster.letters.every((l) => safe.has(`-${l}`));\n}\n/**\n* True for a `switch`, or a branch-creating `checkout`, that the safe-flag\n* allowlists reject only because a flag is unrecognized -- `--orphan`,\n* `--conflict=`, `--recurse-submodules`, an abbreviation of a safe flag.\n* Ambiguous, not known-unsafe: the allowlists enumerate what has been\n* reasoned about, and a legitimate flag nobody listed must reach a human\n* prompt rather than a hard deny (blocking something safe is the failure\n* direction this hook does not accept). Mirrors\n* isGitPushWithoutExplicitRefspec's role in the same git-write `when`.\n*\n* Three shapes are deliberately left to deny instead: a discard spelling\n* (git-discard-changes, and isGitDiscardChanges is exhaustive so that this\n* predicate cannot soften one to a prompt), a `checkout` that creates no\n* branch (git-bare-checkout, whose message carries the restore redirect), and\n* a global config/exec-injection or repo-redirect flag, which keeps routing to\n* the git-write catch-all exactly as isGitBareCheckout leaves it.\n*/\nfunction isGitUnrecognizedBranchFlag(tokens) {\n const sub = getGitSubcommand(tokens);\n if (sub !== \"switch\" && sub !== \"checkout\") return false;\n if (isGitDiscardChanges(tokens)) return false;\n if (hasGitPreSubcommandDangerousFlag(tokens, sub)) return false;\n const after = tokens.slice(tokens.indexOf(sub) + 1);\n if (sub === \"checkout\" && !isGitBranchCreation(tokens)) return false;\n const safe = sub === \"switch\" ? GIT_SWITCH_SAFE_FLAGS : GIT_CHECKOUT_SAFE_FLAGS;\n return after.some((t) => t.startsWith(\"-\") && !isSafeSwitchCheckoutFlag(t, sub, safe));\n}\n/**\n* Git write operations that are safe to auto-approve:\n* - `git add` -- staging is trivially reversible\n* - `git checkout -b/-B` -- branch creation, without a discarding flag\n* - `git switch -c/-C` -- branch creation, without a discarding flag\n* - `git switch <branch>` -- branch switching, without a discarding flag\n* (switch only handles branches, unlike checkout)\n* - `git branch <name> [<start-point>]` -- branch creation, no checkout, under a non-protected name\n* - `git stash` / `git stash push|pop|apply|show` -- reversible stash operations\n* - `git mv` -- only when every path is a `.md` file under `docs/` (plan-lifecycle moves)\n*/\nfunction isGitSafeWriteOp(tokens, ctx) {\n const sub = getGitSubcommand(tokens);\n if (!sub) return false;\n if (hasGitPreSubcommandDangerousFlag(tokens, sub)) return false;\n if (sub === \"add\") return true;\n if (sub === \"branch\") return isGitBranchSafeCreate(tokens, ctx);\n if (sub === \"mv\") {\n const GIT_MV_UNSAFE_FLAGS = /* @__PURE__ */ new Set([\n \"-C\",\n \"--git-dir\",\n \"--work-tree\",\n \"-c\",\n \"--exec-path\"\n ]);\n if (tokens.some((t) => GIT_MV_UNSAFE_FLAGS.has(t) || t.startsWith(\"--exec-path=\"))) return false;\n const paths = gitMvPaths(tokens);\n return paths.length >= 2 && paths.every((p) => isDocsMarkdownPath(p, ctx));\n }\n if (sub === \"checkout\") {\n const afterCheckout = tokens.slice(tokens.indexOf(\"checkout\") + 1);\n if (!isGitBranchCreation(tokens)) return false;\n if (!afterCheckout.every((t) => !t.startsWith(\"-\") || isSafeSwitchCheckoutFlag(t, \"checkout\", GIT_CHECKOUT_SAFE_FLAGS))) return false;\n return !isGitProtectedBranchForceCreate(tokens, ctx) && !isGitProtectedBranchCreate(tokens, ctx);\n }\n if (sub === \"switch\") {\n if (!tokens.slice(tokens.indexOf(\"switch\") + 1).every((t) => !t.startsWith(\"-\") || isSafeSwitchCheckoutFlag(t, \"switch\", GIT_SWITCH_SAFE_FLAGS))) return false;\n return !isGitProtectedBranchForceCreate(tokens, ctx) && !isGitProtectedBranchCreate(tokens, ctx);\n }\n if (sub === \"stash\") {\n const stashAction = tokens[tokens.indexOf(\"stash\") + 1];\n if (stashAction === void 0) return true;\n return STASH_SAFE_ACTIONS.has(stashAction);\n }\n if (sub === \"worktree\") {\n const worktreeAction = tokens[tokens.indexOf(\"worktree\") + 1];\n if (worktreeAction !== \"list\" && worktreeAction !== \"add\") return false;\n return !isGitProtectedBranchForceCreate(tokens, ctx) && !isGitProtectedBranchCreate(tokens, ctx) && !isGitWorktreeImplicitProtectedCreate(tokens, ctx);\n }\n return false;\n}\n/** Git subcommands eligible for the feature-branch auto-approval. */\nconst GIT_FEATURE_BRANCH_OPS = /* @__PURE__ */ new Set([\n \"commit\",\n \"merge\",\n \"rebase\",\n \"cherry-pick\",\n \"revert\",\n \"reset\"\n]);\n/** Global git flags that redirect the effective repository/work tree. */\nconst GIT_REPO_REDIRECT_FLAGS = /* @__PURE__ */ new Set([\n \"-C\",\n \"--git-dir\",\n \"--work-tree\"\n]);\n/**\n* Checks (2)-(4) of the feature-branch predicate -- subcommand membership\n* plus the three flag guards -- with the branch check (5) left out. \"Eligible\n* but for the branch\": isGitFeatureBranchWrite adds \"and the branch isn't\n* protected\", isGitProtectedBranchWrite adds \"and the branch is protected\".\n* A command this rejects (a dangerous flag, say) has no valid redirect to\n* the feature-branch path either way -- it stays on the git-write catch-all,\n* which is why both callers share this instead of each re-deriving their own\n* notion of \"would work on a feature branch\".\n*/\nfunction isGitFeatureBranchEligible(tokens) {\n const sub = getGitSubcommand(tokens);\n if (!sub || !GIT_FEATURE_BRANCH_OPS.has(sub)) return false;\n for (const t of tokens.slice(1)) {\n if (GIT_DANGEROUS_FLAGS.has(t)) return false;\n if (t === \"-o\" || t.startsWith(\"--output\")) return false;\n if (t.startsWith(\"--exec-path=\") || t.startsWith(\"--pager=\") || t.startsWith(\"--upload-pack=\") || t.startsWith(\"--receive-pack=\") || t.startsWith(\"-o=\")) return false;\n }\n for (const t of tokens.slice(1)) {\n if (GIT_REPO_REDIRECT_FLAGS.has(t)) return false;\n if (t.startsWith(\"--git-dir=\") || t.startsWith(\"--work-tree=\")) return false;\n }\n if (sub === \"rebase\") {\n for (const t of tokens.slice(1)) if (t === \"--exec\" || t === \"-x\" || t.startsWith(\"--exec=\")) return false;\n }\n return true;\n}\n/**\n* True when:\n* 1.-4. isGitFeatureBranchEligible(tokens);\n* 5. getCurrentBranch(ctx.cwd) is non-null AND not in the protected set.\n*\n* Both must hold. Any failure -> return false and let the deny rules fire.\n*/\nfunction isGitFeatureBranchWrite(tokens, ctx) {\n if (!isGitFeatureBranchEligible(tokens)) return false;\n const current = getCurrentBranch(ctx.cwd);\n if (current === null) return false;\n return !getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []).has(current);\n}\n/**\n* True when a write is eligible for the feature-branch auto-approval\n* (isGitFeatureBranchEligible) but the current branch is known and\n* protected. An unknown branch -- detached HEAD, outside a repo, git state\n* unreadable -- must NOT take this rule: \"you are on a protected branch\" is\n* a claim the hook cannot make there, so those keep falling through to the\n* git-write catch-all.\n*/\nfunction isGitProtectedBranchWrite(tokens, ctx) {\n if (!isGitFeatureBranchEligible(tokens)) return false;\n const current = getCurrentBranch(ctx.cwd);\n if (current === null) return false;\n return getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []).has(current);\n}\n/** Push flags that move refs we never auto-approve: bulk, tags, deletes. */\nconst GIT_PUSH_UNSAFE_FLAGS = /* @__PURE__ */ new Set([\n \"--mirror\",\n \"--all\",\n \"--tags\",\n \"--follow-tags\",\n \"--delete\",\n \"-d\",\n \"--prune\"\n]);\n/**\n* Push-specific flags that redirect the destination or the remote-side helper\n* program independently of the positional remote/refspec parsing below.\n* `--repo` is equivalent to (and overrides) the positional <repository>\n* argument -- `git push --repo=<url>` can push to an arbitrary host even with\n* zero positional args, bypassing the \"known remote\" check entirely.\n* `--receive-pack`/`--exec` tell git which program to invoke as the remote\n* helper; on local/dumb transports git runs that program directly, so this is\n* local code execution, not just a remote-side concern. All three are unlike\n* `-C`/`--git-dir`/`--work-tree` (GIT_REPO_REDIRECT_FLAGS) in that they are\n* push subcommand options, not global git flags, so they only need checking\n* after the subcommand -- but the checking still has to happen.\n*/\nconst GIT_PUSH_DANGEROUS_FLAGS = /* @__PURE__ */ new Set([\n \"--repo\",\n \"--receive-pack\",\n \"--exec\"\n]);\nconst GIT_PUSH_DANGEROUS_FLAG_PREFIXES = [\n \"--repo=\",\n \"--receive-pack=\",\n \"--exec=\"\n];\n/**\n* `-o`/`--push-option` send a string to the receiving hook -- but the reason\n* they must be rejected here is parsing, not the hook side effect. Unlike\n* `--repo`/`--receive-pack`/`--exec` above, which only ever appear as\n* `--flag=value`, `-o`/`--push-option` take a *space-separated* value\n* (`-o origin https://evil.example.com/x.git HEAD`). Left unrejected, that\n* value token survives the `!t.startsWith(\"-\")` positional filter below and\n* is mistaken for the remote, silently shifting the real destination into\n* the refspec position -- bypassing the \"known remote\" check in (5) entirely.\n* The `=` forms (`-o=value`, `--push-option=value`) aren't exploitable this\n* way (the URL stays a refspec and gets caught by the remote check), but are\n* rejected too rather than relying on that distinction holding up.\n*/\nconst GIT_PUSH_VALUE_FLAGS = /* @__PURE__ */ new Set([\"-o\", \"--push-option\"]);\nconst GIT_PUSH_VALUE_FLAG_PREFIXES = [\"-o=\", \"--push-option=\"];\n/**\n* The ref a refspec writes to. `src:dst` targets dst; a bare `ref` targets the\n* same name on the remote. Strips the leading \"+\" (force) -- callers reject that\n* separately -- and any `refs/heads/` prefix.\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}\n/**\n* Structural safety check shared by `isGitSafePush` and\n* `isGitPushWithoutExplicitRefspec`. Every condition must hold:\n*\n* 1. No global dangerous flag (config/exec injection) or repo-redirect flag.\n* 2. No bulk/tag/delete flag (GIT_PUSH_UNSAFE_FLAGS), no --repo /\n* --receive-pack / --exec (GIT_PUSH_DANGEROUS_FLAGS), and no -o /\n* --push-option (GIT_PUSH_VALUE_FLAGS) -- these redirect the destination,\n* the remote-side helper, or (for -o) the positional parse itself\n* independently of the remote/refspec parsing below, so they must be\n* rejected up front.\n* 3. No refspec begins with \"+\" -- a force-push the `--force` deny rule misses.\n* 4. Every refspec's *destination* is a non-protected branch, doesn't have\n* an empty source (\":branch\", the unflagged spelling of --delete/-d),\n* and isn't under refs/tags/ (the unflagged spelling of --tags). A\n* current-branch check alone would happily approve `git push origin\n* HEAD:main`.\n* 5. The remote, when named, is a configured remote -- never a URL or path, so\n* the repo cannot be pushed to an arbitrary host.\n* 6. The current branch is known and not protected.\n*\n* Deliberately does NOT require a refspec to be present -- an empty refspec\n* list (bare `git push` / `git push origin`) is structurally fine by these\n* six rules, it's just *ambiguous*: git resolves the actual destination from\n* push.default / branch.<x>.merge, config this hook does not (and, as of the\n* .git/ write deny rule, no longer needs to) trust. Callers decide what to do\n* with that ambiguity -- `isGitSafePush` requires refspecs.length > 0;\n* `isGitPushWithoutExplicitRefspec` requires the opposite, so the `git-write`\n* deny rule can route the ambiguous case to passthrough instead of a hard deny.\n*\n* Returns the parsed refspec list on success, or `null` on any failure.\n*/\nfunction gitPushStructuralRefspecs(tokens, ctx) {\n if (getGitSubcommand(tokens) !== \"push\") return null;\n const pushIdx = tokens.indexOf(\"push\");\n for (const t of tokens.slice(1, pushIdx)) {\n if (GIT_DANGEROUS_FLAGS.has(t) || GIT_REPO_REDIRECT_FLAGS.has(t)) return null;\n if (t.startsWith(\"--exec-path=\") || t.startsWith(\"--git-dir=\") || t.startsWith(\"--work-tree=\")) return null;\n }\n const after = tokens.slice(pushIdx + 1);\n if (after.some((t) => GIT_PUSH_UNSAFE_FLAGS.has(t) || GIT_PUSH_DANGEROUS_FLAGS.has(t) || GIT_PUSH_DANGEROUS_FLAG_PREFIXES.some((p) => t.startsWith(p)) || GIT_PUSH_VALUE_FLAGS.has(t) || GIT_PUSH_VALUE_FLAG_PREFIXES.some((p) => t.startsWith(p)))) return null;\n const current = getCurrentBranch(ctx.cwd);\n if (current === null) return null;\n const protectedSet = getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []);\n if (protectedSet.has(current)) return null;\n const positional = after.filter((t) => !t.startsWith(\"-\"));\n const maybeRemote = positional[0];\n const refspecs = positional.slice(1);\n if (maybeRemote !== void 0 && !getRemotes(ctx.cwd).has(maybeRemote)) return null;\n for (const spec of refspecs) {\n if (spec.startsWith(\"+\")) return null;\n if (spec.startsWith(\":\")) return null;\n const dst = refspecDestination(spec);\n if (protectedSet.has(dst)) return null;\n if (dst.startsWith(\"refs/tags/\")) return null;\n }\n return refspecs;\n}\n/**\n* True when `git push` is safe to auto-approve: structurally safe (see\n* `gitPushStructuralRefspecs`) AND carries at least one explicit refspec\n* positional argument. Without one -- bare `git push`, or `git push <remote>`\n* with no refspec -- the destination comes from push.default /\n* branch.<x>.merge, which this rule does not resolve or trust; see\n* `isGitPushWithoutExplicitRefspec` for that case.\n*\n* `--force-with-lease` is deliberately permitted: it is the alternative the\n* `git-force-push` deny rule already points users at, and it is routine after a\n* rebase. `--force`/`-f` remain denied by that rule -- even when\n* `--force-with-lease` also appears; the lease does not downgrade an\n* unconditional force (see `isGitForcePush`).\n*\n* The \"cannot push to an arbitrary host\" guarantee in condition 5 holds only\n* because writes into .git/ (which is where `remote.<name>.url` lives) are\n* denied at the file-tool and path-argument layers (`git-dir-write` in\n* file-rules.ts, the `targetsGitDir` check in `arePathArgsSafe`) -- this\n* function only ever reads `getRemotes()`, it does not defend the state\n* `getRemotes()` reads from. An agent with un-gated filesystem write access\n* outside this hook (e.g. a tool this hook doesn't mediate) is not\n* containable by any of this.\n*/\nfunction isGitSafePush(tokens, ctx) {\n const refspecs = gitPushStructuralRefspecs(tokens, ctx);\n return refspecs !== null && refspecs.length > 0;\n}\n/**\n* True when `git push` is otherwise structurally safe (see\n* `gitPushStructuralRefspecs`) but carries no explicit refspec -- the\n* ambiguous case `isGitSafePush` rejects. Used only to keep the `git-write`\n* deny rule from firing for this shape, so it falls through to passthrough\n* (normal human review) instead of a hard deny -- the push isn't known-unsafe,\n* it's merely unresolved.\n*\n* A push that fails `gitPushStructuralRefspecs` for an actual reason\n* (protected branch, dangerous flag, unconfigured remote, ...) returns false\n* here too, so the `git-write` deny still fires for those.\n*/\nfunction isGitPushWithoutExplicitRefspec(tokens, ctx) {\n const refspecs = gitPushStructuralRefspecs(tokens, ctx);\n return refspecs !== null && refspecs.length === 0;\n}\n/** `git tag` read-only flags taking no argument. */\nconst GIT_TAG_READ_FLAGS_NOARG = /* @__PURE__ */ new Set([\n \"-l\",\n \"--list\",\n \"-i\",\n \"--ignore-case\",\n \"--column\",\n \"--omit-empty\"\n]);\n/** `git tag` read-only flags taking one argument, as `--flag value` or `--flag=value`. */\nconst GIT_TAG_READ_FLAGS_VALUE = /* @__PURE__ */ new Set([\n \"--contains\",\n \"--no-contains\",\n \"--points-at\",\n \"--merged\",\n \"--no-merged\",\n \"--sort\",\n \"--format\"\n]);\n/**\n* `git branch` flags that select list mode -- a positional beside one of\n* these is a match pattern, not a branch name. Long-flag names only (no\n* leading dashes); `-l`/`-a`/`-r` are covered separately by\n* `GIT_BRANCH_LIST_SELECTOR_LETTERS`.\n*/\nconst GIT_BRANCH_LIST_SELECTOR_NAMES = /* @__PURE__ */ new Set([\n \"list\",\n \"all\",\n \"remotes\",\n \"show-current\",\n \"contains\",\n \"no-contains\",\n \"merged\",\n \"no-merged\",\n \"points-at\"\n]);\n/**\n* `git branch` flags that change listing output but never select list mode\n* on their own -- a positional beside only these is still a branch name.\n* Measured against git 2.47.3: `git branch -v main`, `git branch\n* --sort=refname zzz`, `git branch --color main` and `git branch --abbrev\n* zzz` each **create** the branch.\n*/\nconst GIT_BRANCH_LIST_MODIFIER_NAMES = /* @__PURE__ */ new Set([\n \"verbose\",\n \"ignore-case\",\n \"color\",\n \"no-color\",\n \"column\",\n \"no-column\",\n \"abbrev\",\n \"no-abbrev\",\n \"sort\",\n \"format\"\n]);\n/** Short letters `git branch` accepts inside a listing cluster (`-av`, `-vv`). */\nconst GIT_BRANCH_LIST_LETTERS = /* @__PURE__ */ new Set([\n \"a\",\n \"r\",\n \"l\",\n \"v\",\n \"i\"\n]);\n/** Of those, the ones that alone select list mode -- `a`, `r`, `l`. */\nconst GIT_BRANCH_LIST_SELECTOR_LETTERS = /* @__PURE__ */ new Set([\n \"a\",\n \"r\",\n \"l\"\n]);\n/**\n* Long `git branch` flags whose value is the next token when spelled without\n* `=`. `--abbrev`, `--color` and `--column` are deliberately absent: measured,\n* they consume nothing (`git branch --color zz` creates `zz`), so treating\n* them as value-consuming would swallow a branch name and auto-approve\n* `git branch --color main` as a listing -- the exact bug this predicate\n* closes. `-u`/`--set-upstream-to` also consume the next token, but `-u` is\n* handled by `splitShortOptionCluster`'s own branch table, not by name here.\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]);\n/** Exact `git branch` flag tokens safe on a plain branch creation. */\nconst GIT_BRANCH_CREATE_SAFE_FLAGS = /* @__PURE__ */ new Set([\n \"-q\",\n \"--quiet\",\n \"-t\",\n \"--track\",\n \"--track=direct\",\n \"--track=inherit\",\n \"--no-track\"\n]);\n/** Short letters safe inside a creation cluster (`-qt`) -- `q` and `t` only. */\nconst GIT_BRANCH_CREATE_SAFE_LETTERS = /* @__PURE__ */ new Set([\"q\", \"t\"]);\n/**\n* Split the tokens after `git branch` into its flags and positionals -- the\n* one walk `isGitBranchListing`, `isGitBranchSafeCreate` and\n* `isGitProtectedBranchRefWrite` all share. A token not starting with `-` is\n* a positional. `--` matches no allowlist a caller checks, so it drops any\n* command carrying one to a prompt, and is otherwise skipped. A `--flag`\n* token is recorded by its name (the part before `=`) and consumes the\n* following token when it has no `=` and its name is in\n* `GIT_BRANCH_VALUE_FLAG_NAMES`. A short token is decomposed with\n* `splitShortOptionCluster(t, \"branch\")`, recording its letters and\n* consuming the following token when its attached value is the empty string\n* (a trailing `-u`).\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}\n/**\n* `git branch` in a listing spelling. An allowlist, exactly like\n* `isGitTagListing`: an unrecognized flag returns false and costs a prompt,\n* never a write. Every long flag must be a selector or a modifier, and every\n* short token's letters must all be listing letters; then the command lists\n* when there are no positionals at all, or when a selector is present (a\n* long selector, or a cluster letter in `a`/`r`/`l`) -- a positional beside\n* only modifiers, or beside nothing recognized, is a branch name.\n*/\nfunction isGitBranchListing(after) {\n const { flags, positionals } = splitGitBranchArgs(after);\n let hasSelector = false;\n for (const flag of flags) {\n if (flag.letters !== void 0) {\n if (!flag.letters.every((l) => GIT_BRANCH_LIST_LETTERS.has(l))) return false;\n if (flag.letters.some((l) => GIT_BRANCH_LIST_SELECTOR_LETTERS.has(l))) hasSelector = true;\n continue;\n }\n if (flag.name === void 0) return false;\n if (GIT_BRANCH_LIST_SELECTOR_NAMES.has(flag.name)) {\n hasSelector = true;\n continue;\n }\n if (!GIT_BRANCH_LIST_MODIFIER_NAMES.has(flag.name)) return false;\n }\n return positionals.length === 0 || hasSelector;\n}\n/**\n* `git branch <name> [<start-point>]` (or `-t`/`--track`/`--no-track`/`-q`\n* added), under a name that is not protected. An allowlist for the same\n* reason `GIT_SWITCH_SAFE_FLAGS` is one: every flag token must be an exact\n* member of the small creation set, so a delete/rename/copy/force/upstream\n* flag -- whether spelled long or as a cluster letter -- falls through to\n* false rather than being auto-approved. `git branch <name>` does not move\n* HEAD, so unlike `switch -c`/`checkout -b` this is never advised into a\n* worktree (see `WORKTREE_ADVICE`, gated on `isGitBranchCreation`, which this\n* does not feed).\n*/\nfunction isGitBranchSafeCreate(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 for (const flag of flags) {\n if (flag.letters !== void 0) {\n if (!flag.letters.every((l) => GIT_BRANCH_CREATE_SAFE_LETTERS.has(l))) return false;\n continue;\n }\n if (!GIT_BRANCH_CREATE_SAFE_FLAGS.has(flag.raw)) return false;\n }\n if (positionals.length < 1 || positionals.length > 2) return false;\n return !getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []).has(positionals[0]);\n}\n/**\n* `git tag` in a listing spelling. A positional argument is a tag name (a\n* write) unless a list flag is present, in which case it is a match pattern.\n* Bare `git tag` has no positional at all and lists.\n*\n* `-n<num>` here means \"print this many annotation lines\" -- on `clean` the\n* same letter is the dry run, which is why the two subcommands do not share a\n* flag table.\n*/\nfunction isGitTagListing(after) {\n let hasListFlag = false;\n let positionals = 0;\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++;\n continue;\n }\n if (t === \"-l\" || t === \"--list\") {\n hasListFlag = true;\n continue;\n }\n if (GIT_TAG_READ_FLAGS_NOARG.has(t)) continue;\n if (/^-n\\d*$/.test(t)) continue;\n const eq = t.indexOf(\"=\");\n if (eq > 0 && GIT_TAG_READ_FLAGS_VALUE.has(t.slice(0, eq))) continue;\n if (GIT_TAG_READ_FLAGS_VALUE.has(t)) {\n i++;\n continue;\n }\n return false;\n }\n return positionals === 0 || hasListFlag;\n}\n/**\n* `git clean` in a dry run. Requires `-f` to be absent rather than deciding\n* which flag wins when both are given -- that precedence is a git-version\n* behavior this hook would otherwise have to pin.\n*/\nfunction isGitCleanDryRun(after) {\n let dryRun = false;\n for (const t of after) {\n if (!t.startsWith(\"-\")) continue;\n if (t.startsWith(\"--\")) {\n if (t === \"--force\") return false;\n if (t === \"--dry-run\") dryRun = true;\n continue;\n }\n if (t.slice(1).includes(\"f\")) return false;\n if (t.slice(1).includes(\"n\")) dryRun = true;\n }\n return dryRun;\n}\n/**\n* Read-only spellings of subcommands whose writes are correctly denied. The\n* deny keys on the verb, so without this `git tag -l`, `git clean -n` and\n* `git <sub> --help` inherit the tier of `git tag v1.2.3`.\n*\n* An allowlist, not a denylist: an unrecognized flag returns false and keeps\n* today's deny, so a spelling missed here costs a prompt rather than an\n* unintended write.\n*/\nfunction isGitReadOnlyForm(tokens) {\n const sub = getGitSubcommand(tokens);\n if (!sub) return false;\n if (hasGitPreSubcommandDangerousFlag(tokens, sub)) return false;\n const after = tokens.slice(tokens.indexOf(sub) + 1);\n if (after[0] === \"--help\") return true;\n if (sub === \"tag\") return isGitTagListing(after);\n if (sub === \"clean\") return isGitCleanDryRun(after);\n if (sub === \"branch\") return isGitBranchListing(after);\n return false;\n}\n/** pnpm built-in subcommands (not user-defined scripts) */\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]);\n/** Read-only pnpm commands that are always safe */\nconst PNPM_SAFE_BUILTINS = /* @__PURE__ */ new Set([\n \"list\",\n \"ls\",\n \"ll\",\n \"la\",\n \"why\",\n \"view\",\n \"info\",\n \"show\",\n \"outdated\"\n]);\nfunction isPnpmSafe(tokens) {\n const subTokens = tokens.slice(1);\n let subcommand;\n for (const t of subTokens) {\n if (t.startsWith(\"-\") && !PNPM_BUILTINS.has(t)) continue;\n subcommand = t;\n break;\n }\n if (!subcommand) return false;\n if (PNPM_SAFE_BUILTINS.has(subcommand)) return true;\n if (subcommand === \"install\" || subcommand === \"i\") return subTokens.slice(subTokens.indexOf(subcommand) + 1).every((t) => t.startsWith(\"-\"));\n if (subcommand === \"store\") return subTokens.slice(subTokens.indexOf(subcommand) + 1).find((t) => !t.startsWith(\"-\")) === \"status\";\n return false;\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}\n/** Map npm subcommand to a pnpm redirect message */\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}\n/**\n* Commands whose write destination is the *last* positional argument\n* (`cp src dst`, `mv src dst`, `rsync -a src/ dst/`, `install -m 755 src dst`,\n* `ln -f src dst`).\n*/\nconst LAST_POSITIONAL_DEST = /* @__PURE__ */ new Set([\n \"cp\",\n \"mv\",\n \"rsync\",\n \"install\",\n \"ln\"\n]);\n/** Commands where *every* positional is written to (`tee a b`, `truncate -s 0 f`). */\nconst ALL_POSITIONAL_DEST = /* @__PURE__ */ new Set([\"tee\", \"truncate\"]);\n/** `-t DIR` / `--target-directory[=DIR]` -- an explicit destination directory. */\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}\n/**\n* Destination paths named by shell redirects (`> f`, `>> f`, `2> f`, `>f`).\n*\n* Checked for *every* command, not just known write tools: the shell splitter\n* leaves redirect tokens inside the segment, so without this an unrecognized\n* command (`some-generator > node_modules/pkg/index.js`) would sail past a\n* command-keyed rule. Over-capture here is harmless -- a bogus \"destination\"\n* like `&1` from `2>&1` simply doesn't resolve into node_modules.\n*/\nfunction redirectDestinations(tokens) {\n const dests = [];\n for (const [i, t] of tokens.entries()) {\n if (/^(?:\\d*|&)>{1,2}$/.test(t)) {\n const next = tokens[i + 1];\n if (next !== void 0) dests.push(next);\n continue;\n }\n const attached = /^(?:\\d*|&)>{1,2}(.+)$/.exec(t);\n if (attached?.[1] !== void 0) dests.push(attached[1]);\n }\n return dests;\n}\n/**\n* Every path this command would *write to*. Sources are deliberately excluded:\n* reading out of `node_modules` (`cat node_modules/pkg/package.json`,\n* `cp node_modules/pkg/README.md docs/`) is routine and stays allowed. Only the\n* destination side can corrupt anything.\n*/\nfunction writeDestinations(tokens) {\n const cmd = path.basename(tokens[0] ?? \"\");\n const rest = tokens.slice(1);\n const dests = redirectDestinations(tokens);\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}\n/**\n* True when the command writes anything into a `node_modules/` tree.\n*\n* See `targetsNodeModules` for why this is a hard deny rather than a prompt:\n* `node_modules` is hardlinked into pnpm's global content-addressable store, so\n* an in-place overwrite silently corrupts that package for every project on the\n* machine -- and the directory doing the writing looks perfectly fine afterward.\n* The 2026-07-13 store poisoning was exactly this, and it was *human-approved*\n* at the prompt, which is why a passthrough would not have stopped it.\n*/\nfunction writesIntoNodeModules(tokens, ctx) {\n return writeDestinations(tokens).some((d) => targetsNodeModules(d, ctx.cwd));\n}\n/** Shared by the `node-modules-write` deny rule and the early check in `evaluate.ts`. */\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 arePathArgsSafe(tokens, ctx) {\n for (const t of tokens.slice(1)) {\n if (t.startsWith(\"-\")) {\n if (t === \"-t\" || t.startsWith(\"--target-directory\")) return false;\n continue;\n }\n if (path.basename(t).startsWith(\".\")) return false;\n if (t.startsWith(\"~/\") || t === \"~\") return false;\n if (targetsGitDir(t, ctx.cwd)) return false;\n if (!isWithin(ctx.cwd, resolveIn(ctx.cwd, t))) return false;\n }\n return true;\n}\nconst FIND_ALWAYS_DANGEROUS = /* @__PURE__ */ new Set([\n \"-ok\",\n \"-okdir\",\n \"-delete\",\n \"-fprint\",\n \"-fprint0\",\n \"-fprintf\",\n \"-fls\"\n]);\nconst FIND_EXEC_SAFE_COMMANDS = /* @__PURE__ */ new Set([\n \"cat\",\n \"head\",\n \"tail\",\n \"grep\",\n \"egrep\",\n \"fgrep\",\n \"ls\",\n \"wc\",\n \"file\",\n \"stat\",\n \"basename\",\n \"dirname\",\n \"realpath\",\n \"readlink\",\n \"sha256sum\",\n \"sha1sum\",\n \"md5sum\",\n \"echo\"\n]);\n/**\n* Extracts the command name from a `-exec` or `-execdir` block.\n* Returns the basename of the first token after `-exec`/`-execdir`,\n* or undefined if the block is malformed.\n*/\nfunction findExecCommand(tokens, startIdx) {\n const cmdToken = tokens[startIdx + 1];\n if (!cmdToken || cmdToken === \"{}\" || cmdToken === \";\" || cmdToken === \"+\") return void 0;\n return path.basename(cmdToken);\n}\nfunction isFindSafe(tokens) {\n for (let i = 0; i < tokens.length; i++) {\n const t = tokens[i];\n if (t === void 0) continue;\n if (FIND_ALWAYS_DANGEROUS.has(t)) return false;\n if (t === \"-exec\" || t === \"-execdir\") {\n const cmd = findExecCommand(tokens, i);\n if (!cmd || !FIND_EXEC_SAFE_COMMANDS.has(cmd)) return false;\n }\n }\n return true;\n}\nfunction isSortSafe(tokens) {\n return !tokens.includes(\"-o\") && !tokens.some((t) => t.startsWith(\"--output\"));\n}\nfunction isRmRecursive(tokens) {\n const rmIdx = tokens.findIndex((t) => path.basename(t) === \"rm\");\n if (rmIdx === -1) return false;\n const afterRm = tokens.slice(rmIdx + 1);\n const shortFlags = afterRm.filter((t) => t.startsWith(\"-\") && !t.startsWith(\"--\"));\n const longFlags = afterRm.filter((t) => t.startsWith(\"--\"));\n const hasRecursiveShort = shortFlags.some((f) => /[rR]/.test(f));\n const hasRecursiveLong = longFlags.includes(\"--recursive\");\n return hasRecursiveShort || hasRecursiveLong;\n}\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}\nfunction isGitResetHardOnMain(tokens) {\n if (!(tokens.includes(\"reset\") && tokens.includes(\"--hard\"))) return false;\n if (tokens.some((t) => t === \"main\" || t === \"master\")) return true;\n const positionalArgs = tokens.filter((t) => t !== \"git\" && t !== \"reset\" && !t.startsWith(\"-\"));\n return positionalArgs.length === 0 || positionalArgs.includes(\"HEAD\");\n}\n/** bst subcommands that are safe to auto-approve (read-only or local-only). */\nconst BST_SAFE_SUBCOMMANDS = /* @__PURE__ */ new Set([\n \"checks:run\",\n \"lint:types\",\n \"lint:report\",\n \"hooks:audit\",\n \"hooks:sync\",\n \"docker:build\",\n \"docker:check\",\n \"repo:sync\",\n \"coverage:check\",\n \"coverage:summary\",\n \"coverage:status\",\n \"health:check\",\n \"health:summary\",\n \"health:status\",\n \"ascii:check\",\n \"docs:check\",\n \"docs:delink\",\n \"docs:relocate\",\n \"docs:retire\",\n \"pr:create\",\n \"pr:update\",\n \"pr:checks\",\n \"pr:list\",\n \"commit:status\",\n \"issue:view\",\n \"issue:list\",\n \"issue:download\",\n \"findings:add\",\n \"findings:list\",\n \"findings:resolve\",\n \"plan:workspace\",\n \"plan:brief\",\n \"plan:review-package\",\n \"plan:status\"\n]);\n/**\n* Flags that disqualify an otherwise-safe bst subcommand from auto-approval.\n*\n* `issue:download` is a remote read whose local write is confined to --out, sanitized\n* to a basename, and never overwrites. `--force` is exactly the power to overwrite an\n* existing local file with remote-controlled bytes, so it prompts.\n*\n* `pr:create`/`pr:update` are safe because an update PATCHes only the fields it was given and\n* refuses to drop a closing keyword the PR's body already carries. `--allow-unlink` is the one\n* flag that turns that refusal off, and what it permits -- an issue silently staying open after\n* its PR merges -- is the exact failure this whole command pair was split apart to prevent, and\n* one no PR review catches (the diff looks fine; the link is just gone). A guard an agent can\n* step over unattended is not a guard, so passing it prompts.\n*\n* `plan:brief`/`plan:review-package` write only inside the plan's scratch directory unless\n* given `--out`, which writes to any path and overwrites it -- including a path through a\n* pnpm-store hardlink under node_modules that the hook otherwise denies -- so it prompts.\n*\n* Matching is deliberately conservative: any occurrence of the flag token anywhere in\n* the command (as a bare flag, `--flag=value`, after a `--` separator, or even as the\n* value of another flag) disqualifies. A flag that *might* be present is treated the\n* same as one that definitely is -- fail closed rather than try to model citty's\n* argument parsing (positional vs. flag-value) here.\n*/\nconst BST_UNSAFE_FLAGS = {\n \"issue:download\": [\"--force\"],\n \"pr:create\": [\"--allow-unlink\"],\n \"pr:update\": [\"--allow-unlink\"],\n \"plan:brief\": [\"--out\"],\n \"plan:review-package\": [\"--out\"]\n};\nfunction hasDisqualifyingFlag(sub, tokens) {\n const flags = BST_UNSAFE_FLAGS[sub];\n if (!flags) return false;\n return tokens.some((token) => flags.some((flag) => token === flag || token.startsWith(`${flag}=`)));\n}\n/**\n* Safe under `bst-agent` only. All eight are read-only, and they only exist where the agent\n* runs, so the published CLI has no such command to classify.\n*\n* `agent:start` is deliberately absent: it spawns containers and holds two credentials, so it\n* prompts. `agent:pause` and `agent:resume` are deliberately absent too: they mutate a live\n* daemon's behavior -- halting it, or re-arming autonomous claiming and automerging an operator\n* paused on purpose -- and neither write flows through a PR where a review could catch it, so an\n* unattended agent must not be able to do either without a human answering a prompt.\n* `bst-agent` is a workspace-linked bin of a private package, the same trust shape as\n* `bst` (a name pnpm resolves, not a filesystem path an agent can manufacture), so it inherits\n* `BST_SAFE_SUBCOMMANDS` and `BST_UNSAFE_FLAGS` unchanged.\n*\n* The three `egress:*` read-only verbs join the set for the same reason the `agent:*` ones did:\n* each only ever reads the drained proxy decision logs (or, for `egress:status`, the orphan\n* history branch) and writes nothing. `egress:record` is deliberately absent -- it pushes to\n* that orphan branch, a remote write no PR review catches, so it stays a passthrough prompt like\n* `coverage:record`/`health:record`.\n*\n* `agent:token-rate` joins for the same reason `agent:review-rounds` did: it only reads the\n* forge's own comment trail and computes a distribution over it, writing nothing.\n*\n* `agent:models-check` joins too: like `agent:calibrate`, it reaches the network, but only to\n* read each configured endpoint's own health and effort words -- it touches no forge, so there is\n* still no remote write a PR review could miss.\n*/\nconst BST_AGENT_ONLY_SAFE_SUBCOMMANDS = /* @__PURE__ */ new Set([\n \"agent:calibrate\",\n \"agent:context-usage\",\n \"agent:models-check\",\n \"agent:repair-candidates\",\n \"agent:review-rounds\",\n \"agent:status\",\n \"agent:token-rate\",\n \"agent:transcript\",\n \"egress:check\",\n \"egress:status\",\n \"egress:summary\"\n]);\nfunction isBstSafe(tokens, extraSafe) {\n const sub = tokens[1];\n if (!sub) return false;\n if (!BST_SAFE_SUBCOMMANDS.has(sub) && !extraSafe?.has(sub)) return false;\n return !hasDisqualifyingFlag(sub, tokens);\n}\n/**\n* mise subcommands that are safe to auto-approve: read-only inspection (`ls`, `which`, `env`,\n* `current`, `doctor`), plus `install` and `trust`, which only provision the pins `mise.toml`\n* already declares (the same trust boundary the file itself carries) rather than fetching\n* anything the repo owner did not already name.\n*\n* `mise run <task>` is deliberately absent: resolving what a task body actually does would mean\n* parsing `mise.toml` here, a dependency this package does not carry, and the only task any\n* fleet repo declares today runs `sdkmanager`, which would prompt anyway under its own name. It\n* stays a passthrough prompt.\n*/\nconst MISE_SAFE_SUBCOMMANDS = /* @__PURE__ */ new Set([\n \"install\",\n \"ls\",\n \"which\",\n \"env\",\n \"trust\",\n \"current\",\n \"doctor\"\n]);\nfunction isMiseSafe(tokens) {\n const sub = tokens[1];\n if (!sub) return false;\n return MISE_SAFE_SUBCOMMANDS.has(sub);\n}\n/** True when `token` resolves to a `.md` file inside the project's `docs/` directory. */\nfunction isDocsMarkdownPath(token, ctx) {\n if (token.startsWith(\"~\")) return false;\n if (!token.endsWith(\".md\")) return false;\n const docsRoot = resolveIn(ctx.cwd, \"docs\");\n return isWithin(docsRoot, resolveIn(ctx.cwd, token));\n}\n/** True when a plain `mv` only moves `.md` files within `docs/`. */\nfunction isMvDocsSafe(tokens, ctx) {\n const paths = [];\n for (const t of tokens.slice(1)) {\n if (t.startsWith(\"-\")) {\n if (t === \"-t\" || t.startsWith(\"--target-directory\")) return false;\n continue;\n }\n paths.push(t);\n }\n return paths.length >= 2 && paths.every((p) => isDocsMarkdownPath(p, ctx));\n}\n/** Short sed flags that take no argument and may be bundled (e.g. `-ne`). */\nconst SED_NOARG_SHORT_CHARS = /* @__PURE__ */ new Set([\n \"n\",\n \"E\",\n \"r\",\n \"s\",\n \"u\",\n \"z\"\n]);\n/** Long sed flags that take no argument (an optional `=value` is still self-contained). */\nconst SED_KNOWN_LONG_NOARG = /* @__PURE__ */ new Set([\n \"--quiet\",\n \"--silent\",\n \"--debug\",\n \"--follow-symlinks\",\n \"--in-place\",\n \"--posix\",\n \"--regexp-extended\",\n \"--separate\",\n \"--sandbox\",\n \"--unbuffered\",\n \"--null-data\",\n \"--help\",\n \"--version\"\n]);\n/**\n* True when `sed` targets only `.md` files under `docs/`.\n*\n* Parses sed's argument grammar enough to tell the script token from file\n* tokens: when neither `-e` nor `-f` is present the first positional is the\n* script, otherwise every positional is a file. Unknown or ambiguous flags\n* make the check bail (-> passthrough) rather than guess.\n*/\nfunction isSedDocsSafe(tokens, ctx) {\n let sawScriptOption = false;\n let skipNext = false;\n const positionals = [];\n for (const t of tokens.slice(1)) {\n if (skipNext) {\n skipNext = false;\n continue;\n }\n if (t === \"-\" || !t.startsWith(\"-\")) {\n positionals.push(t);\n continue;\n }\n if (t.startsWith(\"--\")) {\n const eq = t.indexOf(\"=\");\n const base = eq === -1 ? t : t.slice(0, eq);\n if (base === \"--expression\" || base === \"--file\") {\n sawScriptOption = true;\n if (eq === -1) skipNext = true;\n continue;\n }\n if (base === \"--line-length\") {\n if (eq === -1) skipNext = true;\n continue;\n }\n if (SED_KNOWN_LONG_NOARG.has(base)) continue;\n return false;\n }\n const body = t.slice(1);\n for (let c = 0; c < body.length; c++) {\n const ch = body[c];\n if (ch === \"i\") break;\n if (ch === \"e\" || ch === \"f\") {\n sawScriptOption = true;\n if (c === body.length - 1) skipNext = true;\n break;\n }\n if (ch === \"l\") {\n if (c === body.length - 1) skipNext = true;\n break;\n }\n if (!SED_NOARG_SHORT_CHARS.has(ch)) return false;\n }\n }\n const files = sawScriptOption ? positionals : positionals.slice(1);\n return files.length > 0 && files.every((f) => isDocsMarkdownPath(f, ctx));\n}\nconst denyRules = [\n {\n name: \"rm-recursive\",\n match: /^rm$/,\n action: \"deny\",\n when: (tokens) => isRmRecursive(tokens),\n reason: \"Don't use recursive `rm`. Use more targeted deletes, or remove specific files/directories.\"\n },\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-reset-hard\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens, ctx) => isGitResetHardOnMain(tokens) && !isGitFeatureBranchWrite(tokens, ctx),\n reason: \"Don't `reset --hard` on main. Create a branch first.\"\n },\n {\n name: \"git-discard-changes\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens) => isGitDiscardChanges(tokens),\n reason: \"`-f` / `--discard-changes` throws uncommitted changes away, and nothing reports what was lost. Save them first -- `git stash push` is auto-approved -- then `git switch <branch>` and `git stash pop`. If the changes belong on the target branch, plain `git switch <branch>` carries them over. Ask the user before discarding anything.\"\n },\n {\n name: \"git-bare-checkout\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens) => isGitBareCheckout(tokens) && !isGitCheckoutPathRestore(tokens),\n reason: \"Use `git switch <branch>` to change branches -- unlike `checkout` it cannot discard uncommitted file changes, and it is auto-approved. To create a branch: `git switch -c <branch>` (or `git checkout -b <branch>`). To restore a file, name the path after a `--` separator (`git restore [--source <ref>] -- <path>`, or `git checkout [<ref>] -- <path>`); both prompt rather than being auto-approved.\"\n },\n {\n name: \"git-protected-branch-write\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens, ctx) => isGitProtectedBranchWrite(tokens, ctx),\n reason: \"Not auto-approved on a protected branch. Create a feature branch first -- `git switch -c <branch>` is auto-approved, and commit/merge/rebase/cherry-pick/revert/reset are auto-approved once you are on it. To push from there, `git push origin <branch>` with an explicit refspec.\"\n },\n {\n name: \"git-protected-branch-force-create\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens, ctx) => isGitProtectedBranchForceCreate(tokens, ctx),\n reason: \"Force-create (`switch -C`/`--force-create`, `checkout -B`, `worktree add -B`) doesn't just create a branch -- when the branch already exists it resets it to the start point (HEAD by default) and checks it out. To switch to this branch, plain `git switch <branch>` is auto-approved. To create a work branch, pick a non-protected name (`git switch -C <feature>` stays auto-approved). Resetting a protected branch to its origin state is the user's to run.\"\n },\n {\n name: \"git-protected-branch-ref-write\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens, ctx) => isGitProtectedBranchRefWrite(tokens, ctx),\n reason: \"Not auto-approved on a protected branch: `branch -d`/`-D` deleting one, `branch -f <protected> <start>` overwriting one, `branch -m`/`-M` renaming one away, or `branch -M`/`-C` renaming or copying onto one. To inspect branches, `git branch --list` is auto-approved. To create a work branch, pick a non-protected name (`git branch <feature>` stays auto-approved). Deleting, resetting or renaming a protected branch is the user's to run.\"\n },\n {\n name: \"git-write\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens, ctx) => isGitWriteOp(tokens) && !isGitSafeWriteOp(tokens, ctx) && !isGitFeatureBranchWrite(tokens, ctx) && !isGitSafePush(tokens, ctx) && !isGitPushWithoutExplicitRefspec(tokens, ctx) && !isGitUnrecognizedBranchFlag(tokens) && !isGitProtectedBranchCreate(tokens, ctx) && !isGitWorktreeMoveOrRemove(tokens) && !isGitCheckoutPathRestore(tokens) && !isGitReadOnlyForm(tokens),\n reason: \"This git write isn't auto-approved. Working-tree and history operations are auto-approved on a feature branch (`git switch -c <branch>` first); a push needs an explicit refspec from one (`git push origin <branch>`). Writing forms of `tag` and `clean` have no auto-approved form (their listing and dry-run forms do -- `git tag -l`, `git clean -n`); `stash drop`, `git rm -r`, and anything carrying a config/exec-injection or repo-redirect flag have none at all. All of these need the user to run them.\"\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: /^(cat|head|tail|less|more|grep)$/,\n action: \"deny\",\n when: (tokens) => tokensTargetEnvFile(tokens),\n reason: \"Don't read .env files. These contain secrets that should not appear in the conversation.\"\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];\nconst allowRules = [\n {\n name: \"vitest\",\n match: /^vitest$/,\n action: \"allow\"\n },\n {\n name: \"jest\",\n match: /^jest$/,\n action: \"allow\"\n },\n {\n name: \"tsc\",\n match: /^tsc$/,\n action: \"allow\"\n },\n {\n name: \"tsdown\",\n match: /^tsdown$/,\n action: \"allow\"\n },\n {\n name: \"bst\",\n match: /^bst$/,\n action: \"allow\",\n when: (tokens) => isBstSafe(tokens)\n },\n {\n name: \"bst-agent\",\n match: /^bst-agent$/,\n action: \"allow\",\n when: (tokens) => isBstSafe(tokens, BST_AGENT_ONLY_SAFE_SUBCOMMANDS)\n },\n {\n name: \"mise\",\n match: /^mise$/,\n action: \"allow\",\n when: (tokens) => isMiseSafe(tokens)\n },\n {\n name: \"oxlint\",\n match: /^oxlint$/,\n action: \"allow\"\n },\n {\n name: \"oxfmt\",\n match: /^oxfmt$/,\n action: \"allow\"\n },\n {\n name: \"eslint\",\n match: /^eslint$/,\n action: \"allow\"\n },\n {\n name: \"prettier\",\n match: /^prettier$/,\n action: \"allow\"\n },\n {\n name: \"biome\",\n match: /^biome$/,\n action: \"allow\"\n },\n {\n name: \"knip\",\n match: /^knip$/,\n action: \"allow\"\n },\n {\n name: \"cat\",\n match: /^cat$/,\n action: \"allow\"\n },\n {\n name: \"head\",\n match: /^head$/,\n action: \"allow\"\n },\n {\n name: \"tail\",\n match: /^tail$/,\n action: \"allow\"\n },\n {\n name: \"grep\",\n match: /^grep$/,\n action: \"allow\"\n },\n {\n name: \"ls\",\n match: /^ls$/,\n action: \"allow\"\n },\n {\n name: \"wc\",\n match: /^wc$/,\n action: \"allow\"\n },\n {\n name: \"uniq\",\n match: /^uniq$/,\n action: \"allow\"\n },\n {\n name: \"diff\",\n match: /^diff$/,\n action: \"allow\"\n },\n {\n name: \"echo\",\n match: /^echo$/,\n action: \"allow\"\n },\n {\n name: \"pwd\",\n match: /^pwd$/,\n action: \"allow\"\n },\n {\n name: \"which\",\n match: /^which$/,\n action: \"allow\"\n },\n {\n name: \"dirname\",\n match: /^dirname$/,\n action: \"allow\"\n },\n {\n name: \"basename\",\n match: /^basename$/,\n action: \"allow\"\n },\n {\n name: \"realpath\",\n match: /^realpath$/,\n action: \"allow\"\n },\n {\n name: \"npm\",\n match: /^npm$/,\n action: \"allow\",\n when: (tokens) => isNpmReadOp(tokens)\n },\n {\n name: \"test\",\n match: /^test$/,\n action: \"allow\"\n },\n {\n name: \"bracket-test\",\n match: /^\\[$/,\n action: \"allow\"\n },\n {\n name: \"cd\",\n match: /^cd$/,\n action: \"allow\"\n },\n {\n name: \"true\",\n match: /^true$/,\n action: \"allow\"\n },\n {\n name: \"colon\",\n match: /^:$/,\n action: \"allow\"\n },\n {\n name: \"find\",\n match: /^find$/,\n action: \"allow\",\n when: (tokens) => isFindSafe(tokens)\n },\n {\n name: \"sort\",\n match: /^sort$/,\n action: \"allow\",\n when: (tokens) => isSortSafe(tokens)\n },\n {\n name: \"git\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens) => isGitReadOp(tokens)\n },\n {\n name: \"git-read-only-form\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens) => isGitReadOnlyForm(tokens)\n },\n {\n name: \"git-safe-write\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens, ctx) => isGitSafeWriteOp(tokens, ctx),\n advise: (tokens, ctx) => isGitBranchCreation(tokens) && isPrimaryWorktree(ctx.cwd) ? WORKTREE_ADVICE : void 0\n },\n {\n name: \"git-feature-branch-write\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens, ctx) => isGitFeatureBranchWrite(tokens, ctx)\n },\n {\n name: \"git-safe-push\",\n match: /^git$/,\n action: \"allow\",\n when: (tokens, ctx) => isGitSafePush(tokens, ctx)\n },\n {\n name: \"pnpm\",\n match: /^pnpm$/,\n action: \"allow\",\n when: (tokens) => isPnpmSafe(tokens)\n },\n {\n name: \"cp\",\n match: /^cp$/,\n action: \"allow\",\n when: (tokens, ctx) => arePathArgsSafe(tokens, ctx)\n },\n {\n name: \"mkdir\",\n match: /^mkdir$/,\n action: \"allow\",\n when: (tokens, ctx) => arePathArgsSafe(tokens, ctx)\n },\n {\n name: \"touch\",\n match: /^touch$/,\n action: \"allow\",\n when: (tokens, ctx) => arePathArgsSafe(tokens, ctx)\n },\n {\n name: \"mv-docs\",\n match: /^mv$/,\n action: \"allow\",\n when: (tokens, ctx) => isMvDocsSafe(tokens, ctx)\n },\n {\n name: \"sed-docs\",\n match: /^sed$/,\n action: \"allow\",\n when: (tokens, ctx) => isSedDocsSafe(tokens, ctx)\n }\n];\n\n//#endregion\n//#region src/lib/workspace-root.ts\n/**\n* Walk up from `cwd` to the directory holding `pnpm-workspace.yaml`.\n*\n* Its own module rather than a `script-resolver.ts` export because\n* `settings.ts` needs it and `script-resolver.ts` needs `settings.ts`,\n* which is a cycle the health gate reports.\n*/\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/lib/settings.ts\nconst SETTINGS_PATH = \".claude/hooks/bst-hook.settings.json\";\nconst BstHookSettingsSchema = object({\n allow: array(string().min(1)).default([]),\n protectedBranches: array(string().min(1)).default([])\n});\n/**\n* Check whether a file at `relativePath` inside `cwd` is tracked and\n* unmodified in git (no staged or unstaged changes vs HEAD).\n*\n* Returns false on any error -- safe default is to not trust the file.\n*/\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}\n/**\n* Load repo-level settings from `.claude/hooks/bst-hook.settings.json`.\n*\n* Walks up from `cwd` to find the workspace root (or uses `cwd` directly),\n* reads the settings file, validates it, and checks that the file is\n* git-tracked and unmodified before trusting it.\n*\n* Returns `{ allow: [], protectedBranches: [] }` if the file is missing,\n* invalid, or untrusted.\n*/\nfunction loadRepoSettings(cwd) {\n const root = findWorkspaceRoot(cwd) ?? cwd;\n const settingsFile = join(root, SETTINGS_PATH);\n const empty = {\n allow: [],\n protectedBranches: []\n };\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 {\n allow: result.data.allow,\n protectedBranches: result.data.protectedBranches\n };\n } catch {\n return empty;\n }\n}\n/**\n* Check whether a command matches a repo-level allow pattern.\n*\n* Pattern format: `\"<command> [arg1] [arg2] ...\"`\n* - First token is matched exactly against the command basename\n* - Remaining tokens (if any) must match positionally as prefixes of the actual arguments\n*\n* Examples:\n* - `\"tooling\"` matches `tooling repo:sync --yes`\n* - `\"node packages/foo/dist/bin.mjs\"` matches `node packages/foo/dist/bin.mjs run`\n* - `\"node packages/foo/dist/bin.mjs\"` does NOT match `node packages/bar/dist/bin.mjs`\n*/\nfunction matchesRepoPattern(cmdName, tokens, pattern) {\n const parts = pattern.split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return false;\n if (parts[0] !== cmdName) return false;\n const patternArgs = parts.slice(1);\n const actualArgs = tokens.slice(1);\n for (let i = 0; i < patternArgs.length; i++) {\n if (i >= actualArgs.length) return false;\n if (actualArgs[i] !== patternArgs[i]) return false;\n }\n return true;\n}\n\n//#endregion\n//#region src/lib/script-resolver.ts\nconst PackageJsonSchema = object({ scripts: record(string(), unknown()) });\n/**\n* Resolve a pnpm script name to its actual command(s) by reading package.json.\n*\n* Returns null if the script cannot be resolved (missing package.json,\n* missing script name, parse error).\n*/\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}\n/**\n* Resolve a script in a specific workspace package identified by --filter.\n*\n* Reads pnpm-workspace.yaml, scans workspace directories to match filterPkg\n* against package.json#name fields, then resolves the script in that package's directory.\n*\n* Returns null if the package or script cannot be resolved.\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}\n/**\n* Resolve a script name across all workspace packages (for `pnpm -r <script>`).\n*\n* Returns an array of resolved scripts (one per workspace package that defines the script),\n* each with a `pkgDir` field. Returns null if workspace patterns can't be read.\n* Packages that don't define the script are silently skipped.\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}\n/**\n* List all workspace package directories.\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}\n/**\n* Find the directory of a workspace package by name.\n* Reads pnpm-workspace.yaml for glob patterns, then scans matching directories.\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}\n/**\n* Read workspace glob patterns from pnpm-workspace.yaml.\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() });\n/**\n* Read the name field from package.json in the given directory.\n*/\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}\n/**\n* Read the scripts field from package.json in the given directory.\n* Returns null on any failure (missing file, parse error, no scripts field).\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* Check whether the package.json in the given directory is clean in git\n* (no staged or unstaged changes compared to HEAD).\n*\n* Returns true only when the file is tracked and unmodified. Returns false\n* on any error (not a git repo, untracked file, no HEAD, etc.) -- safe default\n* is to not trust the scripts.\n*/\nfunction isPackageJsonClean(pkgDir) {\n return isFileClean(pkgDir, \"package.json\");\n}\n\n//#endregion\n//#region src/evaluate.ts\nconst MAX_EVAL_DEPTH = 5;\n/** Environment variables that enable code injection */\nconst DANGEROUS_ENV_VARS = /* @__PURE__ */ new Set([\n \"LD_PRELOAD\",\n \"LD_LIBRARY_PATH\",\n \"DYLD_INSERT_LIBRARIES\",\n \"NODE_OPTIONS\",\n \"PYTHONPATH\",\n \"PYTHONSTARTUP\",\n \"PERL5OPT\",\n \"PERL5LIB\",\n \"RUBYOPT\",\n \"RUBYLIB\",\n \"BASH_ENV\",\n \"ENV\",\n \"npm_config_prefix\",\n \"npm_config_registry\",\n \"EDITOR\",\n \"VISUAL\"\n]);\n/** Git config env vars use prefix matching */\nconst DANGEROUS_GIT_ENV_PREFIXES = [\n \"GIT_CONFIG_\",\n \"GIT_PAGER\",\n \"GIT_SSH_COMMAND\",\n \"GIT_EDITOR\",\n \"GIT_EXEC_PATH\"\n];\nfunction isDangerousEnvVar(name) {\n if (DANGEROUS_ENV_VARS.has(name)) return true;\n return DANGEROUS_GIT_ENV_PREFIXES.some((prefix) => name.startsWith(prefix));\n}\n/**\n* Extract the target directory from a `cd <dir>` segment.\n* Returns the resolved absolute path, or null if the segment isn't a simple `cd`.\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}\n/**\n* Evaluate a full command string. Splits on operators and evaluates each segment.\n*\n* This is the single reentrant entry point. Called at depth 0 for top-level\n* commands and at depth 1+ for derived commands (resolved scripts, exec tails, etc.).\n*/\nfunction evaluateCommand(command, ctx) {\n if (ctx.depth >= MAX_EVAL_DEPTH) return {\n action: \"passthrough\",\n rule: null,\n reason: \"max evaluation depth exceeded\"\n };\n const trimmed = command.trim();\n if (!trimmed) return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n if (trimmed.startsWith(\"(\") && trimmed.endsWith(\")\")) return {\n action: \"passthrough\",\n rule: null,\n reason: \"subshell\"\n };\n const segments = splitCommand(trimmed);\n if (segments.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n let firstPassthrough = null;\n let lastAllow = null;\n const currentCtx = { ...ctx };\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 (result.action === \"passthrough\") {\n if (!firstPassthrough) firstPassthrough = result;\n }\n if (result.action === \"allow\") lastAllow = result;\n }\n if (firstPassthrough) return firstPassthrough;\n if (segments.length === 1 && lastAllow) return lastAllow;\n return {\n action: \"allow\",\n rule: \"all-segments\",\n reason: null\n };\n}\n/**\n* Helper to evaluate a resolved script with its pre and post hooks.\n*\n* When the source package.json is clean in git, sets `insideResolvedScript`\n* on the context so downstream evaluation trusts file redirects and\n* unmatched commands (user-authored scripts). Deny rules still apply.\n*/\nfunction evaluateResolvedScript(resolved, ctx) {\n const pkgDir = resolved.pkgDir ?? ctx.cwd;\n const innerCtx = ctx.insideResolvedScript ?? isPackageJsonClean(pkgDir) ? {\n ...ctx,\n insideResolvedScript: true\n } : ctx;\n if (resolved.pre) {\n const preResult = evaluateCommand(resolved.pre, innerCtx);\n if (preResult.action !== \"allow\") return preResult;\n }\n const mainResult = evaluateCommand(resolved.main, innerCtx);\n if (mainResult.action !== \"allow\") return mainResult;\n if (resolved.post) {\n const postResult = evaluateCommand(resolved.post, innerCtx);\n if (postResult.action !== \"allow\") return postResult;\n }\n return \"allow\";\n}\n/**\n* Evaluate a single command segment (no operators).\n* May recurse into evaluateCommand() for indirection constructs.\n*/\nfunction evaluateSegment(rawSegment, ctx) {\n const trimmed = rawSegment.trim();\n if (trimmed.startsWith(\"(\") && trimmed.endsWith(\")\")) return {\n action: \"passthrough\",\n rule: null,\n reason: \"subshell\"\n };\n const tokenResult = tokenize(rawSegment);\n if (!tokenResult.ok) return {\n action: \"passthrough\",\n rule: null,\n reason: tokenResult.error\n };\n let tokens = tokenResult.tokens;\n if (tokens.length === 0) return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n let envSkipped = 0;\n while (envSkipped < tokens.length && /^\\w+=/.test(tokens[envSkipped])) {\n const varName = tokens[envSkipped].split(\"=\")[0];\n if (isDangerousEnvVar(varName)) return {\n action: \"passthrough\",\n rule: null,\n reason: `dangerous env var: ${varName}`\n };\n envSkipped++;\n }\n if (envSkipped >= tokens.length) return {\n action: \"allow\",\n rule: \"env-only\",\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, ctx.insideResolvedScript);\n if (!injection.safe) return {\n action: \"passthrough\",\n rule: null,\n reason: injection.reason\n };\n if (!ctx.insideResolvedScript) for (let i = 0; i < tokens.length; i++) {\n const token = tokens[i];\n if (token === \"2>&1\" || token === \"2>/dev/null\" || token === \">/dev/null\" || token === \"1>/dev/null\") continue;\n if (token === \">\" || token === \">>\") {\n if (tokens[i + 1] === \"/dev/null\") continue;\n return {\n action: \"passthrough\",\n rule: null,\n reason: \"output redirect to file\"\n };\n }\n if (token === \"<\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"input redirect\"\n };\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])) {\n const varName = tokens[envIdx].split(\"=\")[0];\n if (isDangerousEnvVar(varName)) return {\n action: \"passthrough\",\n rule: null,\n reason: `dangerous env var: ${varName}`\n };\n envIdx++;\n }\n if (envIdx >= tokens.length) return {\n action: \"allow\",\n rule: \"env-only\",\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: \"passthrough\",\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: \"passthrough\",\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 for (const rule of allowRules) if (rule.match.test(cmdName)) {\n if (!rule.when || rule.when(tokens, ctx)) {\n const advice = rule.advise?.(tokens, ctx);\n return {\n action: \"allow\",\n rule: rule.name,\n reason: null,\n ...advice ? { advice } : {}\n };\n }\n }\n if (ctx.repoAllowPatterns) {\n for (const pattern of ctx.repoAllowPatterns) if (matchesRepoPattern(cmdName, tokens, pattern)) return {\n action: \"allow\",\n rule: `repo:${cmdName}`,\n reason: null\n };\n }\n if (ctx.insideResolvedScript) return {\n action: \"allow\",\n rule: \"resolved-script\",\n reason: null\n };\n return {\n action: \"passthrough\",\n rule: null,\n reason: `no rule matched: ${cmdName}`\n };\n}\n/**\n* The inner command of `mise exec [tool@version...] -- <cmd...>`: everything after the `--`\n* separator. `undefined` when there is no `--` (a bare `mise exec` with nothing to unwrap) or\n* nothing follows it.\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}\n/**\n* Scan pnpm subTokens starting at `from`, skipping and accumulating flags\n* (--filter / -F, -r, -w, -C, and generic --flags). Returns the first non-flag\n* token as `name`, or `undefined` if none found. Returns `\"filter\"` as a\n* sentinel when --filter is encountered (caller must handle).\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}\n/**\n* Evaluate a pnpm command. Classifies the subcommand as built-in, exec, or script.\n* Script commands are resolved via package.json and recursively evaluated.\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: \"passthrough\",\n rule: null,\n reason: null\n };\n if (subcommand === \"dlx\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm dlx\"\n };\n if (subcommand === \"add\") return {\n action: \"passthrough\",\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: \"passthrough\",\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: \"passthrough\",\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 {\n for (const rule of allowRules) if (rule.match.test(\"pnpm\")) {\n if (!rule.when || rule.when(tokens, ctx)) return {\n action: \"allow\",\n rule: rule.name,\n reason: null\n };\n }\n return {\n action: \"passthrough\",\n rule: null,\n reason: null\n };\n }\n if (isRecursive) return evaluatePnpmRecursive(scriptName, ctx);\n if (isWorkspaceRoot) {\n const root = findWorkspaceRoot(ctx.cwd);\n if (!root) return {\n action: \"passthrough\",\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}\n/**\n* Resolve a pnpm script name via package.json and recursively evaluate\n* the resolved command(s) including pre/post scripts.\n*/\nfunction evaluatePnpmScript(scriptName, ctx) {\n const resolved = resolveScript(scriptName, ctx.cwd);\n if (!resolved) return {\n action: \"passthrough\",\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 !== \"allow\") return res;\n return {\n action: \"allow\",\n rule: `pnpm-script:${scriptName}`,\n reason: null\n };\n}\n/**\n* Evaluate `pnpm -r <script>` by resolving the script in each workspace package.\n*\n* Unlike `evaluatePnpmScript` (which resolves from the root package.json and would\n* cycle back to `pnpm -r <script>`), this resolves against each workspace package's\n* own package.json, breaking the recursion.\n*/\nfunction evaluatePnpmRecursive(scriptName, ctx) {\n const resolved = resolveRecursiveScripts(scriptName, ctx.cwd);\n if (!resolved || resolved.length === 0) return {\n action: \"passthrough\",\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 !== \"allow\") return res;\n }\n return {\n action: \"allow\",\n rule: `pnpm-recursive:${scriptName}`,\n reason: null\n };\n}\n/**\n* Evaluate an npm command. Read-only ops (view/info/show) are allowed.\n* Everything else is denied with a redirect message to the pnpm equivalent.\n*/\nfunction evaluateNpm(tokens) {\n for (const rule of allowRules) if (rule.match.test(\"npm\")) {\n if (!rule.when || rule.when(tokens, {\n cwd: \"\",\n depth: 0\n })) return {\n action: \"allow\",\n rule: rule.name,\n reason: null\n };\n }\n return {\n action: \"deny\",\n rule: \"npm-redirect\",\n reason: getNpmRedirectReason(tokens)\n };\n}\n/**\n* Evaluate `pnpm --filter <pkg> [--filter <pkg> ...] <subcommand>`.\n*\n* Collects every `--filter` / `-F` flag (long, equals, and shorthand forms),\n* resolves each against pnpm-workspace.yaml, then classifies the subcommand as\n* built-in or script. Script commands are resolved and evaluated in each\n* filtered package -- all must allow.\n*\n* Falls back to passthrough if workspace resolution fails.\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: \"passthrough\",\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: \"passthrough\",\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: \"passthrough\",\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: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter without subcommand\"\n };\n if (filterSubcommand === \"install\" || filterSubcommand === \"i\") {\n if (!subTokens.slice(subcmdIdx + 1).every((t) => t.startsWith(\"-\"))) return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter install with package arg\"\n };\n return {\n action: \"allow\",\n rule: \"pnpm\",\n reason: null\n };\n }\n if (filterSubcommand === \"dlx\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm dlx\"\n };\n if (filterSubcommand === \"add\") return {\n action: \"passthrough\",\n rule: null,\n reason: \"pnpm add\"\n };\n if ((/* @__PURE__ */ new Set([\n \"list\",\n \"ls\",\n \"ll\",\n \"la\",\n \"why\",\n \"view\",\n \"info\",\n \"show\",\n \"outdated\",\n \"licenses\",\n \"audit\"\n ])).has(filterSubcommand)) return {\n action: \"allow\",\n rule: \"pnpm\",\n reason: null\n };\n if (filterSubcommand === \"exec\") {\n const execArgs = subTokens.slice(subcmdIdx + 1);\n if (execArgs.length === 0) return {\n action: \"passthrough\",\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: \"passthrough\",\n rule: null,\n reason: \"pnpm --filter run without script name\"\n };\n if (PNPM_BUILTINS.has(scriptName) && scriptName !== \"run\") return {\n action: \"passthrough\",\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: \"passthrough\",\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 !== \"allow\") return res;\n }\n return {\n action: \"allow\",\n rule: `pnpm-filter:${filterPkgs.join(\",\")}:${scriptName}`,\n reason: null\n };\n}\n/** Flags that modify shell behavior beyond simple command execution */\nconst SHELL_UNSAFE_FLAGS = /* @__PURE__ */ new Set([\n \"-i\",\n \"--interactive\",\n \"-l\",\n \"--login\",\n \"--init-file\",\n \"--rcfile\",\n \"--norc\",\n \"--noprofile\",\n \"-s\",\n \"--stdin\"\n]);\n/**\n* Evaluate `bash -c` / `sh -c` / `zsh -c` commands.\n*\n* Only unwraps when the tokens are exactly: `<shell> -c <command-string>`.\n* Any additional flags, extra positional args, or missing -c -> passthrough.\n*/\nfunction evaluateShellC(tokens, ctx) {\n const cIdx = tokens.indexOf(\"-c\");\n if (cIdx === -1) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell without -c\"\n };\n for (let i = 1; i < tokens.length; i++) {\n if (i === cIdx) continue;\n if (i === cIdx + 1) continue;\n const t = tokens[i];\n if (t.startsWith(\"-\") || SHELL_UNSAFE_FLAGS.has(t)) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c with extra flags\"\n };\n }\n const cmdArg = tokens[cIdx + 1];\n if (!cmdArg) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c without command\"\n };\n if (tokens.length > cIdx + 2) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c with positional args\"\n };\n if (cIdx !== 1) return {\n action: \"passthrough\",\n rule: null,\n reason: \"shell -c with extra flags\"\n };\n return evaluateCommand(cmdArg, {\n ...ctx,\n depth: ctx.depth + 1\n });\n}\n/** xargs flags that consume the next token as an argument */\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]);\n/** xargs flags that are boolean (no argument) */\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]);\n/**\n* Evaluate `xargs <cmd>` commands.\n*\n* Skips xargs flags to find the inner command, then recursively evaluates it.\n* Passthrough if no command follows the flags or if unrecognised flags are found.\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: \"passthrough\",\n rule: null,\n reason: \"xargs with unrecognised flags\"\n };\n }\n if (i >= tokens.length) return {\n action: \"passthrough\",\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\"]);\n/**\n* The harness directs every temporary file to a per-session scratchpad under\n* the system temp directory, so denying it puts the hook at odds with the\n* prompt it ships beside.\n*\n* Matching the session id as a path segment rather than reconstructing the\n* documented full path is deliberate: the reconstruction encodes several\n* harness conventions, and when one changes it fails silently back to denying\n* every scratchpad write. The id arrives in the same payload as the path, so\n* the reach is one session's own temp tree.\n*\n* The payload schema only requires a string, so a degenerate id (empty, or a\n* couple of characters) would match as a path segment of nearly every path\n* under the temp directory and widen the allow to all of it. Real ids are\n* UUIDs; the length floor bounds the blast radius by construction rather than\n* trusting the harness to always produce one.\n*\n* `isWithin` compares lexically and does not resolve symlinks, so a symlink\n* planted inside the scratchpad pointing outside it is not caught. That\n* residual is accepted deliberately, the same as the cwd-scoped rules below --\n* the hook is a guardrail against accidents and prompt fatigue, not a sandbox.\n*/\nfunction isSessionScratch(filePath, ctx) {\n const { sessionId } = ctx;\n if (!sessionId || sessionId.length < MIN_SESSION_ID_LENGTH) return false;\n const resolved = resolveIn(ctx.cwd, filePath);\n if (!isWithin(os.tmpdir(), resolved)) return false;\n return resolved.split(path.sep).includes(sessionId);\n}\n/** Real session ids are UUIDs; see the length-floor rationale above. */\nconst MIN_SESSION_ID_LENGTH = 16;\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: \"outside-cwd-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx) => {\n const resolved = resolveIn(ctx.cwd, filePath);\n if (isWithin(ctx.cwd, resolved)) return false;\n const homeClaudeDir = path.join(os.homedir(), \".claude\");\n if (isWithin(homeClaudeDir, resolved)) return false;\n if (isSessionScratch(filePath, ctx)) return false;\n return true;\n },\n reason: \"Don't write files outside the project directory.\"\n }\n];\nconst fileAllowRules = [\n {\n name: \"session-scratchpad-read\",\n tools: READ_TOOLS,\n action: \"allow\",\n when: (filePath, ctx) => isSessionScratch(filePath, ctx)\n },\n {\n name: \"session-scratchpad-write\",\n tools: WRITE_TOOLS,\n action: \"allow\",\n when: (filePath, ctx) => isSessionScratch(filePath, ctx)\n },\n {\n name: \"cwd-read\",\n tools: READ_TOOLS,\n action: \"allow\",\n when: (filePath, ctx) => isWithin(ctx.cwd, resolveIn(ctx.cwd, filePath))\n },\n {\n name: \"cwd-write\",\n tools: WRITE_TOOLS,\n action: \"allow\",\n when: (filePath, ctx) => isWithin(ctx.cwd, resolveIn(ctx.cwd, filePath))\n }\n];\n\n//#endregion\n//#region src/evaluate-tool.ts\n/** Tools whose input contains a `file_path` field. */\nconst FILE_PATH_TOOLS = /* @__PURE__ */ new Set([\n \"Read\",\n \"Write\",\n \"Edit\"\n]);\n/** Tools whose input contains a `path` field (search root, optional). */\nconst SEARCH_TOOLS = /* @__PURE__ */ new Set([\"Glob\", \"Grep\"]);\n/**\n* Extract the relevant file path from tool input based on tool name.\n* Returns null if the input shape doesn't match expectations.\n*/\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}\n/**\n* Evaluate a non-Bash tool invocation against file-tool rules.\n* Returns allow, deny, or passthrough.\n*/\nfunction evaluateToolUse(toolName, toolInput, ctx) {\n const filePath = extractPath(toolName, toolInput, ctx.cwd);\n if (filePath === null) return {\n action: \"passthrough\",\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)) return {\n action: \"deny\",\n rule: rule.name,\n reason: rule.reason ?? null\n };\n for (const rule of fileAllowRules) if (rule.tools.has(toolName) && rule.when(filePath, ctx)) return {\n action: \"allow\",\n rule: rule.name,\n reason: null\n };\n return {\n action: \"passthrough\",\n rule: null,\n reason: \"no file rule matched\"\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: \"passthrough\",\n rule: null,\n reason: `validation failed: ${inputResult.error}`\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: \"passthrough\",\n rule: null,\n reason: \"Bash tool_input missing or invalid 'command' field\"\n });\n process.exit(0);\n }\n decision = evaluateCommand(bashInput.command, {\n cwd: input.cwd,\n depth: 0,\n repoAllowPatterns: repoSettings.allow,\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});\nif (decision.action === \"allow\") writeDecision(\"allow\", decision.advice);\nelse if (decision.action === \"deny\") writeDecision(\"deny\", decision.reason);\n\n//#endregion\nexport { };", "bst-hook.mjs"), writeIfChanged(ctx, GUIDE_PATH, "# bst-hook Permission Guide\n\nThis project uses a PreToolUse hook (`bst-hook`) that evaluates Bash commands\nand file tool operations before execution. Commands fall into three tiers:\n\n- **Denied** -- blocked with a redirect message\n- **Allowed** -- auto-approved, no prompt\n- **Passthrough** -- falls through to Claude Code's normal permission prompt\n\nAn allow may additionally carry a non-blocking advisory -- e.g. creating a feature branch\nin the primary worktree suggests using a `git worktree` instead. It never blocks; the\ntiers above are unchanged.\n\n## Denied commands\n\nThese are blocked. The hook tells you what to do instead.\n\n### Bash\n\n- **rm-recursive**: Don't use recursive `rm`. Use more targeted deletes, or remove specific files/directories.\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-reset-hard**: Don't `reset --hard` on main. Create a branch first.\n- **git-discard-changes**: `-f` / `--discard-changes` throws uncommitted changes away, and nothing reports what was lost. Save them first -- `git stash push` is auto-approved -- then `git switch <branch>` and `git stash pop`. If the changes belong on the target branch, plain `git switch <branch>` carries them over. Ask the user before discarding anything.\n- **git-bare-checkout**: Use `git switch <branch>` to change branches -- unlike `checkout` it cannot discard uncommitted file changes, and it is auto-approved. To create a branch: `git switch -c <branch>` (or `git checkout -b <branch>`). To restore a file, name the path after a `--` separator (`git restore [--source <ref>] -- <path>`, or `git checkout [<ref>] -- <path>`); both prompt rather than being auto-approved.\n- **git-protected-branch-write**: Not auto-approved on a protected branch. Create a feature branch first -- `git switch -c <branch>` is auto-approved, and commit/merge/rebase/cherry-pick/revert/reset are auto-approved once you are on it. To push from there, `git push origin <branch>` with an explicit refspec.\n- **git-protected-branch-force-create**: Force-create (`switch -C`/`--force-create`, `checkout -B`, `worktree add -B`) doesn't just create a branch -- when the branch already exists it resets it to the start point (HEAD by default) and checks it out. To switch to this branch, plain `git switch <branch>` is auto-approved. To create a work branch, pick a non-protected name (`git switch -C <feature>` stays auto-approved). Resetting a protected branch to its origin state is the user's to run.\n- **git-protected-branch-ref-write**: Not auto-approved on a protected branch: `branch -d`/`-D` deleting one, `branch -f <protected> <start>` overwriting one, `branch -m`/`-M` renaming one away, or `branch -M`/`-C` renaming or copying onto one. To inspect branches, `git branch --list` is auto-approved. To create a work branch, pick a non-protected name (`git branch <feature>` stays auto-approved). Deleting, resetting or renaming a protected branch is the user's to run.\n- **git-write**: This git write isn't auto-approved. Working-tree and history operations are auto-approved on a feature branch (`git switch -c <branch>` first); a push needs an explicit refspec from one (`git push origin <branch>`). Writing forms of `tag` and `clean` have no auto-approved form (their listing and dry-run forms do -- `git tag -l`, `git clean -n`); `stash drop`, `git rm -r`, and anything carrying a config/exec-injection or repo-redirect flag have none at all. All of these need the user to run them.\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- **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- **outside-cwd-write**: Don't write files outside the project directory.\n\n## Auto-approved commands\n\nThese run without prompting.\n\n### Bash\n\n- **Test runners**: vitest, jest\n- **Type checking**: tsc\n- **Build tools**: tsdown\n- **Tooling CLI**: bst (checks:run, hooks:audit, docker:build, docker:check, repo:sync), bst-agent (the agent package's bin: every safe bst subcommand, plus agent:repair-candidates, agent:review-rounds, agent:status, agent:token-rate and agent:transcript. agent:start, agent:pause and agent:resume prompt)\n- **mise (toolchain provisioning)**: mise (install, ls, which, env, trust, current, doctor. `mise exec [tool@version...] -- <cmd>` unwraps and re-evaluates <cmd>; `mise run <task>` and `mise self-update` prompt.)\n- **Linters & formatters**: oxlint, oxfmt, eslint, prettier, biome, knip\n- **Read-only commands**: cat, head, tail, grep, ls, wc, uniq, diff, echo, pwd, which, dirname, basename, realpath\n- **Package managers (read-only)**: npm (view, info, show only)\n- **Shell builtins**: test, bracket-test\n- **Navigation**: cd, true, colon\n- **Conditional commands**: find (without -exec, -delete), sort (without -o / --output)\n- **Git read operations**: git (status, log, diff, show, blame, remote, fetch, rev-parse, etc.)\n- **Git read-only forms of write verbs**: git-read-only-form (`--help` immediately after any subcommand (not `-h` -- an alias still runs with it); `git tag` listing forms (bare, -l/--list, --points-at, --contains, --merged, --sort, --format); `git clean -n`/`--dry-run` without -f; `git branch` listing forms (bare, -a/-r, -l/--list, --show-current, -v, --contains/--merged/--points-at, --sort=/--format=). A positional beside a branch flag that isn't one of those selectors is a branch name, not a pattern -- `git branch -v main` creates and prompts. Writing forms of tag and clean stay denied)\n- **Git safe writes**: git-safe-write (add, `switch <branch>` (existing branch), switch -c/-C (new branch), checkout -b/-B (new branch), `branch <name> [<start-point>]` (new branch, no checkout, with the same protected-name rule as switch -c), stash push/pop/apply/show, mv (docs/*.md only), worktree list/add (remove/prune/move prompt -- they can delete uncommitted work). -C/-B/--force-create onto a protected branch name (main/master/default, or repo-configured) is denied instead of created, since it resets an existing branch rather than merely creating one; -c/-b/--create onto one prompts instead of auto-approving, since with no local ref of that name it succeeds and parks the session on a protected branch (`switch <branch>` stays auto-approved). switch/checkout otherwise auto-approve only with recognized non-discarding flags: -f/--discard-changes discards uncommitted work and is denied, any other flag prompts)\n- **Git writes (feature branches)**: git-feature-branch-write (commit, merge, rebase, cherry-pick, revert, reset (only when current branch is not main/master/default; push is never auto-approved))\n- **Git push (feature branches)**: git-safe-push (push with an explicit refspec (e.g. `git push origin feat/x`) from a non-protected branch to a non-protected destination on a configured remote (--force-with-lease ok; --force, --tags, --delete, --mirror, and pushes to main are not). A push with no refspec (bare `git push` / `git push <remote>`) is never auto-approved -- its destination comes from push.default/upstream config, which is not resolved here)\n- **pnpm (safe built-ins)**: pnpm (list, why, view, info, show, outdated, install (bare only))\n- **File operations (within project)**: cp (paths must be within project directory), mkdir (paths must be within project directory), touch (paths must be within project directory), mv-docs (mv only between docs/*.md paths (plan-lifecycle moves)), sed-docs (sed only on docs/*.md files (plan-lifecycle edits))\n\n### File tools\n\n- **Read/Glob/Grep**: the per-session scratchpad under the system temp directory\n- **Write/Edit**: the per-session scratchpad under the system temp directory\n- **Read/Glob/Grep**: Allowed within project directory\n- **Write/Edit**: Allowed within project directory\n\n## Passthrough\n\nEverything not listed above triggers Claude Code's normal permission prompt.\nCommon examples:\n\n- `pnpm add <pkg>`, `pnpm dlx <pkg>` (dependency changes)\n- `mv`, `git mv` (file moves outside `docs/*.md`)\n- `git rm` (without `-r`)\n- `node`, `npx` scripts\n- Commands with output redirection (`> file`)\n- Any unrecognized command\n\n## How commands are evaluated\n\n- **Pipelines and chains**: every segment must be allowed; any deny 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- **Audit log**: all decisions are logged to `.claude/hooks/bst-hook.audit.local.jsonl`.\n", "bst-hook-guide.md")];
8716
8907
  }
8717
8908
  //#endregion
8718
8909
  //#region src/generators/claude-settings.ts
@@ -9348,7 +9539,8 @@ const FILE = "CLAUDE.md";
9348
9539
  const AGENT_RULES_HEAD = [
9349
9540
  "### Agent rules",
9350
9541
  "",
9351
- "- Use pnpm, not npm/yarn/npx. Run binaries with `pnpm exec`."
9542
+ "- Use pnpm, not npm/yarn/npx. Run binaries with `pnpm exec`.",
9543
+ "- Tools come from `mise install` (`mise.toml`); run commands via `mise exec` or an activated shell. A non-interactive session gets them only if mise's shims are on PATH (`mise activate --shims` in the login profile)."
9352
9544
  ];
9353
9545
  const TS_TYPECAST_RULE = "- No typecasts (`as`/`any`). Use zod schemas, type guards, or narrowing.";
9354
9546
  const AGENT_RULES_MID = [
@@ -9357,9 +9549,15 @@ const AGENT_RULES_MID = [
9357
9549
  "- Write in plain language, per ISO 24495-1 -- every word you produce, code comments and commit bodies included."
9358
9550
  ];
9359
9551
  const TS_EXTENSIONLESS_RULE = "- Prefer extensionless imports; if an extension is required, use `.ts` over `.js`.";
9552
+ /**
9553
+ * Gated on `releaseStrategy: "changesets"`: in any other repo there is no `.changeset/` to
9554
+ * write into. Inline rather than a guide, because a missed changeset is a release defect, and
9555
+ * no file read happens at the moment one is owed to hang a guide trigger on.
9556
+ */
9557
+ 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.";
9360
9558
  const AGENT_RULES_TAIL = [
9361
9559
  "- The bst-hook manages Bash/file permissions -- see `.claude/hooks/bst-hook-guide.md`.",
9362
- "- Machine-reading a `bst` command? Pass `--json` _after the command name_ (`bst <command> --json`, not `bst --json <command>`): the whole capture (`2>&1` included) is then exactly one JSON document -- a failure too, as `{ok: false, error}`. Parse that; never scrape the human output. `--verbose` re-opens the stream and breaks it.",
9560
+ "- Machine-reading a `bst` command? Pass `--json` after the command name (`bst <cmd> --json`, not `bst --json <cmd>`); capture `2>&1` and parse it as one JSON document, failures included as `{ok: false, error}`. `--verbose` breaks this.",
9363
9561
  "",
9364
9562
  "#### Git discipline",
9365
9563
  "",
@@ -9367,7 +9565,7 @@ const AGENT_RULES_TAIL = [
9367
9565
  "",
9368
9566
  "**Small change** -- a fix, a doc edit, a one-file tweak: work on the default branch and leave it unstaged. Don't commit; surface the commit message as text for the user to run.",
9369
9567
  "",
9370
- "**Large effort** -- anything that warranted a written plan or spec: start it in a worktree (see the Git guide), commit as you go, finish with `bst pr:create`. Push later commits with `bst pr:update`, which rewrites nothing unless given `--title`/`--body`."
9568
+ "**Large effort** -- anything that warranted a written plan or spec (spec, then plan, then code, each approved before the next): start it in a worktree (see the Git guide), commit as you go, finish with `bst pr:create`. Push later commits with `bst pr:update`, which rewrites nothing unless given `--title`/`--body`. The one exception: a headless agent run (a spec, plan, execute, or review container) never starts a worktree -- it works and commits on the branch it was started on."
9371
9569
  ];
9372
9570
  /**
9373
9571
  * The comment rules. They name the ambient harness instruction they override ("match its
@@ -9397,25 +9595,32 @@ const TESTING_GUIDE_BODY = [
9397
9595
  "",
9398
9596
  "**A test earns its place by failing when behaviour changes.** Prove each new one red by undoing the change it covers: a test that cannot fail reads as coverage, which is worse than none.",
9399
9597
  "",
9598
+ "- Write the test before the code it covers, and watch it fail for the reason you expect. A test that passes on its first run is testing something that already works.",
9400
9599
  "- Never assert that a constant equals its own literal (a default, threshold, model name, label). Test the logic that reads it.",
9401
9600
  "- Never pin prose with `toContain`/`toBe`. Snapshot it, or assert a property: ordering, absence, output that changes with an input, a character set or length.",
9402
9601
  "- One case per branch. A new literal down a path already tested, or the same call through a one-line wrapper, is a duplicate.",
9403
9602
  "- Never assert what the test set up: a mock's stub, a type the compiler guarantees, a call count no caller relies on."
9404
9603
  ].join("\n");
9405
9604
  /**
9406
- * Worktrees and issue linking. The issue-linking rule carries one clause of *why*: the wrong
9407
- * prior is that a convert button exists, so "just use a keyword" invites a hunt for it.
9605
+ * Worktrees, issue linking, and commit subjects. The issue-linking rule carries one clause of
9606
+ * *why*: the wrong prior is that a convert button exists, so "just use a keyword" invites a hunt
9607
+ * for it. The commit-subject rule warns off copying `git log`, because an agent imitates the
9608
+ * history it reads, and nothing mechanical stops a drifted history from spreading.
9408
9609
  */
9409
9610
  const GIT_GUIDE_BODY = [
9410
9611
  "# Git",
9411
9612
  "",
9412
- "**Feature work -> a worktree, by default.** The primary checkout is shared mutable state: another session -- a second agent window, the user's own shell -- can switch branches between any two of your commands, and a commit lands on whatever branch HEAD names then. `git worktree add ../<repo>-<topic> -b feat/<topic>` -- a **sibling outside** the repo, never nested; install its dependencies first; `git worktree remove` it once its PR merges. A sibling directory is invisible in the editor's file tree and isn't writable from this session -- announce the worktree's path and open a session there (`code ../<repo>-<topic>`); without one, stay on a feature branch in the primary checkout and verify `git branch --show-current` before every commit and push.",
9613
+ "**Feature work -> a worktree, by default.** The primary checkout is shared mutable state: another session -- a second agent window, the user's own shell -- can switch branches between any two of your commands, and a commit lands on whatever branch HEAD names then. `git worktree add ../<repo>-<topic> -b feat/<topic>` -- a **sibling outside** the repo, never nested; install its dependencies first; `git worktree remove` it once its PR merges. A sibling directory is invisible in the editor's file tree and isn't writable from this session -- announce the worktree's path and open a session there (`code ../<repo>-<topic>`); without one, stay on a feature branch in the primary checkout and verify `git branch --show-current` before every commit and push. A headless agent run (a container driving the spec, plan, execute, or review phase) never takes this path: it commits only on the branch it was started on, because that is the one branch the host reads back once the container exits.",
9413
9614
  "",
9414
9615
  "## Issue linking",
9415
9616
  "",
9416
9617
  "An issue never becomes a pull request -- the two share a numbering sequence but are separate objects, and a branch named `fix/273-...` links nothing. **A closing keyword in the PR body is the only link:** `Closes #273` (or `Fixes` / `Resolves`) closes the issue on merge. `pr:create` derives the body from `git log <base>..HEAD`, so a keyword in a commit message carries through; `pr:update` does not -- pass `--body`.",
9417
9618
  "",
9418
- "It fails silently: an issue still open after its PR merged means the merger lacked permission to close it. The merge succeeds either way, so nothing reports it."
9619
+ "It fails silently: an issue still open after its PR merged means the merger lacked permission to close it. The merge succeeds either way, so nothing reports it.",
9620
+ "",
9621
+ "## Commit subjects",
9622
+ "",
9623
+ "Follow Conventional Commits: `<type>(<scope>): <summary>`. The type is one of `feat`, `fix`, `docs`, `refactor`, `test`, `chore`, `perf`, `build`, `ci`; the scope is optional and names the package or area touched; the summary is an imperative, plain-language sentence. This binds a commit message you suggest to the user too. Nothing checks it, so do not copy the style of recent `git log` if it has drifted."
9419
9624
  ].join("\n");
9420
9625
  /**
9421
9626
  * The plan lifecycle. `bst docs:check` owns the mechanics; this states the one rule it cannot
@@ -9426,7 +9631,7 @@ const PLAN_LIFECYCLE_GUIDE_BODY = [
9426
9631
  "",
9427
9632
  "A plan's **directory** is the source of truth for whether it shipped -- not its `Status:` line, not its checkboxes. Read the source it names.",
9428
9633
  "",
9429
- "`.superpowers/` (no `docs/` prefix) is scratch state, not a plan's home -- a plan doc always lives under `docs/superpowers/plans/`, even one drafted mid-`subagent-driven-development` session.",
9634
+ "`.superpowers/` (no `docs/` prefix) is scratch state, not a plan's home -- a plan doc always lives under `docs/superpowers/plans/`, even one drafted while running a plan through subagents.",
9430
9635
  "",
9431
9636
  "Retiring the plan is the last task of the plan, done in the PR that delivers the work: \"in the tree\" means committed on this branch, not merged to a default branch. Run `bst docs:retire <plan.md> --status <verb> --shipped \"<text>\"` -- it moves the doc, relinks what the move breaks, de-links paths pointing outside `docs/`, and writes the dated terminal `Status:` line, the historical banner, and the \"Shipped:\" paragraph. You supply the judgement: the honest verb -- `completed` / `superseded` / `abandoned` / `rejected` -- and a truthful `--shipped` naming the delivering PR; never fabricate it for uncommitted code or claim a merge that has not happened. A plan that lands one phase of a larger spec retires the plan and leaves the spec open.",
9432
9637
  "",
@@ -9435,6 +9640,130 @@ const PLAN_LIFECYCLE_GUIDE_BODY = [
9435
9640
  "**A retired doc is a historical record, not documentation you maintain.** Do not read one for staleness or check its claims against the tree -- that misreading has already caused real defects -- and keeping one up to date is not your job. One question decides whether an edit is allowed: would it have been right the day the doc retired? Yes means the doc misrecords its own moment and you may fix it where you already are -- an unchecked box for work that PR delivered, a `Shipped:` naming the wrong PR. No means the tree moved afterwards and the doc is merely old -- a renamed path, a superseded claim -- so leave it. The move, status line, banner, and delinking stay `bst docs:retire`/`bst docs:delink`'s; never hand-write those."
9436
9641
  ].join("\n");
9437
9642
  /**
9643
+ * How to write a spec, write a plan, and work through one: the one place this repo's own
9644
+ * spec/plan/code sequence and note vocabulary (Progress/Blocked/Declined) reach a synced repo
9645
+ * with no plugin installed.
9646
+ */
9647
+ const PLANNING_GUIDE_BODY = [
9648
+ "# Specs and plans",
9649
+ "",
9650
+ "**Spec, then plan, then code -- each approved before the next starts.** Approving a spec lets you write the plan, not the code. Approving an idea lets you write the spec, nothing more. With no one to approve (a headless run), the stage you were started on is the approval: finish it, and do not start the next.",
9651
+ "",
9652
+ "## The spec",
9653
+ "",
9654
+ "Say why and what, not how: the problem, who it is for, the constraints, what done looks like, and the alternatives you rejected, each with its reason. Work with several independent parts gets one spec per part. Save it as `docs/superpowers/specs/<YYYY-MM-DD>-<topic>-design.md`.",
9655
+ "",
9656
+ "## The plan",
9657
+ "",
9658
+ "Write for an executor who knows nothing about this repo and has only the plan and the spec. Save it under `docs/superpowers/plans/`.",
9659
+ "",
9660
+ "- Check the plan's premises against the tree before building on them. \"Today X is broken\" is a claim: read the code that handles X, not just the line that throws.",
9661
+ "- Label each task `Task <n>:` at the start of its heading or checkbox, numbered from 1, so `bst plan:brief` can find it.",
9662
+ "- Name exact file paths, the interfaces each task uses and provides, and every command along with the output it should produce.",
9663
+ "- 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.",
9664
+ "- 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.",
9665
+ "- State a judgement that recurs across many places once, as a rule in the plan, so every task decides it the same way.",
9666
+ "- 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.",
9667
+ "- 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.",
9668
+ "",
9669
+ "## Working through a plan",
9670
+ "",
9671
+ "Do the tasks in order, commit after each, and tick a box only once its result is verified. A box you cannot finish keeps its text exactly as written and gets one note underneath:",
9672
+ "",
9673
+ "- `**Progress:**` -- partly done: what landed, what remains, where to pick up. The next pass resumes it.",
9674
+ "- `**Blocked:**` -- tried and failing: the error. The next pass retries it.",
9675
+ "- `**Declined:**` -- must not be done, with the reason: a standing rule, a repo convention, or a check that found it moot. No pass touches it again. \"Too hard\" or \"out of budget\" is Progress, not Declined.",
9676
+ "",
9677
+ "To run a plan through subagents, see the subagents guide."
9678
+ ].join("\n");
9679
+ /**
9680
+ * How to brief, parallelize, and run a plan through subagents: the failure this distils is a
9681
+ * subagent that inherits nothing and guesses, or a session that skips review by fixing findings
9682
+ * itself.
9683
+ */
9684
+ const SUBAGENTS_GUIDE_BODY = [
9685
+ "# Subagents",
9686
+ "",
9687
+ "**A subagent knows only what you hand it.** Write the brief as its whole world: never your session's history, and never \"see above\".",
9688
+ "",
9689
+ "## Briefing one",
9690
+ "",
9691
+ "- One task per subagent: the goal, the exact files in scope, what it must not touch, and what to return.",
9692
+ "- Hand large inputs over as files and ask for a short reply -- status, commits, a one-line test result, concerns -- with the full report written to a file. Everything pasted into or returned from a subagent stays in your context for the rest of the session.",
9693
+ "- Choose the model per task and name it explicitly; an unnamed one inherits yours. Use the cheapest model that finishes in few turns: plan text that already holds the code is cheap-tier work, integration and debugging need a standard model, design and the final review need the strongest. Turn count costs more than token price.",
9694
+ "- A subagent does not start subagents of its own, reviewers included. Review comes from you.",
9695
+ "- Its \"done\" is a claim, not evidence -- see the verification guide.",
9696
+ "",
9697
+ "## In parallel",
9698
+ "",
9699
+ "Run subagents at the same time only for problems that share no files and no state, such as unrelated failures in separate subsystems. Failures that might share a cause get investigated together first. Never run two implementers on one working tree. When they return, check their changes for conflicts and run the full suite.",
9700
+ "",
9701
+ "## Running a plan through subagents",
9702
+ "",
9703
+ "- The plan's checkboxes are the progress record. Tick a task's box and commit once its review is clean. After a context reset, `bst plan:status <plan>` names the next open task; trust it and `git log` over your memory, and never re-run a ticked task.",
9704
+ "- `bst plan:workspace <plan>` prints the plan's own git-ignored scratch directory for briefs, reports, review packages, and fix-round notes. Nothing that must outlive the run goes there.",
9705
+ "- Before the first task, check the plan for tasks that contradict each other or the spec. Rule on each (the spec wins) and record the ruling.",
9706
+ "- Per task: note the base commit, run `bst plan:brief <plan> <n>`, and give one implementer the brief file it writes plus a report file to write back. Then run `bst plan:review-package <plan> <base> <head>` and have a separate reviewer judge that file against the brief, for both spec compliance and quality. Never tell a reviewer what not to flag.",
9707
+ "- Findings go back to the same implementer, followed by a review of the fix diff only. Cap it at five rounds, switching to a stronger model from round four. At the cap, rule on each open finding and record why. Never fix in your own session: that skips review. A minor finding nobody fixes in this run goes to the findings ledger, not a scratch note.",
9708
+ "- Do not stop between tasks to ask. Stop only for something irreversible or destructive, a security-sensitive action, a side effect outside the working tree (a push, a merge, a publish), or a plan so broken that every way forward is a guess. Anything else, decide, and record the ruling with what it costs if wrong.",
9709
+ "- Keep every ruling where the human will read it: list them all, each with what it costs if wrong, in your final message and in the pull request body.",
9710
+ "- Finish with a review of the whole branch on the strongest model and one fixer for all its findings. Then delete the plan's scratch directory."
9711
+ ].join("\n");
9712
+ /**
9713
+ * Root-cause debugging discipline: the guide exists because blaming the environment for a
9714
+ * never-run code path's first failure, and stacking a second fix on an unproven first one, are
9715
+ * both defects this repo has shipped.
9716
+ */
9717
+ const DEBUGGING_GUIDE_BODY = [
9718
+ "# Debugging",
9719
+ "",
9720
+ "**Find the root cause before you change anything.** A fix you cannot explain is a guess, and a guess that goes green hides the cause instead of removing it.",
9721
+ "",
9722
+ "- Read the whole error and stack trace before acting -- it usually names the file, line, and cause. Reproduce the failure before you form a theory, and keep the reproduction: it is how you prove the fix.",
9723
+ "- Trace a bad value back to where it starts and fix it there, not where it surfaces.",
9724
+ "- One hypothesis, one change. State it (\"X fails because Y\"), make the smallest change that would disprove it, and undo that change if it does not hold. Never stack a second fix on an unproven first one.",
9725
+ "- Suspect the newest code first. When a code path that has never run before fails, the new code is the likeliest cause. Blaming the environment (network, credentials, a flaky host) needs evidence that shows it, such as a log line or a second machine.",
9726
+ "- Three failed fixes mean the approach is wrong. Stop, question the design, and say what you do not understand, instead of trying a fourth.",
9727
+ "- A fixed bug gets a regression test that you have seen fail against the unfixed code."
9728
+ ].join("\n");
9729
+ /**
9730
+ * What counts as evidence before a claim of done, fixed, or passing: the guide exists because a
9731
+ * mock-backed test proved nothing about a subprocess fix, and a subagent's own "success" report
9732
+ * has been taken at face value before.
9733
+ */
9734
+ const VERIFICATION_GUIDE_BODY = [
9735
+ "# Verification",
9736
+ "",
9737
+ "**No claim without fresh evidence.** \"Done\", \"fixed\", \"passing\", and \"works\" each need a command you ran after your last edit, whose output you read and which says so. If you have not run it since the last change, you do not know.",
9738
+ "",
9739
+ "- Name the command that proves the claim, run it in full, and read the output and exit code -- not a summary line, not an earlier run, not a narrower check. A lint pass says nothing about types; a type check says nothing about behaviour.",
9740
+ "- Prove it at the level of the claim. A mocked unit test cannot prove a fix to a subprocess, a binary, a container, or a network call: drive the real thing and capture the output before and after.",
9741
+ "- A subagent's or tool's \"success\" is a claim, not evidence. Read the diff it produced and re-run the check yourself.",
9742
+ "- \"Complete\" means every requirement, not a green suite. Re-read the plan or issue item by item and check each against the tree; report what is missing rather than rounding up.",
9743
+ "- Report what you saw. A failure is stated with its output; a step you skipped is named as skipped. \"Should work\" and \"probably passes\" mean you have not verified."
9744
+ ].join("\n");
9745
+ /**
9746
+ * Acting on review feedback and requesting a review: the guide exists because a review finding's
9747
+ * claim about an external system was applied untested and had to be reverted.
9748
+ */
9749
+ const CODE_REVIEW_GUIDE_BODY = [
9750
+ "# Code review",
9751
+ "",
9752
+ "## Acting on feedback",
9753
+ "",
9754
+ "**A finding is a hypothesis until you have checked it against the tree.** Reviewers, human or model, are often right about the symptom and wrong about the cause or the fix -- especially in a claim about how an external system behaves.",
9755
+ "",
9756
+ "- Read every item before changing anything. If any item is unclear, ask about it first: items are often related, and half-understood feedback becomes a wrong change. Where no one can answer, record the item as unresolved -- a `**Blocked:**` note or a recorded ruling -- rather than guessing.",
9757
+ "- For each item, confirm the claim yourself -- read the code it names, run the case it describes, test the behaviour it asserts -- before you apply its prescription. A correct diagnosis can still carry a wrong fix.",
9758
+ "- Push back with evidence when a finding is wrong: the test, output, or code that shows it. When a suggestion adds something nothing uses, say so rather than building it.",
9759
+ "- Apply one item at a time and check each. Reply with what changed, not with agreement or thanks.",
9760
+ "- A valid finding you are not fixing in this change goes where the findings guide says, not into silence.",
9761
+ "",
9762
+ "## Requesting a review",
9763
+ "",
9764
+ "Give the reviewer the requirement (the plan, issue, or spec) and the exact commit range, not your session's history -- it should judge the work, not your reasoning about it. Review before merge, and after any change large enough that you would not spot your own mistake in it."
9765
+ ].join("\n");
9766
+ /**
9438
9767
  * The findings-ledger protocol, shipped at every stack: it names no stack-specific
9439
9768
  * tool, so both the interactive Claude Code side of a synced repo and any human beside it
9440
9769
  * learn the same thing the agent's container prompts already carry. Short on purpose -- the
@@ -9604,12 +9933,42 @@ function agentGuides(config, coverageEnabled, healthEnabled) {
9604
9933
  body: GIT_GUIDE_BODY,
9605
9934
  enabled: true
9606
9935
  },
9936
+ {
9937
+ name: "planning.md",
9938
+ trigger: "write a spec or plan, or work through a plan's tasks",
9939
+ body: PLANNING_GUIDE_BODY,
9940
+ enabled: true
9941
+ },
9942
+ {
9943
+ name: "subagents.md",
9944
+ trigger: "hand work to a subagent, or run a plan through subagents",
9945
+ body: SUBAGENTS_GUIDE_BODY,
9946
+ enabled: true
9947
+ },
9607
9948
  {
9608
9949
  name: "plan-lifecycle.md",
9609
- trigger: "write, read, or retire a plan or spec",
9950
+ trigger: "read or retire a plan or spec",
9610
9951
  body: PLAN_LIFECYCLE_GUIDE_BODY,
9611
9952
  enabled: true
9612
9953
  },
9954
+ {
9955
+ name: "debugging.md",
9956
+ trigger: "fix a bug, a failing test, or unexpected behaviour",
9957
+ body: DEBUGGING_GUIDE_BODY,
9958
+ enabled: true
9959
+ },
9960
+ {
9961
+ name: "verification.md",
9962
+ trigger: "claim work is done, fixed, or passing",
9963
+ body: VERIFICATION_GUIDE_BODY,
9964
+ enabled: true
9965
+ },
9966
+ {
9967
+ name: "code-review.md",
9968
+ trigger: "request a review, or act on review feedback",
9969
+ body: CODE_REVIEW_GUIDE_BODY,
9970
+ enabled: true
9971
+ },
9613
9972
  {
9614
9973
  name: "findings.md",
9615
9974
  trigger: "note a defect you are not fixing, or work a finding",
@@ -9640,7 +9999,7 @@ function guideIndex(guides) {
9640
9999
  const lines = [
9641
10000
  "#### Guides",
9642
10001
  "",
9643
- "Each guide below is a file `bst repo:sync` keeps current. Read it at the moment it names, before you act.",
10002
+ "Each guide below is a file `bst repo:sync` keeps current. Read it at the moment it names, before you act. Where an installed plugin or skill covers the same ground as a guide, the guide wins.",
9644
10003
  ""
9645
10004
  ];
9646
10005
  for (const guide of guides.filter((g) => g.enabled)) lines.push(`- Before you ${guide.trigger}: \`${GUIDES_DIR}/${guide.name}\`.`);
@@ -9651,6 +10010,7 @@ function agentRulesBody(config, guides) {
9651
10010
  if (config.stack === "typescript") lines.push(TS_TYPECAST_RULE);
9652
10011
  lines.push(...AGENT_RULES_MID);
9653
10012
  if (config.stack === "typescript") lines.push(TS_EXTENSIONLESS_RULE);
10013
+ if (config.releaseStrategy === "changesets") lines.push(CHANGESETS_RULE);
9654
10014
  lines.push(...AGENT_RULES_TAIL, "", guideIndex(guides));
9655
10015
  return lines.join("\n");
9656
10016
  }
@@ -9873,6 +10233,7 @@ async function generateVersionrc(ctx) {
9873
10233
  async function runGenerators(ctx) {
9874
10234
  const results = [];
9875
10235
  results.push(await generatePackageJson(ctx));
10236
+ results.push(await generateMise(ctx));
9876
10237
  results.push(...await generateWorkspaceEngines(ctx));
9877
10238
  results.push(await generatePnpmWorkspace(ctx));
9878
10239
  results.push(...await generateTsconfig(ctx));
@@ -9923,7 +10284,7 @@ function reportFailures(failed, command) {
9923
10284
  function generateMigratePrompt(results, config, detected) {
9924
10285
  const sections = [];
9925
10286
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
9926
- sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.77.0 repo:sync\` on ${timestamp}_\n`);
10287
+ sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.78.0 repo:sync\` on ${timestamp}_\n`);
9927
10288
  const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
9928
10289
  if (changed.length > 0) {
9929
10290
  const items = changed.map((r) => {
@@ -10875,7 +11236,12 @@ function createFileReader() {
10875
11236
  * verdict), last occurrence wins (a step's own trailing marker beats anything the code
10876
11237
  * under check managed to print earlier).
10877
11238
  *
10878
- * One verb, `warn`: a second verb earns its place one documented need at a time.
11239
+ * One verb, `warn`: a second verb earns its place one documented need at a time. The count is
11240
+ * every advisory finding `repo:sync --check` folds into this one marker rather than a second
11241
+ * emitter: workflow-dir-only drift (one entry per actionable file) plus, when `mise.toml`
11242
+ * declares a toolchain but `mise` is not on PATH, one more from `runToolchainGate`
11243
+ * (`commands/sync-gates.ts`) -- see that gate's doc comment for why an absent mise warns rather
11244
+ * than fails.
10879
11245
  */
10880
11246
  const CHECKS_MARKER = "::bst-checks::";
10881
11247
  const WARN_LINE = new RegExp(`^${CHECKS_MARKER} warn (\\d+)\\s*$`);
@@ -11544,6 +11910,189 @@ function warnCoveredChecksAdd(config, targetDir) {
11544
11910
  const covered = coveredChecksAddEntries(config, readPackageJson(targetDir)?.scripts ?? {});
11545
11911
  for (const entry of covered) log$1.warn(`.tooling.json#checks.add entry "${entry.name}" is already run by the "${entry.coveredBy}" step's script ("${entry.fragment}"), so every pipeline run executes it twice. Remove the add entry, or drop the inline invocation from the "${entry.coveredBy}" script.`);
11546
11912
  }
11913
+ /** The tolerant shape read off `mise.toml#tools` -- only the plain string pins matter to this
11914
+ * gate; a table-form entry (e.g. `java = { version = "..." }`) is skipped rather than rejected --
11915
+ * `generators/mise.ts` is what owns validating the file's shape, not this gate. */
11916
+ const MiseToolsSchema = z.record(z.string(), z.union([z.string(), z.unknown()]));
11917
+ /** Read `mise.toml`'s `[tools]` table as plain-string pins only (table-form entries, e.g. an
11918
+ * `http:*` archive tool, are skipped -- version/install-state comparison is meaningless for
11919
+ * those). `undefined` when the file is absent or fails to parse; the caller treats that the same
11920
+ * as "nothing declared" rather than failing the gate on a shape `generators/mise.ts` already
11921
+ * guards at sync time. */
11922
+ function readDeclaredTools(miseTomlPath) {
11923
+ let raw;
11924
+ try {
11925
+ raw = readFileSync(miseTomlPath, "utf-8");
11926
+ } catch {
11927
+ return;
11928
+ }
11929
+ let parsed;
11930
+ try {
11931
+ parsed = parse$2(raw);
11932
+ } catch {
11933
+ return;
11934
+ }
11935
+ const document = z.looseObject({ tools: z.unknown().optional() }).safeParse(parsed);
11936
+ if (!document.success) return void 0;
11937
+ const tools = MiseToolsSchema.safeParse(document.data.tools ?? {});
11938
+ if (!tools.success) return void 0;
11939
+ const declared = {};
11940
+ for (const [key, value] of Object.entries(tools.data)) if (typeof value === "string") declared[key] = value;
11941
+ return declared;
11942
+ }
11943
+ /** One `mise ls --current --json` entry, the fields this gate reads (`ls.rs`'s
11944
+ * `JSONToolVersion`, verified against jdx/mise `v2026.9.12` source at execute time). */
11945
+ const MiseListEntrySchema = z.looseObject({
11946
+ version: z.string(),
11947
+ install_path: z.string(),
11948
+ installed: z.boolean()
11949
+ });
11950
+ const MiseListSchema = z.record(z.string(), z.array(MiseListEntrySchema));
11951
+ /** `true` when `mise --version` fails to start (mise is not on PATH). A non-ENOENT spawn error
11952
+ * (permissions, etc.) is treated as "present but broken" -- a hard failure, not this soft path --
11953
+ * so it falls through to the JSON-parsing branch below, which reports it plainly. */
11954
+ function miseIsAbsent(targetDir) {
11955
+ const error = spawn$1.sync("mise", ["--version"], { cwd: targetDir }).error;
11956
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
11957
+ }
11958
+ const MISE_INSTALL_URL = "https://mise.jdx.dev/getting-started.html";
11959
+ /** Strip a `v` prefix and trailing whitespace off a spawned tool's own `--version` output. */
11960
+ function normalizeVersionOutput(stdout) {
11961
+ return stdout.trim().replace(/^v/, "");
11962
+ }
11963
+ const ACTIVATE_HINT = "(`eval \"$(mise activate bash)\"`, `mise activate bash --shims`, or `mise exec`)";
11964
+ /** What a `--version` probe that produced no version says about why: the spawn error, or the
11965
+ * exit status and the first line of its own stderr (pnpm refusing a broken package.json, say). */
11966
+ function describeFailedProbe(probe) {
11967
+ if (probe.error) return probe.error.message;
11968
+ const firstLine = (probe.stderr?.toString() ?? "").trim().split("\n")[0] ?? "";
11969
+ return `exited ${String(probe.status)}${firstLine ? `: ${firstLine}` : ""}`;
11970
+ }
11971
+ /** One `log.error` and `true` when the `tool` on PATH is not the version mise resolved. Compared
11972
+ * against mise's resolved `version`, not the raw pin: a pin can be a range (`"24"`, `"lts"`) that
11973
+ * no live `--version` output will ever equal, even once activated correctly. */
11974
+ function liveVersionMismatch(targetDir, tool, pin, resolved) {
11975
+ const probe = spawn$1.sync(tool, ["--version"], { cwd: targetDir });
11976
+ const live = probe.status === 0 && probe.stdout ? normalizeVersionOutput(probe.stdout.toString()) : void 0;
11977
+ if (live === resolved) return false;
11978
+ const seen = live === void 0 ? `failed (${describeFailedProbe(probe)})` : `reports ${live}`;
11979
+ log$1.error(`mise.toml pins ${tool} ${pin ?? resolved} (resolved to ${resolved}), but \`${tool} --version\` on PATH ${seen}. Activate mise ${ACTIVATE_HINT} before running this check.`);
11980
+ return true;
11981
+ }
11982
+ /** `true` when `child` is `parent` or lies beneath it -- a path comparison, not a string prefix,
11983
+ * so `/data/mise` does not claim `/data/mise-other`. */
11984
+ function isInside(child, parent) {
11985
+ const relative = path.relative(parent, child);
11986
+ return relative === "" || !relative.startsWith("..") && !path.isAbsolute(relative);
11987
+ }
11988
+ /** The first executable named `command` on `process.env.PATH`, resolved the way a shell would,
11989
+ * without spawning `which` (whose presence and output format vary by platform). */
11990
+ function resolveOnPath(command) {
11991
+ for (const dir of (process.env["PATH"] ?? "").split(path.delimiter)) {
11992
+ if (!dir) continue;
11993
+ const candidate = path.join(dir, command);
11994
+ try {
11995
+ if (statSync(candidate).isFile()) {
11996
+ accessSync(candidate, constants.X_OK);
11997
+ return candidate;
11998
+ }
11999
+ } catch (_error) {}
12000
+ }
12001
+ }
12002
+ /**
12003
+ * Java is judged by what a build will actually run. With `JAVA_HOME` set, Gradle and Maven use it,
12004
+ * so it must point into mise's install. With it unset -- the shims-only setup an editor or AI
12005
+ * session gets, since shims set tool environment only for the tool they start -- they fall back
12006
+ * to `java` on PATH, which must then resolve into mise's data directory (a shim or an install).
12007
+ */
12008
+ function javaMismatch(entry) {
12009
+ const dataDir = path.dirname(path.dirname(path.dirname(entry.install_path)));
12010
+ const javaHome = process.env["JAVA_HOME"];
12011
+ if (javaHome) {
12012
+ if (isInside(javaHome, dataDir)) return false;
12013
+ log$1.error(`mise.toml declares java, but JAVA_HOME points outside mise's install (${javaHome}). Unset it, or activate mise ${ACTIVATE_HINT} before running this check.`);
12014
+ return true;
12015
+ }
12016
+ const onPath = resolveOnPath("java");
12017
+ if (onPath !== void 0 && isInside(onPath, dataDir)) return false;
12018
+ log$1.error(`mise.toml declares java, but \`java\` on PATH is ${onPath ?? "missing"}, not mise's. Activate mise ${ACTIVATE_HINT} before running this check.`);
12019
+ return true;
12020
+ }
12021
+ /** Compare `mise ls --current --json`'s installed state and the live `node`/`pnpm`/`java` on PATH
12022
+ * against `mise.toml`'s declared pins, logging one `log.error` per mismatch. Returns the offender
12023
+ * count. */
12024
+ function checkDeclaredToolsActive(targetDir, declared) {
12025
+ const listResult = spawn$1.sync("mise", [
12026
+ "ls",
12027
+ "--current",
12028
+ "--json"
12029
+ ], { cwd: targetDir });
12030
+ if (listResult.status !== 0 && (listResult.stderr?.toString() ?? "").includes("are not trusted")) {
12031
+ log$1.error("mise refuses this repo's mise.toml until it is trusted. Run `mise trust`, or add the directory your checkouts live in to `trusted_config_paths` in ~/.config/mise/config.toml so every new worktree is trusted.");
12032
+ return 1;
12033
+ }
12034
+ let byTool = {};
12035
+ try {
12036
+ const listJson = MiseListSchema.safeParse(JSON.parse(listResult.stdout?.toString() ?? ""));
12037
+ if (listJson.success) byTool = listJson.data;
12038
+ } catch (_error) {}
12039
+ const active = {};
12040
+ let failures = 0;
12041
+ for (const tool of Object.keys(declared)) {
12042
+ const entry = (byTool[tool] ?? []).find((candidate) => candidate.installed);
12043
+ if (!entry) {
12044
+ log$1.error(`mise.toml declares ${tool}, but it is not installed. Run \`mise install\`.`);
12045
+ failures++;
12046
+ continue;
12047
+ }
12048
+ active[tool] = entry;
12049
+ }
12050
+ for (const tool of ["node", "pnpm"]) {
12051
+ const entry = active[tool];
12052
+ if (entry && liveVersionMismatch(targetDir, tool, declared[tool], entry.version)) failures++;
12053
+ }
12054
+ const javaEntry = active["java"];
12055
+ if (javaEntry && javaMismatch(javaEntry)) failures++;
12056
+ return failures;
12057
+ }
12058
+ /**
12059
+ * Assert that a repo whose `mise.toml` declares a toolchain actually has it installed and active
12060
+ * -- a no-op (both fields zero) when there is no `mise.toml` at the target root.
12061
+ *
12062
+ * mise absent from PATH is a warning, not a failure: the promotion to a hard failure is a later
12063
+ * one-line change (flip the `miseIsAbsent` branch below to increment `failures` instead of
12064
+ * returning `warned: true`). It waits on three conditions (2026-09-23 mise local-dev spec,
12065
+ * section 6): generated CI installs mise in every fleet repo, the agent's runner image carries
12066
+ * mise, and this warning names the developer setup. Once they hold, absence can only mean a
12067
+ * developer machine without mise, and failing there is the point. Every other mismatch -- an
12068
+ * untrusted config, a declared tool that `mise ls --current --json` reports as not installed, a
12069
+ * live `node`/`pnpm --version` that disagrees with the pin, a java that is not mise's -- is a hard
12070
+ * failure today: those are deterministic once mise itself is known to be present, so there is no
12071
+ * warm-up period for them to wait out.
12072
+ */
12073
+ function runToolchainGate(targetDir) {
12074
+ const miseTomlPath = path.join(targetDir, "mise.toml");
12075
+ if (!existsSync(miseTomlPath)) return {
12076
+ failures: 0,
12077
+ warned: false
12078
+ };
12079
+ if (miseIsAbsent(targetDir)) {
12080
+ log$1.warn(`mise.toml declares this repo's toolchain, but mise is not on PATH. Install it (${MISE_INSTALL_URL}), then add \`eval "$(mise activate bash)"\` to ~/.bashrc for interactive shells and \`eval "$(mise activate bash --shims)"\` to ~/.profile, so editors, git hooks, and AI sessions get the same tools.`);
12081
+ return {
12082
+ failures: 0,
12083
+ warned: true
12084
+ };
12085
+ }
12086
+ const declared = readDeclaredTools(miseTomlPath);
12087
+ if (!declared) return {
12088
+ failures: 0,
12089
+ warned: false
12090
+ };
12091
+ return {
12092
+ failures: checkDeclaredToolsActive(targetDir, declared),
12093
+ warned: false
12094
+ };
12095
+ }
11547
12096
  //#endregion
11548
12097
  //#region src/utils/workflow-paths.ts
11549
12098
  /**
@@ -11666,6 +12215,8 @@ async function runCheck(targetDir) {
11666
12215
  if (runReleaseTargetGate(config, targetDir)) return 1;
11667
12216
  if (runVersionStampGate(saved, targetDir, config.releaseStrategy) > 0) return 1;
11668
12217
  if (runPublishConfigGate(config, targetDir) > 0) return 1;
12218
+ const toolchain = runToolchainGate(targetDir);
12219
+ if (toolchain.failures > 0) return 1;
11669
12220
  const { ctx, pendingWrites } = createDryRunContext(config);
11670
12221
  logDetectionSummary(ctx);
11671
12222
  const results = await runGenerators(ctx);
@@ -11682,7 +12233,12 @@ async function runCheck(targetDir) {
11682
12233
  }
11683
12234
  return true;
11684
12235
  });
12236
+ const toolchainWarnCount = toolchain.warned ? 1 : 0;
11685
12237
  if (actionable.length === 0 && failed.length === 0) {
12238
+ if (toolchainWarnCount > 0) {
12239
+ rawLog$1(formatWarnMarker(toolchainWarnCount));
12240
+ return 0;
12241
+ }
11686
12242
  log$1.success("Repository is up to date.");
11687
12243
  return 0;
11688
12244
  }
@@ -11690,7 +12246,7 @@ async function runCheck(targetDir) {
11690
12246
  log$1.warn(`${actionable.length} file(s) would be changed by repo:sync`);
11691
12247
  for (const r of actionable) logActionableChange(r, targetDir, pendingWrites);
11692
12248
  if (failed.length === 0 && actionable.every((r) => isWorkflowDirPath(r.filePath))) {
11693
- rawLog$1(formatWarnMarker(actionable.length));
12249
+ rawLog$1(formatWarnMarker(actionable.length + toolchainWarnCount));
11694
12250
  return 0;
11695
12251
  }
11696
12252
  return 1;
@@ -12243,6 +12799,9 @@ function createRealExecutor$1(logger = rootLogger) {
12243
12799
  return [];
12244
12800
  }
12245
12801
  },
12802
+ makeDir(dirPath) {
12803
+ mkdirSync(dirPath, { recursive: true });
12804
+ },
12246
12805
  removeDir(dirPath) {
12247
12806
  rmSync(dirPath, {
12248
12807
  recursive: true,
@@ -26468,7 +27027,7 @@ const PRUNED_DIRS = /* @__PURE__ */ new Set([
26468
27027
  ".tooling-archived",
26469
27028
  ".git"
26470
27029
  ]);
26471
- function runGit(args, cwd) {
27030
+ function runGit$1(args, cwd) {
26472
27031
  try {
26473
27032
  return execFileSync("git", args, {
26474
27033
  cwd,
@@ -26483,10 +27042,10 @@ function runGit(args, cwd) {
26483
27042
  }
26484
27043
  }
26485
27044
  function isInsideGitRepo(cwd) {
26486
- return runGit(["rev-parse", "--is-inside-work-tree"], cwd)?.trim() === "true";
27045
+ return runGit$1(["rev-parse", "--is-inside-work-tree"], cwd)?.trim() === "true";
26487
27046
  }
26488
27047
  function isTracked(rel, cwd) {
26489
- return runGit([
27048
+ return runGit$1([
26490
27049
  "ls-files",
26491
27050
  "--error-unmatch",
26492
27051
  rel
@@ -26529,7 +27088,7 @@ function createRealRelocateExecutor(rootDir) {
26529
27088
  },
26530
27089
  listMarkdownFiles() {
26531
27090
  if (isInsideGitRepo(rootDir)) {
26532
- const stdout = runGit(["ls-files", "*.md"], rootDir);
27091
+ const stdout = runGit$1(["ls-files", "*.md"], rootDir);
26533
27092
  if (stdout !== void 0) return stdout.split("\n").map((line) => line.trim()).filter((line) => line !== "").toSorted();
26534
27093
  }
26535
27094
  const out = [];
@@ -28832,6 +29391,588 @@ const findingsResolveCommand = defineCommand({
28832
29391
  }
28833
29392
  });
28834
29393
  //#endregion
29394
+ //#region src/plan-workspace/git.ts
29395
+ /**
29396
+ * Raw, untrimmed stdout and an explicit exit status. `utils/branch.ts`'s runner trims and turns
29397
+ * empty output into failure, which misreads a passing `merge-base --is-ancestor`, and its default
29398
+ * buffer truncates a large diff.
29399
+ */
29400
+ function runGit(args, cwd) {
29401
+ try {
29402
+ return {
29403
+ ok: true,
29404
+ stdout: execFileSync("git", args, {
29405
+ cwd,
29406
+ encoding: "utf-8",
29407
+ stdio: [
29408
+ "ignore",
29409
+ "pipe",
29410
+ "ignore"
29411
+ ],
29412
+ maxBuffer: 536870912
29413
+ })
29414
+ };
29415
+ } catch {
29416
+ return {
29417
+ ok: false,
29418
+ stdout: ""
29419
+ };
29420
+ }
29421
+ }
29422
+ //#endregion
29423
+ //#region src/plan-workspace/workspace.ts
29424
+ const WORKSPACE_ROOT = ".superpowers/sdd";
29425
+ const MARKER = "plan-path";
29426
+ /**
29427
+ * The plan's id as it read before retirement moved it: its last `retired/` directory segment
29428
+ * removed, or null when it has none.
29429
+ */
29430
+ function unretiredId(planId) {
29431
+ const segments = planId.split("/");
29432
+ const index = segments.length < 2 ? -1 : segments.lastIndexOf("retired", segments.length - 2);
29433
+ if (index < 0) return null;
29434
+ return [...segments.slice(0, index), ...segments.slice(index + 1)].join("/");
29435
+ }
29436
+ /**
29437
+ * True when `dir` is, or now becomes, `planId`'s. A directory with no marker is claimed. A marker
29438
+ * naming the plan's pre-retirement path also counts: retiring the plan is its last task, and the
29439
+ * cleanup after it must find the directory the run used, not mint a new one.
29440
+ */
29441
+ function claims(dir, planId) {
29442
+ const marker = path.join(dir, MARKER);
29443
+ if (existsSync(marker)) {
29444
+ const owner = readFileSync(marker, "utf-8").replace(/\n$/, "");
29445
+ return owner === planId || owner === unretiredId(planId);
29446
+ }
29447
+ mkdirSync(dir, { recursive: true });
29448
+ writeFileSync(marker, `${planId}\n`);
29449
+ return true;
29450
+ }
29451
+ function* candidateNames(slug, parent) {
29452
+ yield slug;
29453
+ yield `${slug}-${parent}`;
29454
+ for (let n = 2;; n++) yield `${slug}-${parent}-${String(n)}`;
29455
+ }
29456
+ /**
29457
+ * The plan's own scratch directory. Keyed on a marker naming the plan, so two plans sharing a
29458
+ * basename never read each other's briefs or review packages. Same root the superpowers plugin
29459
+ * uses, so a session with the plugin and one without share it.
29460
+ */
29461
+ function resolvePlanWorkspace(planPath, cwd) {
29462
+ const planAbs = path.resolve(cwd, planPath);
29463
+ if (!existsSync(planAbs)) throw new FatalError(`no such plan file: ${planPath}`);
29464
+ const top = runGit(["rev-parse", "--show-toplevel"], cwd);
29465
+ if (!top.ok) throw new FatalError(`not inside a git repository: ${cwd}`);
29466
+ const root = realpathSync(top.stdout.trim());
29467
+ const plan = realpathSync(planAbs);
29468
+ const slug = path.basename(plan, ".md");
29469
+ if (slug === "" || slug === "." || slug === "..") throw new FatalError(`cannot derive a workspace name from: ${planPath}`);
29470
+ const planId = plan.startsWith(`${root}${path.sep}`) ? path.relative(root, plan).split(path.sep).join("/") : plan;
29471
+ const base = path.join(root, ...WORKSPACE_ROOT.split("/"));
29472
+ for (const name of candidateNames(slug, path.basename(path.dirname(plan)))) {
29473
+ const dir = path.join(base, name);
29474
+ if (claims(dir, planId)) {
29475
+ writeFileSync(path.join(base, ".gitignore"), "*\n");
29476
+ return dir;
29477
+ }
29478
+ }
29479
+ return base;
29480
+ }
29481
+ /**
29482
+ * An `--out` path made absolute against `cwd`. Its parent must already exist: a mistyped
29483
+ * directory is refused rather than created.
29484
+ */
29485
+ function resolveOutPath(out, cwd) {
29486
+ const target = path.resolve(cwd, out);
29487
+ const parent = path.dirname(target);
29488
+ if (!existsSync(parent)) throw new FatalError(`--out directory does not exist: ${parent}`);
29489
+ return target;
29490
+ }
29491
+ //#endregion
29492
+ //#region src/plan-workspace/review-package.ts
29493
+ function resolveCommit(ref, label, cwd) {
29494
+ const result = runGit([
29495
+ "rev-parse",
29496
+ "--verify",
29497
+ "--quiet",
29498
+ `${ref}^{commit}`
29499
+ ], cwd);
29500
+ if (!result.ok) throw new FatalError(`bad ${label}: ${ref}`);
29501
+ return result.stdout.trim();
29502
+ }
29503
+ function gitOut(args, cwd) {
29504
+ const result = runGit(args, cwd);
29505
+ if (!result.ok) throw new FatalError(`git ${args[0] ?? ""} failed in ${cwd}`);
29506
+ return result.stdout;
29507
+ }
29508
+ /**
29509
+ * The redirect for a base branch that moved on after head forked from it. It names a separate
29510
+ * command rather than a `$(...)` substitution, because the bst-hook's injection guard prompts on
29511
+ * one.
29512
+ */
29513
+ function forkRedirect(base, head, baseSha, headSha, cwd) {
29514
+ const shared = runGit([
29515
+ "merge-base",
29516
+ baseSha,
29517
+ headSha
29518
+ ], cwd);
29519
+ if (!shared.ok) return "";
29520
+ return `; they share merge-base ${shared.stdout.trim()} -- if ${base} is a branch that moved on, run \`git merge-base ${base} ${head}\` and pass that SHA as <base>`;
29521
+ }
29522
+ /**
29523
+ * Commit list, stat summary, and the net diff with ten lines of context, in one file a reviewer
29524
+ * reads in one call. Range guards refuse an empty range and a head not rooted at base: either
29525
+ * would review the wrong change, which is what `HEAD~1` does to a multi-commit task.
29526
+ */
29527
+ function writeReviewPackage(planPath, base, head, cwd, out) {
29528
+ const outPath = out === void 0 ? void 0 : resolveOutPath(out, cwd);
29529
+ const baseSha = resolveCommit(base, "base", cwd);
29530
+ const headSha = resolveCommit(head, "head", cwd);
29531
+ if (!runGit([
29532
+ "merge-base",
29533
+ "--is-ancestor",
29534
+ baseSha,
29535
+ headSha
29536
+ ], cwd).ok) {
29537
+ const redirect = forkRedirect(base, head, baseSha, headSha, cwd);
29538
+ throw new FatalError(`head is not a descendant of base: ${base}..${head}${redirect}`);
29539
+ }
29540
+ const range = `${baseSha}..${headSha}`;
29541
+ const commits = Number(gitOut([
29542
+ "rev-list",
29543
+ "--count",
29544
+ range
29545
+ ], cwd).trim());
29546
+ if (commits === 0) throw new FatalError(`empty commit range: ${base}..${head}`);
29547
+ const body = [
29548
+ `# Review package: ${base}..${head}`,
29549
+ "",
29550
+ "## Commits",
29551
+ gitOut([
29552
+ "log",
29553
+ "--oneline",
29554
+ range
29555
+ ], cwd),
29556
+ "## Files changed",
29557
+ gitOut([
29558
+ "diff",
29559
+ "--stat",
29560
+ range
29561
+ ], cwd),
29562
+ "## Diff",
29563
+ gitOut([
29564
+ "diff",
29565
+ "-U10",
29566
+ range
29567
+ ], cwd)
29568
+ ].join("\n");
29569
+ const name = `review-${baseSha.slice(0, 7)}..${headSha.slice(0, 7)}.diff`;
29570
+ const target = outPath ?? path.join(resolvePlanWorkspace(planPath, cwd), name);
29571
+ writeFileSync(target, body);
29572
+ return {
29573
+ path: target,
29574
+ commits,
29575
+ bytes: Buffer.byteLength(body)
29576
+ };
29577
+ }
29578
+ //#endregion
29579
+ //#region src/plan-workspace/plan-boxes.ts
29580
+ /**
29581
+ * Read a plan document's checkboxes and the notes committed under them. One strict parser, fail-
29582
+ * safe in exactly one direction (mirroring `packages/agent/src/blocked-note.ts`): anything
29583
+ * ambiguous degrades to "not declined" rather than throwing or inventing a declination the run
29584
+ * never wrote.
29585
+ *
29586
+ * A **box line** is `^[ \t]*[-*] \[[ xX]\]`. A box's **block** is every line after its box line up
29587
+ * to the next box line, the next ATX heading (`^#{1,6} `), or end of document -- the heading
29588
+ * boundary matters because a batched plan is a sequence of `### <title>`
29589
+ * sections and a note must never leak across one.
29590
+ *
29591
+ * An unchecked box is **declined** when its block carries a line matching
29592
+ * `^[ \t]*(?:[-*][ \t]+)?\*\*Declined:\*\*[ \t]*(.*)$` whose trimmed remainder is non-empty; that
29593
+ * remainder is the reason. An empty reason is not a declination -- it degrades to plain unchecked,
29594
+ * the same direction `packages/agent/src/blocked-note.ts`'s `parseBlockedNote` degrades in, which
29595
+ * makes the reason a forcing function rather than a courtesy. A checked box's block is never
29596
+ * scanned: checked wins, so resolving the contradiction toward "done" cannot make the run loop. A
29597
+ * block carrying both `**Progress:**` and `**Declined:**` reads declined -- the declined
29598
+ * statement speaks for every future slice, the progress note only for this one. `**Progress:**`
29599
+ * and `**Blocked:**` alone declare nothing.
29600
+ *
29601
+ * A box is the **retire task** when its (emphasis-unwrapped) title starts with the literal
29602
+ * `Retire this plan`, not immediately followed by another letter or digit -- a case-sensitive
29603
+ * prefix with a word boundary, the same trust model as `**Declined:**`. That classification
29604
+ * carries no judgement about whether the task is done; `isOutstanding` reads a retire task as
29605
+ * satisfied only when the plan document itself now sits under `retired/`, because the directory,
29606
+ * not the box, is the source of truth for that one task. Every ambiguity -- different case, a
29607
+ * mid-title occurrence, a longer word sharing the prefix, wording that merely mentions retiring --
29608
+ * resolves toward "this is not a retire box", which is today's park.
29609
+ */
29610
+ const BOX_LINE_REGEX = /^[ \t]*[-*] \[([ xX])\][ \t]*(.*)$/;
29611
+ const HEADING_REGEX = /^#{1,6} /;
29612
+ const DECLINED_LINE_REGEX = /^[ \t]*(?:[-*][ \t]+)?\*\*Declined:\*\*[ \t]*(.*)$/;
29613
+ const NOTE_LINE_REGEX = /^[ \t]*(?:[-*][ \t]+)?\*\*(Progress|Blocked|Declined):\*\*[ \t]*(.*)$/;
29614
+ const RETIRE_TASK_REGEX = /^Retire this plan(?![A-Za-z0-9])/;
29615
+ /**
29616
+ * Whether a box still counts as work a further slice could do. Checked and declined boxes never
29617
+ * do. A recognized retire task does not either, but only when the plan is actually retired --
29618
+ * on a live plan it is outstanding work like any other box.
29619
+ */
29620
+ function isOutstanding(box, options) {
29621
+ if (box.checked || box.declined) return false;
29622
+ return !(box.retireTask && options?.planRetired === true);
29623
+ }
29624
+ function noteKind(label) {
29625
+ if (label === "Progress") return "progress";
29626
+ if (label === "Blocked") return "blocked";
29627
+ return "declined";
29628
+ }
29629
+ /**
29630
+ * The checkbox marker stripped, then a `**`/`__` span wrapping the whole title unwrapped (only
29631
+ * when the closing marker matches the opening one, so a title merely starting or ending with the
29632
+ * marker keeps it), then trimmed. Mirrors the title logic the old `firstUncheckedTaskTitle` did
29633
+ * inline.
29634
+ */
29635
+ function unwrapEmphasis(rawTitle) {
29636
+ const emphasis = /^(\*\*|__)/.exec(rawTitle)?.[1];
29637
+ if (emphasis === void 0 || !rawTitle.endsWith(emphasis)) return rawTitle;
29638
+ return rawTitle.slice(emphasis.length, -emphasis.length).trim();
29639
+ }
29640
+ /** Parse every checkbox on a plan document, each with its own block already resolved. */
29641
+ function parsePlanBoxes(planText) {
29642
+ const lines = planText.split("\n");
29643
+ const boxStarts = [];
29644
+ for (let index = 0; index < lines.length; index++) {
29645
+ const match = BOX_LINE_REGEX.exec(lines[index] ?? "");
29646
+ if (match === null) continue;
29647
+ boxStarts.push({
29648
+ index,
29649
+ checked: (match[1] ?? "").toLowerCase() === "x",
29650
+ rawTitle: (match[2] ?? "").trim()
29651
+ });
29652
+ }
29653
+ return boxStarts.map((box, position) => {
29654
+ const nextBoxIndex = boxStarts[position + 1]?.index ?? lines.length;
29655
+ let blockEnd = nextBoxIndex;
29656
+ for (let index = box.index + 1; index < nextBoxIndex; index++) if (HEADING_REGEX.test(lines[index] ?? "")) {
29657
+ blockEnd = index;
29658
+ break;
29659
+ }
29660
+ const title = unwrapEmphasis(box.rawTitle);
29661
+ const retireTask = RETIRE_TASK_REGEX.test(title);
29662
+ if (box.checked) return {
29663
+ checked: true,
29664
+ declined: false,
29665
+ reason: null,
29666
+ title,
29667
+ retireTask,
29668
+ notes: []
29669
+ };
29670
+ const notes = [];
29671
+ for (let index = box.index + 1; index < blockEnd; index++) {
29672
+ const noteMatch = NOTE_LINE_REGEX.exec(lines[index] ?? "");
29673
+ if (noteMatch === null) continue;
29674
+ const text = (noteMatch[2] ?? "").trim();
29675
+ if (text !== "") notes.push({
29676
+ kind: noteKind(noteMatch[1] ?? ""),
29677
+ text
29678
+ });
29679
+ }
29680
+ let reason = null;
29681
+ for (let index = box.index + 1; index < blockEnd; index++) {
29682
+ const declinedMatch = DECLINED_LINE_REGEX.exec(lines[index] ?? "");
29683
+ if (declinedMatch === null) continue;
29684
+ const remainder = (declinedMatch[1] ?? "").trim();
29685
+ if (remainder !== "") {
29686
+ reason = remainder;
29687
+ break;
29688
+ }
29689
+ }
29690
+ return {
29691
+ checked: false,
29692
+ declined: reason !== null,
29693
+ reason,
29694
+ title,
29695
+ retireTask,
29696
+ notes
29697
+ };
29698
+ });
29699
+ }
29700
+ //#endregion
29701
+ //#region src/plan-workspace/task-label.ts
29702
+ /** The line shapes `plan:brief` and `plan:status` both read a task's number from. */
29703
+ const FENCE = /^[ \t]*(```|~~~)/;
29704
+ const HEADING = /^(#{1,6})[ \t]+(.*)$/;
29705
+ const BOX = /^([ \t]*)[-*] \[[ xX]\][ \t]*(.*)$/;
29706
+ const LABEL = /^Task[ \t]+([0-9]+)(?![0-9])/;
29707
+ /**
29708
+ * The `<n>` of a heading or checkbox text that starts `Task <n>` (after an opening `**` or `__`),
29709
+ * or null. A number with a leading zero reads as null, because `plan:brief <n>` matches the digits
29710
+ * as written and could never find `Task 05`.
29711
+ */
29712
+ function taskLabelNumber(text) {
29713
+ const digits = LABEL.exec(text.replace(/^(\*\*|__)/, ""))?.[1];
29714
+ if (digits === void 0 || String(Number(digits)) !== digits) return null;
29715
+ return Number(digits);
29716
+ }
29717
+ //#endregion
29718
+ //#region src/plan-workspace/status.ts
29719
+ const RETIRED_SEGMENT = /(^|\/)retired\//;
29720
+ /**
29721
+ * The nearest `Task <n>` label at or before the plan's `ordinal`-th box line. Box lines are counted
29722
+ * fence-blind, as `parsePlanBoxes` counts them, so the ordinal lands on the same line; only the
29723
+ * label tracking skips fences, as `plan:brief` does.
29724
+ */
29725
+ function enclosingTaskNumber(planText, ordinal) {
29726
+ let inFence = false;
29727
+ let boxes = -1;
29728
+ let label = null;
29729
+ for (const line of planText.split("\n")) {
29730
+ const box = BOX.exec(line);
29731
+ if (box !== null) boxes++;
29732
+ if (box !== null && boxes === ordinal) return taskLabelNumber(box[2] ?? "") ?? label;
29733
+ if (FENCE.test(line)) {
29734
+ inFence = !inFence;
29735
+ continue;
29736
+ }
29737
+ if (inFence) continue;
29738
+ const text = box?.[2] ?? HEADING.exec(line)?.[2];
29739
+ const n = text === void 0 ? null : taskLabelNumber(text);
29740
+ if (n !== null) label = n;
29741
+ }
29742
+ return null;
29743
+ }
29744
+ /** The progress a plan's own checkboxes record; the location decides whether it has retired. */
29745
+ function planStatus(planText, planPath) {
29746
+ const retired = RETIRED_SEGMENT.test(planPath.split("\\").join("/"));
29747
+ const boxes = parsePlanBoxes(planText);
29748
+ const open = boxes.filter((box) => isOutstanding(box, { planRetired: retired }));
29749
+ const notes = boxes.flatMap((box) => box.notes.map((note) => ({
29750
+ task: box.title,
29751
+ kind: note.kind,
29752
+ text: note.text
29753
+ })));
29754
+ const nextBox = open.find((box) => box.title !== "");
29755
+ return {
29756
+ plan: planPath,
29757
+ retired,
29758
+ checked: boxes.filter((box) => box.checked).length,
29759
+ outstanding: open.length,
29760
+ declined: boxes.filter((box) => box.declined).length,
29761
+ next: nextBox?.title ?? null,
29762
+ nextTask: nextBox === void 0 ? null : enclosingTaskNumber(planText, boxes.indexOf(nextBox)),
29763
+ onlyRetireLeft: open.length > 0 && open.every((box) => box.retireTask),
29764
+ notes
29765
+ };
29766
+ }
29767
+ //#endregion
29768
+ //#region src/plan-workspace/task-brief.ts
29769
+ function isTaskLabel(text, n) {
29770
+ return taskLabelNumber(text) === n;
29771
+ }
29772
+ /**
29773
+ * One task's text. Reads both task shapes plans here use: a `### Task <n>` heading (ending at the
29774
+ * next heading of the same or a higher level) and a `- [ ] **Task <n>: ...**` checkbox (ending at
29775
+ * the next checkbox no deeper than it, or any heading). Lines inside a fence are never boundaries.
29776
+ */
29777
+ function extractTask(planText, n) {
29778
+ const lines = planText.split("\n");
29779
+ let inFence = false;
29780
+ let start = -1;
29781
+ let shape = null;
29782
+ let end = lines.length;
29783
+ for (let index = 0; index < lines.length; index++) {
29784
+ const line = lines[index] ?? "";
29785
+ if (FENCE.test(line)) {
29786
+ inFence = !inFence;
29787
+ continue;
29788
+ }
29789
+ if (inFence) continue;
29790
+ const heading = HEADING.exec(line);
29791
+ const box = BOX.exec(line);
29792
+ if (shape === null) {
29793
+ if (heading !== null && isTaskLabel(heading[2] ?? "", n)) {
29794
+ start = index;
29795
+ shape = {
29796
+ kind: "heading",
29797
+ level: (heading[1] ?? "").length
29798
+ };
29799
+ } else if (box !== null && isTaskLabel(box[2] ?? "", n)) {
29800
+ start = index;
29801
+ shape = {
29802
+ kind: "box",
29803
+ indent: (box[1] ?? "").length
29804
+ };
29805
+ }
29806
+ continue;
29807
+ }
29808
+ const endsHeadingTask = shape.kind === "heading" && heading !== null && (heading[1] ?? "").length <= shape.level;
29809
+ const endsBoxTask = shape.kind === "box" && (heading !== null || box !== null && (box[1] ?? "").length <= shape.indent);
29810
+ if (endsHeadingTask || endsBoxTask) {
29811
+ end = index;
29812
+ break;
29813
+ }
29814
+ }
29815
+ if (start < 0) return null;
29816
+ return `${lines.slice(start, end).join("\n").trimEnd()}\n`;
29817
+ }
29818
+ function writeTaskBrief(planPath, n, cwd, out) {
29819
+ let planText;
29820
+ try {
29821
+ planText = readFileSync(path.resolve(cwd, planPath), "utf-8");
29822
+ } catch {
29823
+ throw new FatalError(`no such plan file: ${planPath}`);
29824
+ }
29825
+ const task = extractTask(planText, n);
29826
+ if (task === null) throw new FatalError(`task ${String(n)} not found in ${planPath} (no heading or checkbox starting "Task ${String(n)}")`);
29827
+ const target = out === void 0 ? path.join(resolvePlanWorkspace(planPath, cwd), `task-${String(n)}-brief.md`) : resolveOutPath(out, cwd);
29828
+ writeFileSync(target, task);
29829
+ return {
29830
+ path: target,
29831
+ lines: task.split("\n").length - 1
29832
+ };
29833
+ }
29834
+ //#endregion
29835
+ //#region src/commands/plan.ts
29836
+ const PLAN_ARG = {
29837
+ type: "positional",
29838
+ required: true,
29839
+ description: "Path to the plan document"
29840
+ };
29841
+ /** citty rejects an absent positional but passes an empty one through, and an empty plan path
29842
+ * resolves to the repository root. */
29843
+ function nonEmpty(value, name, command) {
29844
+ if (value === "") throw new FatalError(`${command}: <${name}> must not be empty`);
29845
+ return value;
29846
+ }
29847
+ const planWorkspaceCommand = defineCommand({
29848
+ meta: {
29849
+ name: "plan:workspace",
29850
+ description: "Print (and create) a plan's scratch directory"
29851
+ },
29852
+ args: {
29853
+ plan: PLAN_ARG,
29854
+ json: {
29855
+ type: "boolean",
29856
+ description: "Emit JSON on stdout"
29857
+ }
29858
+ },
29859
+ run({ args }) {
29860
+ const dir = resolvePlanWorkspace(nonEmpty(args.plan, "plan", "plan:workspace"), process.cwd());
29861
+ const out = createCliOutput(args.json === true);
29862
+ out.json({
29863
+ ok: true,
29864
+ path: dir
29865
+ });
29866
+ out.text(dir);
29867
+ }
29868
+ });
29869
+ const planBriefCommand = defineCommand({
29870
+ meta: {
29871
+ name: "plan:brief",
29872
+ description: "Write one plan task's text to a brief file"
29873
+ },
29874
+ args: {
29875
+ plan: PLAN_ARG,
29876
+ "task-number": {
29877
+ type: "positional",
29878
+ required: true,
29879
+ description: "The task's number, as its heading or checkbox names it"
29880
+ },
29881
+ out: {
29882
+ type: "string",
29883
+ description: "Write here instead of the plan workspace"
29884
+ },
29885
+ json: {
29886
+ type: "boolean",
29887
+ description: "Emit JSON on stdout"
29888
+ }
29889
+ },
29890
+ run({ args }) {
29891
+ const plan = nonEmpty(args.plan, "plan", "plan:brief");
29892
+ const raw = args["task-number"];
29893
+ if (!/^[1-9][0-9]*$/.test(raw)) throw new FatalError(`plan:brief: <task-number> must be a positive integer, got: ${raw}`);
29894
+ const result = writeTaskBrief(plan, Number(raw), process.cwd(), args.out);
29895
+ const out = createCliOutput(args.json === true);
29896
+ out.json({
29897
+ ok: true,
29898
+ ...result
29899
+ });
29900
+ out.text(`wrote ${result.path}: ${String(result.lines)} lines`);
29901
+ }
29902
+ });
29903
+ const planReviewPackageCommand = defineCommand({
29904
+ meta: {
29905
+ name: "plan:review-package",
29906
+ description: "Write the commit list, stat, and diff for base..head to one file"
29907
+ },
29908
+ args: {
29909
+ plan: PLAN_ARG,
29910
+ base: {
29911
+ type: "positional",
29912
+ required: true,
29913
+ description: "The commit the task started from"
29914
+ },
29915
+ head: {
29916
+ type: "positional",
29917
+ required: true,
29918
+ description: "The task's last commit"
29919
+ },
29920
+ out: {
29921
+ type: "string",
29922
+ description: "Write here instead of the plan workspace"
29923
+ },
29924
+ json: {
29925
+ type: "boolean",
29926
+ description: "Emit JSON on stdout"
29927
+ }
29928
+ },
29929
+ run({ args }) {
29930
+ const result = writeReviewPackage(nonEmpty(args.plan, "plan", "plan:review-package"), nonEmpty(args.base, "base", "plan:review-package"), nonEmpty(args.head, "head", "plan:review-package"), process.cwd(), args.out);
29931
+ const out = createCliOutput(args.json === true);
29932
+ out.json({
29933
+ ok: true,
29934
+ ...result
29935
+ });
29936
+ out.text(`wrote ${result.path}: ${String(result.commits)} commit(s), ${String(result.bytes)} bytes`);
29937
+ }
29938
+ });
29939
+ function renderStatus(report) {
29940
+ const next = report.next ?? "(none)";
29941
+ const task = report.nextTask === null || taskLabelNumber(next) === report.nextTask ? "" : `Task ${String(report.nextTask)} -- `;
29942
+ const lines = [`${report.plan}${report.retired ? " (retired)" : ""}: ${String(report.checked)} done, ${String(report.outstanding)} outstanding, ${String(report.declined)} declined`, `next: ${task}${next}${report.onlyRetireLeft ? " -- only the retire task is left" : ""}`];
29943
+ for (const note of report.notes) lines.push(` [${note.kind}] ${note.task}: ${note.text}`);
29944
+ return lines.join("\n");
29945
+ }
29946
+ const planStatusCommand = defineCommand({
29947
+ meta: {
29948
+ name: "plan:status",
29949
+ description: "Report a plan's progress from its checkboxes"
29950
+ },
29951
+ args: {
29952
+ plan: PLAN_ARG,
29953
+ json: {
29954
+ type: "boolean",
29955
+ description: "Emit JSON on stdout"
29956
+ }
29957
+ },
29958
+ run({ args }) {
29959
+ const plan = nonEmpty(args.plan, "plan", "plan:status");
29960
+ let text;
29961
+ try {
29962
+ text = readFileSync(path.resolve(process.cwd(), plan), "utf-8");
29963
+ } catch {
29964
+ throw new FatalError(`plan:status: no such plan file: ${plan}`);
29965
+ }
29966
+ const report = planStatus(text, plan);
29967
+ const out = createCliOutput(args.json === true);
29968
+ out.json({
29969
+ ok: true,
29970
+ ...report
29971
+ });
29972
+ out.text(renderStatus(report));
29973
+ }
29974
+ });
29975
+ //#endregion
28835
29976
  //#region src/cli-main.ts
28836
29977
  setLogLevel(resolveLogLevel({}));
28837
29978
  setForgejoLogger(log$1);
@@ -28840,7 +29981,7 @@ setForgejoLogger(log$1);
28840
29981
  * `local <sha>[-dirty]` otherwise -- so an artifact built from the working tree
28841
29982
  * can't pass itself off as the published release whose version it carries.
28842
29983
  */
28843
- const displayVersion = "0.77.0";
29984
+ const displayVersion = "0.78.0";
28844
29985
  /**
28845
29986
  * Wait until a stream's internally-buffered data has been flushed to the OS.
28846
29987
  *
@@ -28939,7 +30080,11 @@ const bstSubCommands = {
28939
30080
  "issue:reopen": issueReopenCommand,
28940
30081
  "findings:add": findingsAddCommand,
28941
30082
  "findings:list": findingsListCommand,
28942
- "findings:resolve": findingsResolveCommand
30083
+ "findings:resolve": findingsResolveCommand,
30084
+ "plan:workspace": planWorkspaceCommand,
30085
+ "plan:brief": planBriefCommand,
30086
+ "plan:review-package": planReviewPackageCommand,
30087
+ "plan:status": planStatusCommand
28943
30088
  };
28944
30089
  /**
28945
30090
  * Raise the process log level to what `--verbose`/`-v`/`--trace` in `rawArgs` ask for.