@bensandee/tooling 0.79.0 → 0.81.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.mjs CHANGED
@@ -78,15 +78,15 @@ function resolveLogLevel(flags) {
78
78
  return "info";
79
79
  }
80
80
  const isCI$1 = Boolean(process.env["CI"]);
81
- const out$5 = (msg) => console.log(msg);
81
+ const out$1 = (msg) => console.log(msg);
82
82
  const err = (line) => console.error(line);
83
83
  /** The plain user-facing adapter -- stdout with level tags, mirroring the pre-unification `log`.
84
84
  * The tag is what carries the level once clack's coloured symbol is gone. */
85
85
  const plainUserSink = {
86
- info: out$5,
87
- warn: (msg) => out$5(`[warn] ${msg}`),
88
- error: (msg) => out$5(`[error] ${msg}`),
89
- success: (msg) => out$5(`[ok] ${msg}`)
86
+ info: out$1,
87
+ warn: (msg) => out$1(`[warn] ${msg}`),
88
+ error: (msg) => out$1(`[error] ${msg}`),
89
+ success: (msg) => out$1(`[ok] ${msg}`)
90
90
  };
91
91
  let plainOutput = isCI$1;
92
92
  let jsonMode = false;
@@ -188,17 +188,27 @@ const rootLogger = createLogger();
188
188
  * purpose: `vitest.config.ts` excludes that file from coverage as entry-point wiring, so any
189
189
  * logic worth testing has to live somewhere coverage actually reaches.
190
190
  */
191
- /** True when `rawArgs` ask for JSON mode: the exact token `--json`, no `--help`/`-h`, and not
192
- * the bare `--version` query. Exact token, so `--json=false` and `--no-json` never match --
193
- * citty would parse those into `{json: false}`, not into this literal string. A `--json` that
194
- * arrives as the *value* of a preceding string flag (e.g. `--body --json`) is scanned the same
195
- * way and is a known, accepted ambiguity: this is a raw-argument scan, not an arg-aware parse. */
191
+ /** True when `rawArgs` ask for JSON mode: a `--json` token (bare or `--json=<truthy>`), no
192
+ * `--help`/`-h`, and not the bare `--version` query. The `=<value>` form is matched because citty
193
+ * coerces a boolean flag that way (`value !== "false"`), so `--json=true` DOES put the command in
194
+ * json mode; matching only the bare token left that invocation with `createCliOutput` suppressing
195
+ * prose into a buffer `runBstCli` never flushed -- zero bytes of output for exit code 0. `--json=false`
196
+ * and `--no-json` stay excluded, the way citty parses them. A `--json` that arrives as the *value*
197
+ * of a preceding string flag (e.g. `--body --json`) is scanned the same way and is a known,
198
+ * accepted ambiguity: this is a raw-argument scan, not an arg-aware parse. */
196
199
  function resolveJsonMode(rawArgs) {
197
- if (!rawArgs.includes("--json")) return false;
200
+ if (!rawArgs.some(requestsJson)) return false;
198
201
  if (rawArgs.includes("--help") || rawArgs.includes("-h")) return false;
199
202
  if (rawArgs.length === 1 && (rawArgs[0] === "--version" || rawArgs[0] === "-v")) return false;
200
203
  return true;
201
204
  }
205
+ /** One raw token: whether it asks for json mode, mirroring citty's boolean coercion of a value
206
+ * (anything but the literal `false` is truthy). */
207
+ function requestsJson(token) {
208
+ if (token === "--json") return true;
209
+ if (!token.startsWith("--json=")) return false;
210
+ return token !== "--json=false";
211
+ }
202
212
  /** The first token that is not a flag and not a flag's value, mirroring citty's own
203
213
  * `findSubCommandIndex` for a command whose own `args` is empty (true of `bst`'s top-level
204
214
  * command) -- a `--` sentinel ends the scan with no command found, same as citty. */
@@ -229,8 +239,8 @@ function asCommandDef(value) {
229
239
  if (value instanceof Promise) return void 0;
230
240
  return value;
231
241
  }
232
- /** Whether a resolved command declares a `json` argument -- the gate between "granted --json"
233
- * and the coverage test's `OWED`/`EXEMPT` backlog. */
242
+ /** Whether a resolved command declares a `json` argument -- what the coverage test reads to
243
+ * decide between "granted --json" and its `EXEMPT` list. */
234
244
  function commandDeclaresJson(cmd) {
235
245
  if (cmd === void 0) return false;
236
246
  const argsDef = cmd.args;
@@ -397,7 +407,7 @@ function loadEnvFileForBin(rawArgs, io = processIo) {
397
407
  if (resolveJsonMode(rawArgs)) {
398
408
  const document = buildErrorDocument({
399
409
  command: resolveCommandName(rawArgs),
400
- version: "0.79.0",
410
+ version: "0.81.0",
401
411
  kind: "usage",
402
412
  message
403
413
  });
@@ -423,7 +433,7 @@ const envFileLoad = loadEnvFileForBin(process.argv.slice(2));
423
433
  //#endregion
424
434
  //#region src/utils/log.ts
425
435
  const isCI = Boolean(process.env["CI"]);
426
- const out$4 = (msg) => console.log(msg);
436
+ const out = (msg) => console.log(msg);
427
437
  /**
428
438
  * The process-wide logger. Kept exported as `log` for the ~43 call sites that predate the unified
429
439
  * {@link rootLogger}; new code that needs levels/context imports `Logger` from ./logger.ts.
@@ -432,11 +442,84 @@ const log$1 = rootLogger;
432
442
  function note(body, title) {
433
443
  if (isJsonMode()) return;
434
444
  if (isCI) {
435
- if (title) out$4(`--- ${title} ---`);
436
- out$4(body);
445
+ if (title) out(`--- ${title} ---`);
446
+ out(body);
437
447
  } else clack.note(body, title);
438
448
  }
439
449
  //#endregion
450
+ //#region src/utils/cli-output.ts
451
+ const stdout = (msg) => console.log(msg);
452
+ function initialState() {
453
+ return {
454
+ payload: void 0,
455
+ hasPayload: false,
456
+ error: void 0,
457
+ hasError: false,
458
+ notices: [],
459
+ wrote: false
460
+ };
461
+ }
462
+ let state = initialState();
463
+ function isJsonRecord(value) {
464
+ return typeof value === "object" && value !== null && !Array.isArray(value);
465
+ }
466
+ function createCliOutput(jsonMode) {
467
+ const effective = jsonMode || isJsonMode();
468
+ return {
469
+ json: (payload) => {
470
+ if (!effective) return;
471
+ if (state.hasPayload) log$1.debug("cli-output: a second json() call overwrote the first -- last call wins.");
472
+ state.payload = payload;
473
+ state.hasPayload = true;
474
+ },
475
+ text: (message) => {
476
+ if (!effective) stdout(message);
477
+ },
478
+ notice: (message) => {
479
+ if (effective) state.notices.push(redact(message));
480
+ else stdout(message);
481
+ }
482
+ };
483
+ }
484
+ /** Replace what {@link flushJsonOutput} writes with an error document, discarding any payload
485
+ * already recorded by `json()` -- buffered notices are not discarded, they fold into the error
486
+ * document the same way they fold into a payload. */
487
+ function recordJsonError(document) {
488
+ state.error = document;
489
+ state.hasError = true;
490
+ }
491
+ function buildFlushDocument() {
492
+ let base;
493
+ if (state.hasError) base = state.error;
494
+ else if (state.hasPayload) base = state.payload;
495
+ if (base === void 0) return void 0;
496
+ if (state.notices.length === 0) return base;
497
+ if (isJsonRecord(base)) return {
498
+ ...base,
499
+ notices: state.notices
500
+ };
501
+ return {
502
+ payload: base,
503
+ notices: state.notices
504
+ };
505
+ }
506
+ /** Write the single recorded document -- the error document if `recordJsonError` was called,
507
+ * otherwise the recorded payload, each with buffered notices folded in under `notices` when
508
+ * the buffer is non-empty -- and report whether anything was written. */
509
+ function flushJsonOutput() {
510
+ const document = buildFlushDocument();
511
+ if (document === void 0) return false;
512
+ stdout(JSON.stringify(document));
513
+ state.wrote = true;
514
+ return true;
515
+ }
516
+ /** Whether {@link flushJsonOutput} has already written a document this process -- the guard the
517
+ * premature-exit net (`cli-json.ts`) reads to tell a normal flush from a process that died with
518
+ * the command still outstanding. */
519
+ function jsonDocumentWritten() {
520
+ return state.wrote;
521
+ }
522
+ //#endregion
440
523
  //#region src/types.ts
441
524
  const LEGACY_TOOLS = [
442
525
  "eslint",
@@ -764,6 +847,11 @@ const AsciiConfigSchema = z.strictObject({
764
847
  treatAsProse: z.array(z.string()).optional().meta({ description: "Path globs (matched the same way as exclude) whose files --fix classifies as prose wholesale regardless of extension, flattening rather than flagging. The opt-back-in for an extension with no dedicated lexical-context classifier where blind flattening is known safe. No built-in defaults." })
765
848
  });
766
849
  const LintConfigSchema = z.strictObject({ strictTypeAware: z.boolean().optional().meta({ description: "Make `bst lint:types` (the type-aware oxlint gate, backed by oxlint-tsgolint) exit non-zero on any finding while the tooling is still in its report phase (default false; the enforce phase, a later tooling release, exits non-zero unconditionally). A missing oxlint-tsgolint binary is also a hard failure rather than a notice while this is set: arming the gate is what turns a platform gap into a build break, deliberately, because the repo that armed it is the one that asked for it." }) });
850
+ const ChecksConfigSchema = z.strictObject({
851
+ add: z.array(z.string()).optional().meta({ description: "Extra package.json scripts to run as first-class check steps, appended to the built-in list (e.g. ['lint:php']). An added step with no matching script is a hard failure." }),
852
+ skip: z.array(z.string()).optional().meta({ description: "Check steps to skip, unioned with the built-in CI defaults (never replacing them). Supports glob patterns." }),
853
+ runOnVersionPr: z.boolean().optional().meta({ description: "Run the CI check job on the changesets version PR (changeset-release/main). Default false: the generated check job is skipped there, because that branch is regenerated on every push to main and main's own run gates the release. Has no effect unless releaseStrategy is changesets." })
854
+ });
767
855
  const CoverageConfigSchema = z.strictObject({
768
856
  history: z.enum(["orphan-branch", "none"]).optional().meta({ description: "How coverage history is persisted. 'orphan-branch' (default) appends to a dedicated branch; 'none' disables CI history but keeps the local ratchet." }),
769
857
  historyBranch: z.string().optional().meta({ description: "Orphan branch name for coverage history (default: 'bst/coverage-metrics')" }),
@@ -1892,10 +1980,6 @@ const BranchPruneConfigSchema = z.strictObject({
1892
1980
  exclude: z.array(z.string()).optional().meta({ description: "Branch-name globs added to bst branch:prune's built-in never-touch list (main, changeset-release/*, renovate/*, bst/coverage-metrics, bst/health-metrics, bst/egress-metrics, anything with an open PR or protected). Additive only -- there is no key that removes a built-in entry." })
1893
1981
  });
1894
1982
  const IssuesConfigSchema = z.strictObject({ assignees: z.array(z.string().min(1)).optional().meta({ description: "The repo's default reviewer(s) for issues opened by `bst issue:create` -- assigned when the invocation names no --assignee and no --repo. An explicit --assignee replaces this default rather than adding to it; --no-assignee opens an issue deliberately unassigned." }) });
1895
- const ChecksConfigSchema = z.strictObject({
1896
- add: z.array(z.string()).optional().meta({ description: "Extra package.json scripts to run as first-class check steps, appended to the built-in list (e.g. ['lint:php']). An added step with no matching script is a hard failure." }),
1897
- skip: z.array(z.string()).optional().meta({ description: "Check steps to skip, unioned with the built-in CI defaults (never replacing them). Supports glob patterns." })
1898
- });
1899
1983
  /** One label definition inside a `labelSets` entry -- the same shape `bst forgejo:labels sync`
1900
1984
  * writes to the forge (`AgentLabelSpec` in `@bensandee/forgejo/label-provisioning`), expressed as a
1901
1985
  * zod schema so a malformed entry in `.tooling.json` fails at load time rather than at the first API
@@ -3848,24 +3932,22 @@ function reconcileTags(expectedTags, remoteTags, stdoutTags) {
3848
3932
  * semantics and the plan that feeds them cannot drift apart.
3849
3933
  */
3850
3934
  const VERSION_PATTERN = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
3851
- /**
3852
- * What `git ls-remote --tags` says about every tag on the remote: tag name to the commit it
3853
- * describes.
3854
- *
3855
- * Keeps the SHA (unlike `parseRemoteTags`, which answers only "does this tag exist") because the
3856
- * forward-only decision compares a floating tag's target against the commits the exact version tags
3857
- * name. An annotated tag appears twice in that listing -- once for the tag object, once peeled with
3858
- * a `^{}` suffix for the commit it points at -- and the peeled line wins: `commit-and-tag-version`
3859
- * (the simple publish path) makes annotated tags, so its object SHA is not the commit anything
3860
- * else agrees on.
3861
- */
3862
3935
  function parseRemoteTagTargets(lsRemoteStdout) {
3863
3936
  const targets = /* @__PURE__ */ new Map();
3864
3937
  for (const line of lsRemoteStdout.split("\n")) {
3865
3938
  const match = /^([0-9a-f]{7,40})\s+refs\/tags\/(.+)$/.exec(line.trim());
3866
3939
  if (!match?.[1] || !match[2]) continue;
3867
- if (match[2].endsWith("^{}")) targets.set(match[2].slice(0, -3), match[1]);
3868
- else targets.set(match[2], match[1]);
3940
+ if (match[2].endsWith("^{}")) {
3941
+ const name = match[2].slice(0, -3);
3942
+ const existing = targets.get(name);
3943
+ targets.set(name, {
3944
+ ref: existing?.ref ?? match[1],
3945
+ commit: match[1]
3946
+ });
3947
+ } else targets.set(match[2], {
3948
+ ref: match[1],
3949
+ commit: match[1]
3950
+ });
3869
3951
  }
3870
3952
  return targets;
3871
3953
  }
@@ -3884,6 +3966,34 @@ function parseReleaseVersion(version) {
3884
3966
  prerelease: match[4] === void 0 ? [] : match[4].split(".")
3885
3967
  };
3886
3968
  }
3969
+ /**
3970
+ * Whether `version` names a prerelease. The one spelling of that question the git publish paths and
3971
+ * the Docker registry share, so a beta cannot move a floating ref on one surface and not another.
3972
+ *
3973
+ * Build metadata (`1.2.3+sha`) is not a prerelease, and neither is a version that will not parse --
3974
+ * an unknown fails open everywhere else in this module.
3975
+ */
3976
+ function isPrerelease(version) {
3977
+ return (parseReleaseVersion(version)?.prerelease.length ?? 0) > 0;
3978
+ }
3979
+ /**
3980
+ * Whether a bare tag name names a prerelease, without the workspace listing
3981
+ * {@link prereleaseTag} in `release/publish/publish.ts` needs.
3982
+ *
3983
+ * Both tag shapes are tried because a caller reading a tag off `GITHUB_REF` or a CLI flag does not
3984
+ * know which strategy published it: the `vX.Y.Z` form first, then the changesets
3985
+ * `<name>@<version>` form (whose name can itself contain `@`, hence the last one). A tag neither
3986
+ * shape resolves -- a floating ref, or one no package owns -- answers `false`, the same fail-open
3987
+ * answer `prereleaseTag`'s doc comment describes: an unknown moves and announces as a release
3988
+ * rather than aborting or withholding the announcement.
3989
+ */
3990
+ function tagIsPrerelease(tag) {
3991
+ let version = simpleTagFamily().versionOfExact(tag);
3992
+ const separator = tag.lastIndexOf("@");
3993
+ if (version === void 0 && separator > 0) version = changesetsTagFamily(tag.slice(0, separator)).versionOfExact(tag);
3994
+ if (version === void 0) return false;
3995
+ return isPrerelease(version);
3996
+ }
3887
3997
  function comparePrereleaseIdentifier(a, b) {
3888
3998
  const aNumeric = /^\d+$/.test(a);
3889
3999
  const bNumeric = /^\d+$/.test(b);
@@ -3959,8 +4069,8 @@ function simpleTagFamily() {
3959
4069
  }
3960
4070
  function highestVersionAtSha(family, remote, sha) {
3961
4071
  let highest;
3962
- for (const [tag, tagSha] of remote) {
3963
- if (tagSha !== sha) continue;
4072
+ for (const [tag, target] of remote) {
4073
+ if (target.commit !== sha) continue;
3964
4074
  const version = family.versionOfExact(tag);
3965
4075
  if (version === void 0) continue;
3966
4076
  if (highest === void 0 || compareReleaseVersions(version, highest) > 0) highest = version;
@@ -3974,16 +4084,35 @@ function highestVersionAtSha(family, remote, sha) {
3974
4084
  * the deliberate fail-open: an absent tag, a target no exact tag names (hand-moved, or predating
3975
4085
  * this tooling), and a target named by a tag whose version will not parse all leave the tag free to
3976
4086
  * move. Only a target whose version is known and newer holds it.
4087
+ *
4088
+ * A prerelease is the exception decided ahead of all of that: it keeps every floating tag, before the
4089
+ * remote snapshot is consulted at all.
3977
4090
  */
3978
4091
  function planFloatingTagMoves(family, version, remote) {
3979
- const targetSha = remote.get(family.exact(version));
4092
+ const targetSha = remote.get(family.exact(version))?.commit;
3980
4093
  const planned = [];
4094
+ if (isPrerelease(version)) {
4095
+ for (const tag of family.floating(version)) {
4096
+ const observed = remote.get(tag);
4097
+ planned.push({
4098
+ tag,
4099
+ targetSha,
4100
+ observedSha: observed?.commit,
4101
+ observedRef: observed?.ref,
4102
+ decision: "keep",
4103
+ reason: `${version} is a prerelease`
4104
+ });
4105
+ }
4106
+ return planned;
4107
+ }
3981
4108
  for (const tag of family.floating(version)) {
3982
- const observedSha = remote.get(tag);
4109
+ const observed = remote.get(tag);
4110
+ const observedSha = observed?.commit;
3983
4111
  const base = {
3984
4112
  tag,
3985
4113
  targetSha,
3986
- observedSha
4114
+ observedSha,
4115
+ observedRef: observed?.ref
3987
4116
  };
3988
4117
  if (observedSha === void 0) {
3989
4118
  planned.push({
@@ -4061,11 +4190,16 @@ function readRemoteTagTargets(call) {
4061
4190
  return parseRemoteTagTargets(read.stdout);
4062
4191
  }
4063
4192
  /** Every floating tag of this call that no attempt has settled is a failure. */
4064
- function markUnsettledFailed(call, run, versions) {
4065
- for (const version of versions) for (const tag of call.family.floating(version)) if (!run.decided.has(tag)) run.outcome.failed.push(tag);
4193
+ function markUnsettledFailed(run, entries) {
4194
+ for (const entry of entries) for (const tag of entry.family.floating(entry.version)) if (!run.decided.has(tag)) run.outcome.failed.push(tag);
4066
4195
  }
4067
4196
  /**
4068
- * Move each family's floating tags to `versions`, forward only, one lease push per tag.
4197
+ * Move every entry's floating tags forward only, one lease push per tag.
4198
+ *
4199
+ * Every entry is planned against one remote snapshot per attempt, so a workspace publish reads the
4200
+ * tag list once instead of once per package; only tags whose lease was rejected go round again,
4201
+ * re-decided against the next attempt's read. The shared decided/rejected sets stay correct across
4202
+ * families because their tag names are distinct (`@scope/name@latest` against `latest`/`vX`).
4069
4203
  *
4070
4204
  * The lease is what makes the forward-only rule exact rather than probabilistic: without it, two
4071
4205
  * overlapping runs that read the same target and both decide to move can still finish in the wrong
@@ -4075,11 +4209,10 @@ function markUnsettledFailed(call, run, versions) {
4075
4209
  * `coverage-tracking/history.ts`, except that it ends in a warning rather than a failure, because a
4076
4210
  * stale floating ref costs a reader a wrong `latest`, not a lost release.
4077
4211
  */
4078
- function moveFloatingTags(executor, options, family, versions) {
4212
+ function moveFloatingTags(executor, options, entries) {
4079
4213
  const call = {
4080
4214
  executor,
4081
- options,
4082
- family
4215
+ options
4083
4216
  };
4084
4217
  const run = {
4085
4218
  outcome: {
@@ -4095,12 +4228,12 @@ function moveFloatingTags(executor, options, family, versions) {
4095
4228
  run.rejected = [];
4096
4229
  const remote = readRemoteTagTargets(call);
4097
4230
  if (remote === void 0) {
4098
- markUnsettledFailed(call, run, versions);
4231
+ markUnsettledFailed(run, entries);
4099
4232
  return run.outcome;
4100
4233
  }
4101
- for (const version of versions) for (const move of planFloatingTagMoves(family, version, remote)) {
4234
+ for (const entry of entries) for (const move of planFloatingTagMoves(entry.family, entry.version, remote)) {
4102
4235
  if (run.decided.has(move.tag)) continue;
4103
- applyFloatingMove(call, version, move, run);
4236
+ applyFloatingMove(call, entry, move, run);
4104
4237
  }
4105
4238
  if (run.rejected.length === 0) return run.outcome;
4106
4239
  if (attempt === MAX_FLOATING_TAG_ATTEMPTS) {
@@ -4112,18 +4245,19 @@ function moveFloatingTags(executor, options, family, versions) {
4112
4245
  return run.outcome;
4113
4246
  }
4114
4247
  /** Decide and act on one planned move, recording the verdict in `run`. */
4115
- function applyFloatingMove(call, version, move, run) {
4248
+ function applyFloatingMove(call, entry, move, run) {
4116
4249
  if (move.decision === "keep" || move.decision === "current") {
4117
4250
  run.decided.add(move.tag);
4118
4251
  run.outcome.kept.push(move.tag);
4119
4252
  log$1.info(`Leaving ${move.tag} alone: ${move.reason}`);
4120
4253
  return;
4121
4254
  }
4122
- const targetSha = move.targetSha ?? localTarget(call, version);
4255
+ const version = entry.version;
4256
+ const targetSha = move.targetSha ?? localTarget(call, entry);
4123
4257
  if (targetSha === void 0) {
4124
4258
  run.decided.add(move.tag);
4125
4259
  run.outcome.failed.push(move.tag);
4126
- log$1.warn(`Could not resolve the commit for ${move.tag}: no ${call.family.exact(version)} to point it at`);
4260
+ log$1.warn(`Could not resolve the commit for ${move.tag}: no ${entry.family.exact(version)} to point it at`);
4127
4261
  return;
4128
4262
  }
4129
4263
  if (call.options.dryRun) {
@@ -4152,7 +4286,7 @@ function pushFloatingMove(call, move, targetSha, run) {
4152
4286
  log$1.warn(`Failed to create ${move.tag}: ${describeGitFailure(localTag)}`);
4153
4287
  return;
4154
4288
  }
4155
- const expectation = move.observedSha ?? "";
4289
+ const expectation = move.observedRef ?? "";
4156
4290
  const lease = `--force-with-lease=refs/tags/${move.tag}:${expectation}`;
4157
4291
  const push = call.executor.exec("git", [
4158
4292
  ...call.options.git.args,
@@ -4171,8 +4305,8 @@ function pushFloatingMove(call, move, targetSha, run) {
4171
4305
  run.rejected.push(move.tag);
4172
4306
  }
4173
4307
  /** Where a floating tag should point when the remote read has no exact tag to read the SHA from. */
4174
- function localTarget(call, version) {
4175
- return resolveTagCommit(call.executor, call.options.cwd, call.family.exact(version));
4308
+ function localTarget(call, entry) {
4309
+ return resolveTagCommit(call.executor, call.options.cwd, entry.family.exact(entry.version));
4176
4310
  }
4177
4311
  //#endregion
4178
4312
  //#region src/release/docker.ts
@@ -4570,17 +4704,30 @@ function parseSemver(version) {
4570
4704
  patch: Number(match[3])
4571
4705
  };
4572
4706
  }
4573
- /** Generate semver tag variants: latest, vX.Y.Z, vX.Y, vX */
4707
+ /** Generate the image tag variants: latest, the exact record tag, vX.Y, vX. Only the record tag
4708
+ * carries a prerelease suffix; the floaters name the triple they point at. */
4574
4709
  function generateTags(version) {
4575
- const { major, minor, patch } = parseSemver(version);
4710
+ const { major, minor } = parseSemver(version);
4576
4711
  return [
4577
4712
  "latest",
4578
- `v${major}.${minor}.${patch}`,
4713
+ exactImageTag(version),
4579
4714
  `v${major}.${minor}`,
4580
4715
  `v${major}`
4581
4716
  ];
4582
4717
  }
4583
4718
  /**
4719
+ * The image tag that is the record for `version` -- the one a floating tag never is. It spells the
4720
+ * version as written, so a prerelease is distinguishable in the registry; `parseSemver` is called
4721
+ * only to reject a version that is not a triple at all.
4722
+ *
4723
+ * Build metadata is dropped rather than carried: `+` is outside Docker's tag grammar
4724
+ * (`[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}`), so spelling it produces a ref `docker tag` rejects.
4725
+ */
4726
+ function exactImageTag(version) {
4727
+ parseSemver(version);
4728
+ return `v${version.replace(/^v/, "").split("+")[0]}`;
4729
+ }
4730
+ /**
4584
4731
  * Build the full image reference. An `imageName` that already carries a registry host
4585
4732
  * (per {@link imageRegistryHost}) is used as written -- `namespace` is ignored -- because
4586
4733
  * prefixing it again would nest a second registry inside the ref. This is safe only because
@@ -4747,27 +4894,25 @@ function runDockerBuild(executor, config) {
4747
4894
  return { packages };
4748
4895
  }
4749
4896
  /**
4750
- * Run the full Docker publish pipeline:
4751
- * 1. Build all images via runDockerBuild
4752
- * 2. Login to registry
4753
- * 3. Tag each image with semver variants from its resolved version
4754
- * 4. Push all tags
4755
- * 5. Logout from registry
4897
+ * Tag a single package's image with all its tags and push them (unless dry-run).
4756
4898
  *
4757
- * A floating tag (`latest`, `vX`, `vX.Y`) moves forward only: its published manifest is read
4758
- * for the version label and left alone when that names a newer version than this run's. The
4759
- * accepted residual window is that a registry offers no compare-and-swap for a tag, so two
4760
- * overlapping runs that both read `latest` before either pushes can still finish in the wrong
4761
- * order -- seconds wide, where an unconditional push was open for a whole run.
4899
+ * A prerelease publishes its record tag only: every floating ref is reported as skipped before it
4900
+ * is even tagged locally, in a dry run as in a live one, so a beta never displaces the `latest` a
4901
+ * deployment pulls.
4762
4902
  */
4763
- /** Tag a single package's image with all its tags and push them (unless dry-run). */
4764
4903
  function tagAndPushPackage(executor, config, pkg, allTags, skipped) {
4765
4904
  const version = pkg.version ?? "";
4905
+ const prerelease = isPrerelease(version);
4766
4906
  const tags = generateTags(version);
4767
4907
  const exactTag = exactImageTag(version);
4768
4908
  log$1.info(`${pkg.dir} v${pkg.version} -> tags: ${tags.join(", ")}`);
4769
4909
  for (const tag of tags) {
4770
4910
  const ref = imageRef(config.registryNamespace, pkg.imageName, tag);
4911
+ if (prerelease && tag !== exactTag) {
4912
+ log$1.info(`Leaving ${ref} alone: ${version} is a prerelease`);
4913
+ skipped.push(ref);
4914
+ continue;
4915
+ }
4771
4916
  log$1.info(`Tagging ${pkg.imageName} -> ${ref}`);
4772
4917
  const tagResult = executor.exec("docker", [
4773
4918
  "tag",
@@ -4796,19 +4941,16 @@ function tagAndPushPackage(executor, config, pkg, allTags, skipped) {
4796
4941
  if (pushResult.exitCode !== 0) throw new FatalError(`docker push failed: ${pushResult.stderr}`);
4797
4942
  }
4798
4943
  }
4799
- /** The image tag that is the record for `version` -- the one a floating tag never is. */
4800
- function exactImageTag(version) {
4801
- const { major, minor, patch } = parseSemver(version);
4802
- return `v${major}.${minor}.${patch}`;
4803
- }
4804
4944
  /**
4805
4945
  * One image descriptor's config labels, as `docker buildx imagetools inspect --format
4806
4946
  * '{{json .Image}}'` renders them.
4807
4947
  *
4808
- * `Labels` is `null` (not `{}`) for an image built with no labels at all -- Go marshals a nil map
4809
- * to null -- and absent entirely when the manifest carries no config blob. Both read as "no
4810
- * version label", which is the fail-open answer: an unlabelled `latest` moves, exactly as it did
4811
- * before this label existed.
4948
+ * `Labels` is absent when the config carries none -- `opencontainers/image-spec`'s `ImageConfig`
4949
+ * declares the field with `omitempty`, so a nil or empty map is left out of the JSON rather than
4950
+ * rendered as `{}` -- and it may still arrive as `null` when a tool marshals a nil map explicitly.
4951
+ * Both read as "no version label", which is the fail-open answer: an unlabelled `latest` moves,
4952
+ * exactly as it did before this label existed. Neither half may be dropped from the schema: the
4953
+ * optional covers the omitted field, the nullable the explicit null.
4812
4954
  */
4813
4955
  const ImageDescriptorSchema = z.object({ config: z.object({ Labels: z.union([z.record(z.string(), z.string()), z.null()]).optional() }) });
4814
4956
  /** `.Image` for a multi-platform build is a record of descriptors keyed by platform string. */
@@ -4852,6 +4994,20 @@ function readPublishedImageVersion(executor, config, ref) {
4852
4994
  if (list.success) return versionFromImageManifest(list.data);
4853
4995
  debug(config, `Unexpected manifest shape for ${ref}; no version label read`);
4854
4996
  }
4997
+ /**
4998
+ * Run the full Docker publish pipeline:
4999
+ * 1. Build all images via runDockerBuild
5000
+ * 2. Login to registry
5001
+ * 3. Tag each image with semver variants from its resolved version
5002
+ * 4. Push all tags
5003
+ * 5. Logout from registry
5004
+ *
5005
+ * A floating tag (`latest`, `vX`, `vX.Y`) moves forward only: its published manifest is read
5006
+ * for the version label and left alone when that names a newer version than this run's. The
5007
+ * accepted residual window is that a registry offers no compare-and-swap for a tag, so two
5008
+ * overlapping runs that both read `latest` before either pushes can still finish in the wrong
5009
+ * order -- seconds wide, where an unconditional push was open for a whole run.
5010
+ */
4855
5011
  function runDockerPublish(executor, config) {
4856
5012
  const { packages } = runDockerBuild(executor, {
4857
5013
  cwd: config.cwd,
@@ -4898,7 +5054,7 @@ function runDockerPublish(executor, config) {
4898
5054
  }
4899
5055
  }
4900
5056
  log$1.info(`Published ${String(allTags.length)} image tag(s)`);
4901
- if (skipped.length > 0) log$1.info(`Left ${String(skipped.length)} newer floating tag(s) alone: ${skipped.join(", ")}`);
5057
+ if (skipped.length > 0) log$1.info(`Left ${String(skipped.length)} floating tag(s) alone: ${skipped.join(", ")}`);
4902
5058
  return {
4903
5059
  packages,
4904
5060
  tags: allTags,
@@ -5225,6 +5381,7 @@ function buildWorkflowYaml(options) {
5225
5381
  if (options.permissions) doc["permissions"] = options.permissions;
5226
5382
  if (options.concurrency) doc["concurrency"] = options.concurrency;
5227
5383
  const jobs = { [options.jobName]: {
5384
+ ...options.jobIf && { if: options.jobIf },
5228
5385
  "runs-on": options.runsOn ?? "ubuntu-latest",
5229
5386
  ...options.jobEnv && { env: options.jobEnv },
5230
5387
  ...options.jobOutputs && { outputs: options.jobOutputs },
@@ -5338,8 +5495,8 @@ function declaredPublishRegistries(targetDir, structure, rootPackageJson) {
5338
5495
  * shape of custom regex manager in the root `renovate.json` can rewrite both this version and this
5339
5496
  * digest (`customManagers` entries scoped to this file).
5340
5497
  */
5341
- const MISE_VERSION = "2026.9.12";
5342
- const MISE_LINUX_X64_SHA256 = "e79ae57945034903aee8aa2ea66b4c7ca9cd4f4edd5a8a78a589cbae6d0f428a";
5498
+ const MISE_VERSION = "2026.9.13";
5499
+ const MISE_LINUX_X64_SHA256 = "a72f49916b33ba952ba398046c5cc91a58238f0b718fee1d938206ef2af21c6d";
5343
5500
  /** Where generated Forgejo workflows fetch the shared mise setup action: a composite action in
5344
5501
  * `bensandee/common`, on the forge every fleet repo lives on. GitHub cannot reach it; the
5345
5502
  * generators keep inline steps there. */
@@ -5374,7 +5531,9 @@ const IS_MAIN_BRANCH = `github.ref == '${MAIN_BRANCH_REF}'`;
5374
5531
  * version push (`release/publish/version.ts`), the `headBumpedVersion` publish guard plus reading
5375
5532
  * remote tags and releases before acting (`commands/release-changesets.ts`,
5376
5533
  * `release/publish/publish.ts`), and floating git and Docker tags that only move forward
5377
- * (`release/publish/floating-tags.ts`, `release/docker.ts`). On Forgejo the concurrency block is
5534
+ * (`release/publish/floating-tags.ts`, `release/docker.ts`). Such a run may also version pending
5535
+ * changesets after it publishes -- the stale version-PR merge -- under the same supersession guard
5536
+ * and push lease as any other version run. On Forgejo the concurrency block is
5378
5537
  * only a PR-run cancellation mechanism -- it holds a main run in its group and, in Forgejo 16,
5379
5538
  * intermittently fails to release it, which is why main runs get a group of their own there. GitHub's shape stays shared
5380
5539
  * because its queue works: one run, one pending, a newer arrival replacing the pending, so
@@ -6712,7 +6871,7 @@ function buildManagedDevDeps(ctx) {
6712
6871
  const devDeps = { ...rootDevDeps(ctx.config) };
6713
6872
  if (!isMonorepo) Object.assign(devDeps, perPackageDevDeps(ctx.config));
6714
6873
  if (ctx.config.stack !== "none" || ctx.config.ci === "forgejo") devDeps["@bensandee/config"] = isWorkspacePackage(ctx, "@bensandee/config") ? "workspace:*" : "0.14.5";
6715
- if (!isWorkspacePackage(ctx, "@bensandee/tooling")) devDeps["@bensandee/tooling"] = "0.79.0";
6874
+ if (!isWorkspacePackage(ctx, "@bensandee/tooling")) devDeps["@bensandee/tooling"] = "0.81.0";
6716
6875
  if (ctx.config.useEslintPlugin && ctx.config.stack !== "none") devDeps["@bensandee/eslint-plugin"] = isWorkspacePackage(ctx, "@bensandee/eslint-plugin") ? "workspace:*" : "0.15.2";
6717
6876
  if (ctx.config.formatter === "oxfmt") devDeps["oxfmt"] = {
6718
6877
  "@changesets/cli": "3.0.3",
@@ -8212,7 +8371,9 @@ const ReleaseSchema = z.object({
8212
8371
  id: z.number(),
8213
8372
  tag_name: z.string().optional(),
8214
8373
  created_at: z.string().optional(),
8215
- published_at: z.string().optional()
8374
+ published_at: z.string().optional(),
8375
+ html_url: z.string().nullish(),
8376
+ prerelease: z.boolean().optional()
8216
8377
  });
8217
8378
  /**
8218
8379
  * Detect the signature of a broken Forgejo release: a `created_at` that maps to
@@ -8265,7 +8426,8 @@ async function createRelease(executor, conn, tag, options = {}) {
8265
8426
  const payload = {
8266
8427
  tag_name: tag,
8267
8428
  name: tag,
8268
- body: `Published ${tag}`
8429
+ body: `Published ${tag}`,
8430
+ prerelease: options.prerelease ?? false
8269
8431
  };
8270
8432
  if (options.target) payload["target_commitish"] = options.target;
8271
8433
  const res = await executor.fetch(url, {
@@ -8319,6 +8481,35 @@ async function deleteRelease(executor, conn, releaseId) {
8319
8481
  }));
8320
8482
  }
8321
8483
  /**
8484
+ * Flag an existing Forgejo release as a prerelease (`PATCH /repos/{repo}/releases/{id}`).
8485
+ *
8486
+ * {@link ensureRelease} uses this on the path where the release was already there, so a rerun over
8487
+ * a release an earlier unflagged run created can still keep the forge's Latest pointer off a
8488
+ * prerelease. It sets the flag and never clears it, which is why the body is fixed at
8489
+ * `{ prerelease: true }` and why the write is a fixed-field PATCH: `{ idempotent: true }`, the
8490
+ * convention every other fixed-field PATCH in this package passes (see `updateForgejoComment` in
8491
+ * `issue-comments.ts`), because a re-sent convergent PATCH is a no-op.
8492
+ *
8493
+ * The JSON body is {@link createRelease}'s shape (one `Authorization` header plus a content type),
8494
+ * but unlike {@link deleteRelease} against the same URL a 404 is **not** tolerated here: a row that
8495
+ * vanished mid-run means the flag was not applied, and the caller must know.
8496
+ */
8497
+ async function markReleasePrerelease(executor, conn, releaseId, tag) {
8498
+ const url = `${conn.serverUrl}/api/v1/repos/${conn.repository}/releases/${String(releaseId)}`;
8499
+ const res = await executor.fetch(url, {
8500
+ method: "PATCH",
8501
+ headers: {
8502
+ Authorization: `token ${conn.token}`,
8503
+ "Content-Type": "application/json"
8504
+ },
8505
+ body: JSON.stringify({ prerelease: true })
8506
+ }, { idempotent: true });
8507
+ if (!res.ok) throw new TransientError(await forgejoErrorMessage(res, {
8508
+ verb: "edit release",
8509
+ resource: tag
8510
+ }));
8511
+ }
8512
+ /**
8322
8513
  * Ensure a Forgejo release exists for a tag **with a real created date**.
8323
8514
  *
8324
8515
  * Forgejo stamps a pushed annotated tag's date *asynchronously*; a release
@@ -8337,13 +8528,19 @@ async function deleteRelease(executor, conn, releaseId) {
8337
8528
  * 4. If nothing yields a real date, leave a visible release row (rather than only
8338
8529
  * an invisible tag) and throw loudly.
8339
8530
  *
8340
- * Returns `"created" | "exists" | "race" | "repaired"`.
8531
+ * Returns `{ status, release }` -- see {@link EnsureReleaseOutcome}.
8341
8532
  */
8342
8533
  async function ensureRelease(executor, conn, tag, options = {}) {
8343
8534
  const attempts = options.attempts ?? SETTLE_ATTEMPTS;
8344
8535
  const baseDelayMs = options.baseDelayMs ?? SETTLE_BASE_DELAY_MS;
8345
8536
  const existing = await getReleaseByTag(executor, conn, tag);
8346
- if (existing && !isEpochReleaseDate(existing.created_at)) return "exists";
8537
+ if (existing && !isEpochReleaseDate(existing.created_at)) {
8538
+ if (options.prerelease === true && existing.prerelease === false) await markReleasePrerelease(executor, conn, existing.id, tag);
8539
+ return {
8540
+ status: "exists",
8541
+ release: releaseRef(existing)
8542
+ };
8543
+ }
8347
8544
  let current = existing;
8348
8545
  let repairing = false;
8349
8546
  for (let attempt = 1; attempt <= attempts; attempt++) {
@@ -8354,10 +8551,13 @@ async function ensureRelease(executor, conn, tag, options = {}) {
8354
8551
  await settleBackoff(attempt, baseDelayMs);
8355
8552
  }
8356
8553
  try {
8357
- await createRelease(executor, conn, tag);
8554
+ await createRelease(executor, conn, tag, { prerelease: options.prerelease });
8358
8555
  } catch (error) {
8359
8556
  const raced = await getReleaseByTag(executor, conn, tag);
8360
- if (raced && !isEpochReleaseDate(raced.created_at)) return "race";
8557
+ if (raced && !isEpochReleaseDate(raced.created_at)) return {
8558
+ status: "race",
8559
+ release: releaseRef(raced)
8560
+ };
8361
8561
  current = raced;
8362
8562
  if (attempt >= attempts) break;
8363
8563
  log.warn(`Release creation attempt ${String(attempt)}/${String(attempts)} failed: ${describeError(error)}`);
@@ -8365,34 +8565,56 @@ async function ensureRelease(executor, conn, tag, options = {}) {
8365
8565
  continue;
8366
8566
  }
8367
8567
  current = await getReleaseByTag(executor, conn, tag);
8368
- if (current && !isEpochReleaseDate(current.created_at)) return repairing ? "repaired" : "created";
8568
+ if (current && !isEpochReleaseDate(current.created_at)) return {
8569
+ status: repairing ? "repaired" : "created",
8570
+ release: releaseRef(current)
8571
+ };
8369
8572
  if (attempt < attempts) log.warn(`Release for ${tag} came back epoch-dated (attempt ${String(attempt)}/${String(attempts)}) -- waiting for Forgejo to settle the tag's date before retrying`);
8370
8573
  }
8371
- if (options.target && await recreateTagViaApi(executor, conn, tag, {
8574
+ const recreated = options.target ? await recreateTagViaApi(executor, conn, tag, {
8372
8575
  target: options.target,
8576
+ prerelease: options.prerelease,
8373
8577
  current,
8374
8578
  attempts,
8375
8579
  baseDelayMs
8376
- })) return "repaired";
8580
+ }) : null;
8581
+ if (recreated) return {
8582
+ status: "repaired",
8583
+ release: releaseRef(recreated)
8584
+ };
8377
8585
  throw new TransientError(`Release for ${tag} never got a real (non-epoch) created date after ${String(attempts)} attempts. This is an intermittent Forgejo bug: a pushed tag's date settles asynchronously and a racing release creation freezes it at the Unix epoch (1969/1970). ` + (options.target ? `Recreating the tag via the API did not help -- investigate the server or upgrade Forgejo.` : `Pass the release tag's target commit so the tag can be recreated, or retry the release.`));
8378
8586
  }
8379
8587
  async function recreateTagViaApi(executor, conn, tag, opts) {
8380
- const { target, current, attempts, baseDelayMs } = opts;
8588
+ const { target, prerelease, current, attempts, baseDelayMs } = opts;
8381
8589
  log.warn(`Release for ${tag} stayed epoch-dated -- recreating the tag via the API so Forgejo stamps a real date (the tag becomes lightweight)`);
8382
8590
  if (current) await deleteRelease(executor, conn, current.id);
8383
8591
  await deleteTag(executor, conn, tag);
8384
8592
  for (let attempt = 1; attempt <= attempts; attempt++) {
8385
8593
  try {
8386
- await createRelease(executor, conn, tag, { target });
8594
+ await createRelease(executor, conn, tag, {
8595
+ target,
8596
+ prerelease
8597
+ });
8387
8598
  } catch (error) {
8388
8599
  if (attempt >= attempts) throw new TransientError(`Release for ${tag} was deleted to repair its epoch date, but recreating it from ${target} failed: ${describeError(error)}. The tag no longer exists on the server -- re-push it (or re-run the release) to restore it.`);
8389
8600
  await settleBackoff(attempt, baseDelayMs);
8390
8601
  continue;
8391
8602
  }
8392
8603
  const recreated = await getReleaseByTag(executor, conn, tag);
8393
- return Boolean(recreated && !isEpochReleaseDate(recreated.created_at));
8604
+ if (recreated && !isEpochReleaseDate(recreated.created_at)) return recreated;
8605
+ return null;
8394
8606
  }
8395
- return false;
8607
+ return null;
8608
+ }
8609
+ /**
8610
+ * The `{ id, url }` projection of a release row carried back to a caller. A server that omits
8611
+ * `html_url` yields `url: null` -- the id is still the row's identity.
8612
+ */
8613
+ function releaseRef(release) {
8614
+ return {
8615
+ id: release.id,
8616
+ url: release.html_url ?? null
8617
+ };
8396
8618
  }
8397
8619
  /** Exponential backoff (capped) between patient-repair attempts; a non-positive base is a no-op. */
8398
8620
  async function settleBackoff(attempt, baseDelayMs) {
@@ -9182,6 +9404,16 @@ async function generateReleaseCi(ctx) {
9182
9404
  };
9183
9405
  }
9184
9406
  //#endregion
9407
+ //#region src/release/version-branch.ts
9408
+ /**
9409
+ * The branch the changesets release flow writes its version commit to, and so the head branch of
9410
+ * the version PR. `bst release:changesets` force-pushes it on Forgejo; on GitHub it is
9411
+ * `changesets/action@v1`'s default, `changeset-release/<base>`. Not configurable. The CI generator
9412
+ * reads it too, to skip the check job on that PR, which is why it lives at `release/`'s root
9413
+ * rather than in `publish/`.
9414
+ */
9415
+ const VERSION_PR_BRANCH = "changeset-release/main";
9416
+ //#endregion
9185
9417
  //#region src/generators/ci.ts
9186
9418
  function coverageHistorySteps() {
9187
9419
  return [
@@ -9381,6 +9613,8 @@ async function generateCi(ctx) {
9381
9613
  ...emitCoverageSteps || emitHealthHistory ? IS_MAIN_JOB_ENV : {},
9382
9614
  ...npmAuth.jobEnv
9383
9615
  };
9616
+ const checksSlice = parseToolingConfigSlice(ctx.read(".tooling.json"), z.object({ checks: ChecksConfigSchema.optional() }), { block: "checks" });
9617
+ const skipOnVersionPr = isChangesets && checksSlice?.checks?.runOnVersionPr !== true;
9384
9618
  const content = buildWorkflowYaml({
9385
9619
  ci: ctx.config.ci,
9386
9620
  name: "CI",
@@ -9395,6 +9629,7 @@ async function generateCi(ctx) {
9395
9629
  enableEmailNotifications: isForgejo,
9396
9630
  ...isChangesets && { concurrency: ciConcurrency(ctx.config.ci) },
9397
9631
  jobName: "check",
9632
+ ...skipOnVersionPr && { jobIf: `github.head_ref != 'changeset-release/main'` },
9398
9633
  ...Object.keys(checkJobEnv).length > 0 && { jobEnv: checkJobEnv },
9399
9634
  ...jobOutputs && { jobOutputs },
9400
9635
  steps,
@@ -9928,6 +10163,8 @@ async function generatePnpmWorkspace(ctx) {
9928
10163
  //#region src/generators/claude-hooks.ts
9929
10164
  const HOOK_PATH = ".claude/hooks/bst-hook.mjs";
9930
10165
  const GUIDE_PATH = ".claude/hooks/bst-hook-guide.md";
10166
+ const SETTINGS_PATH$1 = ".claude/hooks/bst-hook.settings.json";
10167
+ const LegacyAllowKeySchema = z.object({ allow: z.array(z.unknown()).optional() });
9931
10168
  function writeIfChanged(ctx, filePath, content, label) {
9932
10169
  const existing = ctx.read(filePath);
9933
10170
  if (existing === content) return {
@@ -9942,8 +10179,34 @@ function writeIfChanged(ctx, filePath, content, label) {
9942
10179
  description: existing ? `Updated ${label}` : `Generated ${label}`
9943
10180
  };
9944
10181
  }
10182
+ /**
10183
+ * Warn about a leftover `allow` array in the settings file, without touching it. The file is
10184
+ * user-owned (it also carries `protectedBranches`, which is still read), so this only reports --
10185
+ * it never rewrites or strips the stale key.
10186
+ */
10187
+ function checkLegacyAllowKey(ctx) {
10188
+ const raw = ctx.read(SETTINGS_PATH$1);
10189
+ if (raw === void 0) return void 0;
10190
+ let parsed;
10191
+ try {
10192
+ parsed = JSON.parse(raw);
10193
+ } catch (_error) {
10194
+ return;
10195
+ }
10196
+ const result = LegacyAllowKeySchema.safeParse(parsed);
10197
+ if (!result.success || !result.data.allow || result.data.allow.length === 0) return void 0;
10198
+ log$1.warn(`${SETTINGS_PATH$1}: the "allow" key is no longer read (there is no allow tier any more). It is safe to remove.`);
10199
+ return {
10200
+ filePath: SETTINGS_PATH$1,
10201
+ action: "skipped",
10202
+ description: "Legacy allow key ignored; left in place"
10203
+ };
10204
+ }
9945
10205
  async function generateClaudeHooks(ctx) {
9946
- 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")];
10206
+ const results = [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\";\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}\n/**\n* Write a deny decision to stdout as JSON, with `reason` emitted as\n* `permissionDecisionReason` when present.\n*/\nfunction writeDeny(reason) {\n const decision = { hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n permissionDecision: \"deny\",\n ...reason ? { permissionDecisionReason: reason } : {}\n } };\n process.stdout.write(JSON.stringify(decision));\n}\n/**\n* Write a non-blocking advisory to stdout as `additionalContext`, with no\n* `permissionDecision` at all. Claude Code still shows a PreToolUse hook's\n* `additionalContext` to the model when the output carries no permission\n* decision (checked by hand against a live session), so a `none` result can\n* still carry advice.\n*/\nfunction writeAdvice(advice) {\n const decision = { hookSpecificOutput: {\n hookEventName: \"PreToolUse\",\n additionalContext: advice\n } };\n process.stdout.write(JSON.stringify(decision));\n}\n\n//#endregion\n//#region src/lib/audit.ts\nconst MAX_LOG_SIZE = 1048576;\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/**\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 (a `none`\n* result just defers to Claude Code's own classifier).\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* 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* - Newlines in raw segment (injection via line splitting)\n*\n* Redirect targets (`>`, `>>`, `<`) are deliberately not checked here any\n* more -- with no allow tier, a bare redirect is no longer a reason to\n* withhold a decision on its own, so `evaluateSegment` judges a redirect's\n* *target* itself (the node-modules-write and env-file-write deny rules)\n* instead of asking this guard to flag the operator generically. See\n* `evaluateSegment`'s own comment for why that check has to run ahead of this\n* one.\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 `none` (Claude\n* Code's own classifier judges it from there) rather than risking an\n* unresolved expansion silently being denied or not 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.** `git push --forc[e] origin feat/x` (also `--forc?`, `--for*`)\n* expands to a real `--force` argv, yet this returns `safe` and the `git-force-push` rule\n* never sees a `--force` token -- but *only when a file literally named `--force` already\n* exists in the working directory*. Creating that file is a separate command, so it is a\n* two-step, precondition-gated bypass: strictly weaker than the unconditional `$`/brace\n* bypasses that were fixed. The same class defeats a command-name deny rule too (e.g.\n* `s[u]do`), again only with a pre-existing matching file. Such a command gets no decision\n* from the hook, and auto mode's classifier judges it.\n*\n* *Why not fixed here:* this guard can only withhold a decision. Routing every token with an\n* unquoted glob metacharacter (`*`, `?`, `[`) to `none` would add no deny; it would hide the\n* deny rules from nearly every globbing command an agent runs (`ls *.md`, `cp dist/* ...`).\n* Closing the gap takes a deny rule instead -- the candidate is to deny a glob metachar\n* **only in a token that also begins with `-`** (a flag position, which is where the\n* reconstruction lives), leaving bare-argument globs like `*.md` alone.\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*/\nfunction checkInjection(tokens, rawSegment) {\n if (/[\\n\\r]/.test(rawSegment)) return {\n safe: false,\n reason: \"unquoted newline in command\"\n };\n for (const token of tokens) {\n if (token === \"`\") return {\n safe: false,\n reason: \"backtick command substitution\"\n };\n if (token.includes(\"`\")) return {\n safe: false,\n reason: \"backtick command substitution\"\n };\n if (token.includes(\"$\")) return {\n safe: false,\n reason: \"unresolved dollar-expansion\"\n };\n if (hasBraceExpansion(token)) return {\n safe: false,\n reason: \"brace expansion\"\n };\n if (token.includes(\"<(\")) return {\n safe: false,\n reason: \"process substitution\"\n };\n if (token.includes(\">(\")) return {\n safe: false,\n reason: \"process substitution\"\n };\n }\n return { safe: true };\n}\n\n//#endregion\n//#region src/lib/env-file.ts\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//#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 means \"unknown\", and an unknown branch is not a protected one: no\n* rule denies because the branch could not be read.\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 movesProtectedRef. 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* The upstream of `branch` in short form (`origin/main`), or null when it has\n* none, cwd is not a repository, or git fails for any other reason.\n*/\nfunction getUpstream(cwd, branch) {\n try {\n return execFileSync(\"git\", [\n \"rev-parse\",\n \"--abbrev-ref\",\n `${branch}@{upstream}`\n ], {\n cwd,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ]\n }).toString().trim() || null;\n } catch {\n return null;\n }\n}\n/**\n* The commit id `rev` names, or null when it names no commit (a path, a\n* typo) or git fails. `rev` must not start with `-`: callers pass only\n* positional arguments.\n*/\nfunction resolveCommit(cwd, rev) {\n try {\n return execFileSync(\"git\", [\n \"rev-parse\",\n \"--verify\",\n \"--quiet\",\n `${rev}^{commit}`\n ], {\n cwd,\n stdio: [\n \"ignore\",\n \"pipe\",\n \"ignore\"\n ]\n }).toString().trim() || null;\n } catch {\n return null;\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.\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 fetch: /* @__PURE__ */ new Set([\"o\", \"j\"])\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 none (reads) or a false containment\n* result for a path-scoped write rule -- git-dir-write, node-modules-write,\n* hooks-folder-write (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`. A `.git` outside `cwd` is not this function's\n* concern and reaches `none` -- there is no longer a blanket outside-cwd\n* deny to catch it either.\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\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}\nconst WORKTREE_ADVICE = \"You're creating a feature branch in the primary worktree. An isolated worktree keeps this work independent of other windows, and with no commits on this branch yet, converting is cheap -- `git worktree add ../<dir> -b <branch>` (a sibling outside the repo, with its own dependency install). Claude Code keeps this session inside its own working tree, so the work continues in a new session opened in the sibling worktree. If you cannot open a session there, stay in the primary checkout on this branch and check `git branch --show-current` before each commit.\";\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/**\n* Non-blocking advisory for a `git` command that reaches the end of rule\n* matching with no decision either way. There is no allow tier any more to\n* carry it, so `evaluateSegment` calls this directly and attaches the result\n* to the none result it returns for a `git` command, rather than routing it\n* through a `Rule`'s own `when`/advise pair the way the old `git-safe-write`\n* allow rule did.\n*/\nfunction worktreeAdvice(tokens, ctx) {\n return isGitBranchCreation(tokens) && isPrimaryWorktree(ctx.cwd) ? WORKTREE_ADVICE : void 0;\n}\n/** Force-create letter: `C` on switch, `B` on checkout and `worktree add`. */\nconst GIT_FORCE_CREATE_LETTER = {\n switch: \"C\",\n checkout: \"B\"\n};\n/**\n* The long force-create spelling (switch only). Every unambiguous prefix down\n* to `--force-` (the shortest that is not the distinct `--force` option)\n* matches, because git's parse-options accepts one and a prefix missed here\n* would let a force-create onto a protected name through.\n*/\nconst GIT_FORCE_CREATE_LONG = {\n flag: \"--force-create\",\n shortest: \"--force-\"\n};\n/**\n* Every branch name a force-create spelling in `after` (the tokens following\n* the subcommand -- and past `add`, for `worktree add`) targets. Usually zero\n* or one, but git's last-occurrence-wins on a repeated creation flag is not\n* modeled: every occurrence contributes its target, so a repeated flag is\n* handled conservatively rather than resolving which one wins.\n*\n* Grammar, reusing `splitShortOptionCluster`: a cluster whose letters include\n* the force letter contributes its attached value when non-empty, else the\n* next argv token; the long spelling contributes the next argv token, or the\n* 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.\n*/\nfunction gitForceCreateTargets(after, letterSub) {\n const letter = GIT_FORCE_CREATE_LETTER[letterSub];\n const longFlag = GIT_FORCE_CREATE_LONG;\n const targets = [];\n for (let i = 0; i < after.length; i++) {\n const t = after[i];\n if (t === void 0) continue;\n if (letterSub === \"switch\") {\n const [flag, ...rest] = t.split(\"=\");\n if (flag !== void 0 && flag.length >= longFlag.shortest.length && longFlag.flag.startsWith(flag)) {\n const target = rest.length > 0 ? rest.join(\"=\") : after[i + 1];\n if (target !== void 0) targets.push(target);\n continue;\n }\n }\n const cluster = splitShortOptionCluster(t, letterSub);\n if (cluster === void 0 || !cluster.letters.includes(letter)) continue;\n if (cluster.attachedValue !== void 0 && cluster.attachedValue !== \"\") {\n targets.push(cluster.attachedValue);\n continue;\n }\n const next = after[i + 1];\n if (next !== void 0) targets.push(next);\n }\n return targets;\n}\n/**\n* Every branch name a force-create spelling targets in a full `git ...`\n* command: `switch -C`/`--force-create`, `checkout -B`, or `worktree add -B`.\n* Any other subcommand -- including `worktree list`/`remove`/`move` -- always\n* resolves no target. `worktree add` reuses checkout's `-B` grammar.\n*/\nfunction gitCommandForceCreateTargets(tokens) {\n const sub = getGitSubcommand(tokens);\n if (sub === \"switch\" || sub === \"checkout\") return gitForceCreateTargets(tokens.slice(tokens.indexOf(sub) + 1), sub);\n if (sub === \"worktree\") {\n const worktreeIdx = tokens.indexOf(\"worktree\");\n if (tokens[worktreeIdx + 1] !== \"add\") return [];\n return gitForceCreateTargets(tokens.slice(worktreeIdx + 2), \"checkout\");\n }\n return [];\n}\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 it,\n* so this is a claim about the TOKENS, not the current branch: it never calls\n* getCurrentBranch and is valid even outside a repository, where main/master\n* are protected unconditionally.\n*/\nfunction isGitProtectedBranchForceCreate(tokens, ctx) {\n const targets = gitCommandForceCreateTargets(tokens);\n if (targets.length === 0) return false;\n const protectedSet = getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []);\n return targets.some((t) => protectedSet.has(t));\n}\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 the\n* force-create check above. It carries no pre-subcommand flag guard, so\n* `git -c core.hooksPath=/tmp/evil branch -D main` still denies.\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 gets no decision.\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* 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) and any\n* `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* 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. `-u`/`--set-upstream-to`\n* 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/**\n* Split the tokens after `git branch` into its flags and positionals, for\n* `isGitProtectedBranchRefWrite`. A token not starting with `-` is a\n* positional. `--` is recorded as a flag with no name. 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* Long `git fetch` flags whose value is the next token when spelled without\n* `=`. `--recurse-submodules` is deliberately absent: it takes its optional\n* value only with `=`, so `git fetch --recurse-submodules origin` reads\n* `origin` as the remote, not as the flag's value. The three names that are on\n* no allowlist (`--upload-pack`, `--submodule-prefix`,\n* `--recurse-submodules-default`) are listed here so a swallowed value cannot\n* be misread as a refspec.\n*/\nconst GIT_FETCH_VALUE_FLAG_NAMES = /* @__PURE__ */ new Set([\n \"depth\",\n \"deepen\",\n \"shallow-since\",\n \"shallow-exclude\",\n \"negotiation-tip\",\n \"filter\",\n \"jobs\",\n \"server-option\",\n \"upload-pack\",\n \"refmap\",\n \"submodule-prefix\",\n \"recurse-submodules-default\"\n]);\n/**\n* Split the tokens after `git fetch` into flags, positionals and `--refmap`\n* values, for `isGitProtectedBranchFetchWrite`, in the shape of\n* `splitGitBranchArgs` (whose interfaces are deliberately not reused: the two\n* splitters have different value-flag tables and must stay independent). A\n* token not starting with `-` is a positional. `--` is recorded as a flag with\n* no name and no letters; later tokens are not treated specially. A `--flag` token is recorded\n* by name and consumes the following token when it has no `=` and its name is\n* in `GIT_FETCH_VALUE_FLAG_NAMES`. A short token is decomposed with\n* `splitShortOptionCluster(t, \"fetch\")`, recording its letters and consuming\n* the following token when its attached value is the empty string (a trailing\n* `-j` or `-o`).\n*/\nfunction splitGitFetchArgs(after) {\n const flags = [];\n const positionals = [];\n const refmaps = [];\n for (let i = 0; i < after.length; i++) {\n const t = after[i];\n if (t === void 0) continue;\n if (!t.startsWith(\"-\")) {\n positionals.push(t);\n continue;\n }\n if (t === \"--\") {\n flags.push({ raw: t });\n continue;\n }\n if (t.startsWith(\"--\")) {\n const eq = t.indexOf(\"=\");\n const name = eq > 0 ? t.slice(2, eq) : t.slice(2);\n flags.push({\n raw: t,\n name\n });\n if (name === \"refmap\") {\n if (eq > 0) refmaps.push(t.slice(eq + 1));\n else if (after[i + 1] !== void 0) refmaps.push(after[i + 1]);\n }\n if (eq < 0 && GIT_FETCH_VALUE_FLAG_NAMES.has(name)) i++;\n continue;\n }\n const cluster = splitShortOptionCluster(t, \"fetch\");\n if (cluster === void 0) {\n flags.push({ raw: t });\n continue;\n }\n flags.push({\n raw: t,\n letters: cluster.letters\n });\n if (cluster.attachedValue === \"\") i++;\n }\n return {\n flags,\n positionals,\n refmaps\n };\n}\n/**\n* Classify one fetch refspec's destination. Unlike a push's use of\n* `refspecDestination`, a token with **no colon is not a destination at all**:\n* a source-only fetch refspec writes `FETCH_HEAD` plus whatever the remote's\n* refmap says, never a named local branch, so this function must not consult\n* the shared helper first -- for push, a bare `main` means \"the same name on\n* the remote\" and the helper hands it back as a destination.\n*\n* With a colon the destination goes through `refspecDestination`, which already\n* strips `refs/heads/` and leaves every other `refs/` prefix intact. An empty\n* destination (`main:`) writes nothing. `refs/remotes/` is a remote-tracking\n* cache; any other `refs/` namespace (tags included) is `other`; anything else\n* is a branch name, which git resolves to `refs/heads/<name>` when no such ref\n* exists. Note that `:main` reaches `branch` with name `main` -- the fetch\n* reading, where an empty source means HEAD, not push's delete reading.\n*/\nfunction fetchRefspecTarget(spec) {\n const body = spec.startsWith(\"+\") ? spec.slice(1) : spec;\n if (body.startsWith(\"^\")) return { kind: \"none\" };\n if (!body.includes(\":\")) return { kind: \"none\" };\n const destination = refspecDestination(body);\n if (destination === \"\") return { kind: \"none\" };\n if (destination.startsWith(\"refs/remotes/\")) return { kind: \"tracking\" };\n if (destination.startsWith(\"refs/\")) return { kind: \"other\" };\n return {\n kind: \"branch\",\n name: destination\n };\n}\n/**\n* Escape a glob for regex use, then turn each escaped `*` into \"any run of\n* characters\". Escaping first is what keeps a name containing regex syntax\n* (`feat/x+`, say) from compiling into a pattern that matches something the\n* name does not.\n*/\nfunction globToAnchoredRegex(glob) {\n const escaped = glob.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n return new RegExp(`^${escaped.replace(/\\\\\\*/g, \".*\")}$`);\n}\n/**\n* True when a refspec destination names a protected branch, or is a glob that\n* covers one (`refs/heads/*:refs/heads/*` covers `main`,\n* `refs/heads/*:refs/heads/copy/*` does not).\n*/\nfunction isProtectedFetchBranch(name, protectedSet) {\n if (protectedSet.has(name)) return true;\n if (!name.includes(\"*\")) return false;\n const pattern = globToAnchoredRegex(name);\n for (const member of protectedSet) if (pattern.test(member)) return true;\n return false;\n}\n/**\n* True when a `git fetch` writes a protected local branch: some refspec lands\n* on a protected name (or a glob covering one), forced or not -- an unforced\n* write still fast-forwards the branch. A claim about the TOKENS --\n* it never calls `getCurrentBranch` and never asks whether the ref exists, so it\n* is valid outside a repository, where `main`/`master` are protected\n* unconditionally. Like `isGitProtectedBranchRefWrite`, it carries no\n* pre-subcommand flag guard, so `git -c core.hooksPath=/tmp/evil fetch .\n* +HEAD:main` still denies.\n*\n* Two deliberate widenings, both in the deny direction. **Every** positional\n* is scanned, the remote slot included: if the value-consuming flag list were\n* ever over-inclusive, a swallowed remote would shift the sole refspec into the\n* first slot, and the deny must not be blind there -- the cost is a deny on an\n* scp-style URL whose path is literally a protected name. And every\n* `--refmap` value is classified, because measured,\n* `--refmap=+refs/heads/*:refs/heads/*` beside a source-only positional\n* force-writes `main`.\n*\n* The accepted residual: `--stdin` carries its refspecs on a stream, so a\n* `HEAD:main` arriving that way cannot be seen and gets no decision.\n*/\nfunction isGitProtectedBranchFetchWrite(tokens, ctx) {\n const sub = getGitSubcommand(tokens);\n if (sub !== \"fetch\") return false;\n const { positionals, refmaps } = splitGitFetchArgs(tokens.slice(tokens.indexOf(sub) + 1));\n const protectedSet = getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []);\n return [...positionals, ...refmaps].some((spec) => {\n const target = fetchRefspecTarget(spec);\n if (target.kind !== \"branch\" || target.name === void 0) return false;\n return isProtectedFetchBranch(target.name, protectedSet);\n });\n}\n/**\n* True when `git update-ref` writes or deletes (`-d`) a protected branch: its\n* ref positional, with any `refs/heads/` prefix stripped, is protected. `-m`\n* takes the next token as its reason. `--stdin` carries its commands on a\n* stream, so a protected write arriving that way cannot be seen and gets no\n* decision.\n*/\nfunction isGitProtectedUpdateRef(tokens, ctx) {\n const after = tokens.slice(tokens.indexOf(\"update-ref\") + 1);\n let ref;\n for (let i = 0; i < after.length; i++) {\n const t = after[i];\n if (t === \"-m\") {\n i++;\n continue;\n }\n if (t.startsWith(\"-\")) continue;\n ref = t;\n break;\n }\n if (ref === void 0) return false;\n return getProtectedBranches(ctx.cwd, ctx.repoProtectedBranches ?? []).has(ref.replace(/^refs\\/heads\\//, \"\"));\n}\n/**\n* The directory git runs in: `ctx.cwd` (already the effective directory after\n* `cd <dir> &&` propagation) with every global `-C <dir>` applied in order, the\n* way git composes them. `--git-dir`/`--work-tree` are not modelled: a command\n* carrying one reads its branch from `ctx.cwd`, an accepted residual.\n*/\nfunction gitEffectiveCwd(tokens, cwd) {\n let dir = cwd;\n const args = tokens.slice(1);\n for (let i = 0; i < args.length; i++) {\n const t = args[i];\n if (t === \"-C\") {\n const next = args[i + 1];\n if (next !== void 0) dir = path.resolve(dir, next);\n i++;\n continue;\n }\n if (GIT_FLAGS_WITH_ARG.has(t)) {\n i++;\n continue;\n }\n if (!t.startsWith(\"-\")) break;\n }\n return dir;\n}\n/** Subcommands that move the checked-out branch's ref when they succeed. */\nconst GIT_CHECKED_OUT_WRITES = /* @__PURE__ */ new Set([\n \"commit\",\n \"merge\",\n \"rebase\",\n \"cherry-pick\",\n \"revert\",\n \"am\",\n \"pull\"\n]);\n/**\n* Split the tokens after `sub` into flag-shaped and positional tokens. A flag\n* in `valueFlags` also consumes the next token as its value.\n*/\nfunction subcommandArgs(tokens, sub, valueFlags = /* @__PURE__ */ new Set()) {\n const after = tokens.slice(tokens.indexOf(sub) + 1);\n const flags = [];\n const positionals = [];\n for (let i = 0; i < after.length; i++) {\n const t = after[i];\n if (!t.startsWith(\"-\")) {\n positionals.push(t);\n continue;\n }\n flags.push(t);\n if (valueFlags.has(t)) i++;\n }\n return {\n flags,\n positionals\n };\n}\n/**\n* `git push` flags whose value is the next token when spelled without `=`.\n* Left unskipped, the value reads as the remote and shifts the real remote\n* into the refspec slot: `git push -o ci.skip origin` would look like a push\n* of the refspec `origin` instead of a push with no refspec.\n*/\nconst GIT_PUSH_VALUE_FLAGS = /* @__PURE__ */ new Set([\n \"-o\",\n \"--push-option\",\n \"--repo\",\n \"--receive-pack\",\n \"--exec\"\n]);\n/**\n* True for `merge --ff-only <ref>` where `<ref>` is the branch's own upstream,\n* spelled out (`origin/main`) or as `@{u}`/`@{upstream}`. Fast-forwarding\n* `main` from its upstream is how a human keeps it current.\n*/\nfunction isMergeFastForwardFromUpstream(tokens, upstream) {\n const { flags, positionals } = subcommandArgs(tokens, \"merge\");\n if (!flags.includes(\"--ff-only\") || positionals.length !== 1) return false;\n const ref = positionals[0];\n return ref === \"@{u}\" || ref === \"@{upstream}\" || upstream !== null && ref === upstream;\n}\n/** `--rebase=<value>` spellings git reads as false, so the pull does not rebase. */\nconst GIT_PULL_NO_REBASE = /* @__PURE__ */ new Set([\n \"--rebase=false\",\n \"--rebase=no\",\n \"--rebase=off\",\n \"--rebase=0\"\n]);\n/**\n* True for a `pull` that can only fast-forward or merge the branch's own\n* upstream: no rebase or `--no-ff`, and either no positional or exactly\n* `<remote> <branch>` naming the upstream. `--rebase` is matched as a prefix\n* (`--rebase=merges`), and `-r` inside any short cluster, so neither spelling\n* slips through; `--rebase=false` and git's other false spellings turn\n* rebasing off rather than on.\n*/\nfunction isPullFromUpstream(tokens, upstream) {\n const { flags, positionals } = subcommandArgs(tokens, \"pull\");\n if (flags.some((t) => t.startsWith(\"--rebase\") && !GIT_PULL_NO_REBASE.has(t) || t === \"--no-ff\" || !t.startsWith(\"--\") && t.slice(1).includes(\"r\"))) return false;\n if (positionals.length === 0) return true;\n if (positionals.length !== 2 || upstream === null) return false;\n const [remote, branch] = positionals;\n return `${remote}/${branch.replace(/^refs\\/heads\\//, \"\")}` === upstream;\n}\n/**\n* True for a `reset` that moves the ref: no `--`, exactly one positional, and\n* that positional resolves to a commit other than HEAD's. `git reset`,\n* `git reset <path>` and `git reset --hard` unstage or discard work but leave\n* the ref where it is, so they are false.\n*/\nfunction isRefMovingReset(tokens, cwd) {\n if (tokens.slice(tokens.indexOf(\"reset\") + 1).includes(\"--\")) return false;\n const { positionals } = subcommandArgs(tokens, \"reset\");\n if (positionals.length !== 1) return false;\n const target = resolveCommit(cwd, positionals[0]);\n if (target === null) return false;\n return target !== resolveCommit(cwd, \"HEAD\");\n}\n/**\n* The branch names a `git push` writes on the remote. A refspec's destination\n* comes from `refspecDestination`; `HEAD` and `@` stand for the checked-out\n* branch. The first positional is the remote. Returns null when the command\n* names no refspec, so the destination comes from push config.\n*/\nfunction pushDestinations(tokens, current) {\n const { positionals } = subcommandArgs(tokens, \"push\", GIT_PUSH_VALUE_FLAGS);\n const refspecs = positionals.slice(1);\n if (refspecs.length === 0) return null;\n const names = [];\n for (const spec of refspecs) {\n const dst = refspecDestination(spec);\n if (dst === \"HEAD\" || dst === \"@\") {\n if (current !== null) names.push(current);\n continue;\n }\n names.push(dst);\n }\n return names;\n}\n/** `git push` flags that push every local branch rather than named refspecs. */\nconst GIT_PUSH_EVERY_BRANCH = /* @__PURE__ */ new Set([\n \"--all\",\n \"--branches\",\n \"--mirror\"\n]);\n/**\n* True for a checked-out write that cannot move the ref forward: `--abort`\n* and `--quit` leave an operation in progress (a plain `git pull` can leave a\n* conflicted merge that has to be abandoned), and `commit --dry-run` only\n* reports what would be committed. `--continue` and `--skip` are not here,\n* because they do commit.\n*/\nfunction leavesRefInPlace(tokens, sub) {\n const { flags } = subcommandArgs(tokens, sub);\n if (flags.includes(\"--abort\") || flags.includes(\"--quit\")) return true;\n return sub === \"commit\" && flags.includes(\"--dry-run\");\n}\n/**\n* True when a git command would move a protected branch's ref:\n*\n* - With a protected branch checked out (in the effective directory): a\n* subcommand in `GIT_CHECKED_OUT_WRITES`, except a fast-forward from the\n* branch's own upstream (`isMergeFastForwardFromUpstream`,\n* `isPullFromUpstream`) and a command that leaves the ref in place\n* (`leavesRefInPlace`); or a `reset` that moves the ref (`isRefMovingReset`).\n* - From any branch: a push of every branch (`--all`, `--branches`,\n* `--mirror`), a push whose refspec destination is protected, or a push\n* with no refspec while a protected branch is checked out, unless it pushes\n* only tags (`--tags`).\n* - From any branch: `switch -C`/`checkout -B`/`worktree add -B`, a\n* `git branch` delete/rename/force, a `fetch` refspec, or `update-ref`, onto\n* a protected name.\n*\n* An unknown branch -- a directory that does not exist yet, a detached HEAD,\n* git failing -- is not a protected one, so the checked-out cases are false\n* there. The subcommand is checked before any git process is spawned.\n*/\nfunction movesProtectedRef(tokens, ctx) {\n const sub = getGitSubcommand(tokens);\n if (sub === void 0) return false;\n if (sub === \"switch\" || sub === \"checkout\" || sub === \"worktree\") return isGitProtectedBranchForceCreate(tokens, ctx);\n if (sub === \"branch\") return isGitProtectedBranchRefWrite(tokens, ctx);\n if (sub === \"fetch\") return isGitProtectedBranchFetchWrite(tokens, ctx);\n if (sub === \"update-ref\") return isGitProtectedUpdateRef(tokens, ctx);\n if (sub !== \"push\" && sub !== \"reset\" && !GIT_CHECKED_OUT_WRITES.has(sub)) return false;\n const cwd = gitEffectiveCwd(tokens, ctx.cwd);\n const current = getCurrentBranch(cwd);\n const protectedSet = getProtectedBranches(cwd, ctx.repoProtectedBranches ?? []);\n const onProtected = current !== null && protectedSet.has(current);\n if (sub === \"push\") {\n const { flags } = subcommandArgs(tokens, \"push\", GIT_PUSH_VALUE_FLAGS);\n if (flags.some((f) => GIT_PUSH_EVERY_BRANCH.has(f))) return true;\n const destinations = pushDestinations(tokens, current);\n if (destinations === null) {\n if (flags.includes(\"--tags\")) return false;\n return onProtected;\n }\n return destinations.some((d) => protectedSet.has(d));\n }\n if (!onProtected) return false;\n if (leavesRefInPlace(tokens, sub)) return false;\n if (sub === \"reset\") return isRefMovingReset(tokens, cwd);\n if (sub === \"merge\" || sub === \"pull\") {\n const upstream = getUpstream(cwd, current);\n if (sub === \"merge\" && isMergeFastForwardFromUpstream(tokens, upstream)) return false;\n if (sub === \"pull\" && isPullFromUpstream(tokens, upstream)) return false;\n }\n return true;\n}\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]);\nconst NPM_READ_OPS = /* @__PURE__ */ new Set([\n \"view\",\n \"info\",\n \"show\"\n]);\n/** True for a read-only npm query (`view`/`info`/`show`) -- everything else denies with a redirect. */\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/** An output operator: `>` or `>>`, optionally after a file descriptor number or `&`. */\nconst OUTPUT_REDIRECT = /^(?:\\d*|&)>{1,2}$/;\nconst OUTPUT_REDIRECT_ATTACHED = /^(?:\\d*|&)>{1,2}(.+)$/;\n/**\n* An input operator: a single `<`, optionally after a file descriptor number.\n* `<<` and `<<<` feed inline text rather than a file, so they are not matched.\n*/\nconst INPUT_REDIRECT = /^\\d*<$/;\nconst INPUT_REDIRECT_ATTACHED = /^\\d*<([^<].*)$/;\n/** The direction of a redirect operator written apart from its target, or null. */\nfunction spacedRedirectDirection(token) {\n if (OUTPUT_REDIRECT.test(token)) return \"out\";\n if (INPUT_REDIRECT.test(token)) return \"in\";\n return null;\n}\n/**\n* Split a command's tokens into its file redirects and every other token, in\n* both the spaced (`> f`, `2> f`, `&> f`, `< f`) and attached (`>f`, `2>>f`,\n* `<f`) spellings. A trailing operator with no target is dropped.\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 target like\n* `&1` from `2>&1` names neither node_modules nor an env file.\n*/\nfunction splitRedirects(tokens) {\n const redirects = [];\n const rest = [];\n for (let i = 0; i < tokens.length; i++) {\n const t = tokens[i];\n const spaced = spacedRedirectDirection(t);\n if (spaced !== null) {\n const next = tokens[i + 1];\n if (next !== void 0) redirects.push({\n direction: spaced,\n target: next\n });\n i++;\n continue;\n }\n const out = OUTPUT_REDIRECT_ATTACHED.exec(t);\n if (out) {\n redirects.push({\n direction: \"out\",\n target: out[1]\n });\n continue;\n }\n const input = INPUT_REDIRECT_ATTACHED.exec(t);\n if (input) {\n redirects.push({\n direction: \"in\",\n target: input[1]\n });\n continue;\n }\n rest.push(t);\n }\n return {\n redirects,\n rest\n };\n}\n/** The files a command's redirects write to (`direction` \"out\") or read from (\"in\"). */\nfunction redirectTargets(tokens, direction) {\n return splitRedirects(tokens).redirects.filter((r) => r.direction === direction).map((r) => r.target);\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 = redirectTargets(tokens, \"out\");\n if (cmd === \"dd\") dests.push(...rest.filter((t) => t.startsWith(\"of=\")).map((t) => t.slice(3)));\n else if (cmd === \"sed\") {\n if (rest.some((t) => t === \"--in-place\" || /^-[a-zA-Z]*i/.test(t))) dests.push(...rest.filter((t) => !t.startsWith(\"-\")));\n } else if (ALL_POSITIONAL_DEST.has(cmd)) dests.push(...rest.filter((t) => !t.startsWith(\"-\")));\n else if (LAST_POSITIONAL_DEST.has(cmd)) {\n if (cmd === \"cp\" && rest.includes(\"--remove-destination\")) return dests;\n const targetDir = explicitTargetDirectory(rest);\n if (targetDir !== null) dests.push(targetDir);\n else {\n const last = rest.findLast((t) => !t.startsWith(\"-\"));\n if (last !== void 0) dests.push(last);\n }\n }\n return dests;\n}\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 `none` result 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 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}\n/**\n* Commands whose bare positional arguments are files to read verbatim -- no\n* argument among them is a search pattern the way grep's is.\n*/\nconst ENV_READ_FILE_COMMANDS = /* @__PURE__ */ new Set([\n \"cat\",\n \"head\",\n \"tail\",\n \"less\",\n \"more\",\n \"source\",\n \".\"\n]);\n/** grep-family commands: their first positional is normally a search pattern, not a file. */\nconst GREP_FAMILY_COMMANDS = /* @__PURE__ */ new Set([\n \"grep\",\n \"rg\",\n \"ag\"\n]);\n/**\n* The file operands of a grep-family (`grep`/`rg`/`ag`) invocation, given its\n* tokens after the command name with redirects already stripped.\n*\n* This walks the tokens instead of filtering positionals and asking \"was a\n* pattern flag present anywhere\" as a single yes/no, because the two pattern\n* flags disagree about what to do with their *own* value once one is found:\n*\n* - `-e`/`--regexp` (bare, or attached as `-eX`/`--regexp=X`) supply the\n* search pattern directly, so that value is a pattern, never a file, and is\n* dropped -- `grep -e process.env src/` and `grep -rn -e process.env src/`\n* must not deny just because the pattern happens to look like a filename,\n* the same false positive the no-flag case below exists to avoid.\n* - `-f`/`--file` (bare, or attached as `-fX`/`--file=X`) name a *file of\n* patterns*, which is a real file read, so that value is kept as an\n* operand -- `grep -f pats.txt .env.local` must still deny on `.env.local`.\n*\n* Absent either flag, the first bare positional is the search pattern and\n* every one after it is a file -- `grep -n \"process.env\" src/x.ts` is the\n* false positive that motivated all of this: the pattern string\n* \"process.env\" matches the `.env` filename pattern just as well as a real\n* path does, so it must never be checked as a file operand.\n*\n* A `--` ends option parsing (see `optionsEnded` below): every token after it\n* is a literal positional even when it looks like `-e` or `-f`, following the\n* same \"first one is the pattern, absent an earlier flag\" rule -- `grep --\n* -e .env` passes `-e` as the pattern and `.env` as the file, and `grep --\n* .env src/x.ts` passes `.env` itself as the pattern (not a file) and\n* `src/x.ts` as the file.\n*/\nfunction grepFileOperands(rest) {\n const files = [];\n let patternAccountedFor = false;\n let optionsEnded = false;\n for (let i = 0; i < rest.length; i++) {\n const t = rest[i];\n if (!optionsEnded && t === \"--\") {\n optionsEnded = true;\n continue;\n }\n if (!optionsEnded) {\n if (t === \"-f\" || t === \"--file\") {\n const value = rest[i + 1];\n if (value !== void 0) {\n files.push(value);\n i++;\n }\n patternAccountedFor = true;\n continue;\n }\n if (t.startsWith(\"--file=\")) {\n files.push(t.slice(7));\n patternAccountedFor = true;\n continue;\n }\n if (t.startsWith(\"-f\") && t !== \"-f\" && !t.startsWith(\"--\")) {\n files.push(t.slice(2));\n patternAccountedFor = true;\n continue;\n }\n if (t === \"-e\" || t === \"--regexp\") {\n i++;\n patternAccountedFor = true;\n continue;\n }\n if (t.startsWith(\"--regexp=\") || t.startsWith(\"-e\") && t !== \"-e\" && !t.startsWith(\"--\")) {\n patternAccountedFor = true;\n continue;\n }\n if (t.startsWith(\"-\")) continue;\n }\n if (!patternAccountedFor) {\n patternAccountedFor = true;\n continue;\n }\n files.push(t);\n }\n return files;\n}\n/**\n* Every file a command reads: each input redirect target, on any command,\n* plus the file operands of a `cat`/`head`/`tail`/`less`/`more`/`source`/`.`/\n* `grep`/`rg`/`ag` command. Backs the `env-file-read` rule. grep/rg/ag\n* delegate to `grepFileOperands` (above); every other command named here has\n* no search pattern to exclude, so every bare positional is a file operand.\n* Output redirects are left out: they write, and `env-file-write` owns them.\n*/\nfunction envFileOperands(tokens) {\n const cmd = path.basename(tokens[0] ?? \"\");\n const { redirects, rest } = splitRedirects(tokens.slice(1));\n const operands = redirects.filter((r) => r.direction === \"in\").map((r) => r.target);\n if (GREP_FAMILY_COMMANDS.has(cmd)) operands.push(...grepFileOperands(rest));\n else if (ENV_READ_FILE_COMMANDS.has(cmd)) operands.push(...rest.filter((t) => !t.startsWith(\"-\")));\n return operands;\n}\nconst denyRules = [\n {\n name: \"sudo\",\n match: /^sudo$/,\n action: \"deny\",\n reason: \"Don't use sudo. If you need elevated permissions, ask me to do it manually.\"\n },\n {\n name: \"git-force-push\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens) => isGitForcePush(tokens),\n reason: \"Use `git push --force-with-lease` instead of `--force`.\"\n },\n {\n name: \"git-protected-branch-write\",\n match: /^git$/,\n action: \"deny\",\n when: (tokens, ctx) => movesProtectedRef(tokens, ctx),\n reason: \"This would move a protected branch (main, master, the remote's default branch, or one listed in .claude/hooks/bst-hook.settings.json#protectedBranches). Work on a feature branch in a worktree: `git worktree add ../<repo>-<topic> -b feat/<topic>`, then commit and push there. To bring `main` up to date, use `git pull --ff-only`.\"\n },\n {\n name: \"chmod-777\",\n match: /^chmod$/,\n action: \"deny\",\n when: (tokens) => isChmod777(tokens),\n reason: \"Don't use 777 permissions. Use more restrictive permissions.\"\n },\n {\n name: \"node-modules-write\",\n match: /./,\n action: \"deny\",\n when: (tokens, ctx) => writesIntoNodeModules(tokens, ctx),\n reason: NODE_MODULES_WRITE_REASON\n },\n {\n name: \"env-file-read\",\n match: /./,\n action: \"deny\",\n when: (tokens) => envFileOperands(tokens).some(isEnvFile),\n reason: \"Don't read .env files. These contain secrets that should not appear in the conversation.\"\n },\n {\n name: \"env-file-write\",\n match: /./,\n action: \"deny\",\n when: (tokens) => redirectTargets(tokens, \"out\").some(isEnvFile),\n reason: \"Don't write to .env files. These contain secrets.\"\n },\n {\n name: \"npx-redirect\",\n match: /^npx$/,\n action: \"deny\",\n reason: \"This project uses pnpm. Use `pnpm dlx <pkg>` or `pnpm exec <cmd>` instead of npx.\"\n },\n {\n name: \"mise-self-update\",\n match: /^mise$/,\n action: \"deny\",\n when: (tokens) => tokens[1] === \"self-update\",\n reason: \"Don't self-update mise. The version is Renovate-managed; ask a human to bump the pin.\"\n }\n];\n\n//#endregion\n//#region src/lib/script-resolver.ts\nconst PackageJsonSchema = object({ scripts: record(string(), unknown()) });\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//#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/evaluate.ts\nconst MAX_EVAL_DEPTH = 5;\n/**\n* The body of a command that is wholly one subshell -- its first `(` closes at\n* its last character -- or null. `(a) && (b)` is not one subshell: its first\n* `(` closes early. Parentheses inside single or double quotes, or escaped\n* with a backslash, are not counted.\n*/\nfunction subshellBody(command) {\n if (!command.startsWith(\"(\") || !command.endsWith(\")\")) return null;\n let depth = 0;\n let quote = null;\n for (let i = 0; i < command.length; i++) {\n const ch = command[i];\n if (quote !== null) {\n if (ch === quote) quote = null;\n else if (ch === \"\\\\\" && quote === \"\\\"\") i++;\n continue;\n }\n if (ch === \"\\\\\") i++;\n else if (ch === \"'\" || ch === \"\\\"\") quote = ch;\n else if (ch === \"(\") depth++;\n else if (ch === \")\") {\n depth--;\n if (depth === 0) return i === command.length - 1 ? command.slice(1, -1) : null;\n }\n }\n return null;\n}\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: \"none\",\n rule: null,\n reason: \"max evaluation depth exceeded\"\n };\n const trimmed = command.trim();\n if (!trimmed) return {\n action: \"none\",\n rule: null,\n reason: null\n };\n const body = subshellBody(trimmed);\n if (body !== null) return evaluateCommand(body, {\n ...ctx,\n depth: ctx.depth + 1\n });\n const segments = splitCommand(trimmed);\n if (segments.length === 0) return {\n action: \"none\",\n rule: null,\n reason: null\n };\n let firstNone = null;\n const currentCtx = { ...ctx };\n for (const segment of segments) {\n if (segment.operator === \"&&\") {\n const cdDir = extractCdDir(segment.command, currentCtx);\n if (cdDir) currentCtx.cwd = cdDir;\n }\n const result = evaluateSegment(segment.command, currentCtx);\n if (result.action === \"deny\") return result;\n if (!firstNone) firstNone = result;\n }\n return firstNone ?? {\n action: \"none\",\n rule: null,\n reason: null\n };\n}\n/**\n* Helper to evaluate a resolved script with its pre and post hooks.\n*\n* There is no allow tier, so there is nothing to \"trust\" a clean package.json\n* into skipping: every resolved script -- clean or not -- is evaluated for\n* denies the same way a top-level command is. A deny can only get stronger by\n* running the check, never weaker, so there is no safety reason to skip it.\n*/\nfunction evaluateResolvedScript(resolved, ctx) {\n if (resolved.pre) {\n const preResult = evaluateCommand(resolved.pre, ctx);\n if (preResult.action === \"deny\") return preResult;\n }\n const mainResult = evaluateCommand(resolved.main, ctx);\n if (mainResult.action === \"deny\") return mainResult;\n if (resolved.post) {\n const postResult = evaluateCommand(resolved.post, ctx);\n if (postResult.action === \"deny\") return postResult;\n }\n return {\n action: \"none\",\n rule: null,\n reason: null\n };\n}\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 const body = subshellBody(trimmed);\n if (body !== null) return evaluateCommand(body, {\n ...ctx,\n depth: ctx.depth + 1\n });\n if (trimmed.startsWith(\"(\") && trimmed.endsWith(\")\")) return {\n action: \"none\",\n rule: null,\n reason: \"subshell\"\n };\n const tokenResult = tokenize(rawSegment);\n if (!tokenResult.ok) return {\n action: \"none\",\n rule: null,\n reason: tokenResult.error\n };\n let tokens = tokenResult.tokens;\n if (tokens.length === 0) return {\n action: \"none\",\n rule: null,\n reason: null\n };\n let envSkipped = 0;\n while (envSkipped < tokens.length && /^\\w+=/.test(tokens[envSkipped])) envSkipped++;\n if (envSkipped >= tokens.length) return {\n action: \"none\",\n rule: null,\n reason: null\n };\n tokens = tokens.slice(envSkipped);\n if (writesIntoNodeModules(tokens, ctx)) return {\n action: \"deny\",\n rule: \"node-modules-write\",\n reason: NODE_MODULES_WRITE_REASON\n };\n const injection = checkInjection(tokens, rawSegment);\n if (!injection.safe) return {\n action: \"none\",\n rule: null,\n reason: injection.reason\n };\n const SAFE_REDIRECT_TOKENS = /* @__PURE__ */ new Set([\n \"2>&1\",\n \"2>/dev/null\",\n \">/dev/null\",\n \"1>/dev/null\"\n ]);\n tokens = tokens.filter((t, i) => {\n if (SAFE_REDIRECT_TOKENS.has(t)) return false;\n if ((t === \">\" || t === \">>\") && tokens[i + 1] === \"/dev/null\") return false;\n if (t === \"/dev/null\" && i > 0 && (tokens[i - 1] === \">\" || tokens[i - 1] === \">>\")) return false;\n return true;\n });\n const cmdName = path.basename(tokens[0]);\n if (cmdName === \"command\" && tokens.length > 1) return evaluateCommand(joinTokens(tokens.slice(1)), {\n ...ctx,\n depth: ctx.depth + 1\n });\n if (cmdName === \"env\") {\n let envIdx = 1;\n while (envIdx < tokens.length && /^\\w+=/.test(tokens[envIdx])) envIdx++;\n if (envIdx >= tokens.length) return {\n action: \"none\",\n rule: null,\n reason: null\n };\n return evaluateCommand(joinTokens(tokens.slice(envIdx)), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n if (cmdName === \"pnpm\") return evaluatePnpm(tokens, ctx);\n if (cmdName === \"npm\") return evaluateNpm(tokens);\n if (cmdName === \"mise\" && tokens[1] === \"exec\") {\n const inner = miseExecInner(tokens);\n if (!inner) return {\n action: \"none\",\n rule: null,\n reason: \"bare mise exec\"\n };\n return evaluateCommand(joinTokens(inner), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n if (cmdName === \"bash\" || cmdName === \"sh\" || cmdName === \"zsh\") return evaluateShellC(tokens, ctx);\n if (cmdName === \"xargs\") return evaluateXargs(tokens, ctx);\n if (cmdName === \"node\" && (tokens.includes(\"-e\") || tokens.includes(\"-p\") || tokens.includes(\"--eval\") || tokens.includes(\"--print\"))) return {\n action: \"none\",\n rule: null,\n reason: \"node eval\"\n };\n for (const rule of denyRules) if (rule.match.test(cmdName)) {\n if (!rule.when || rule.when(tokens, ctx)) return {\n action: \"deny\",\n rule: rule.name,\n reason: rule.reason ?? null\n };\n }\n const advice = cmdName === \"git\" ? worktreeAdvice(tokens, ctx) : void 0;\n return {\n action: \"none\",\n rule: null,\n reason: `no rule matched: ${cmdName}`,\n ...advice ? { advice } : {}\n };\n}\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: \"none\",\n rule: null,\n reason: null\n };\n if (subcommand === \"dlx\") return {\n action: \"none\",\n rule: null,\n reason: \"pnpm dlx\"\n };\n if (subcommand === \"add\") return {\n action: \"none\",\n rule: null,\n reason: \"pnpm add\"\n };\n if (subcommand === \"exec\") {\n const execArgs = subTokens.slice(subcommandIdx + 1);\n if (execArgs.length === 0) return {\n action: \"none\",\n rule: null,\n reason: \"bare pnpm exec\"\n };\n return evaluateCommand(joinTokens(execArgs), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n let scriptName;\n let { isRecursive, isWorkspaceRoot, dirOverride } = state;\n if (subcommand === \"run\") {\n const runScan = scanPnpmFlags(subTokens, subcommandIdx + 1, state);\n ({isRecursive, isWorkspaceRoot, dirOverride} = state);\n if (runScan.filterIdx !== void 0) return evaluatePnpmFilter(tokens, subTokens, runScan.filterIdx, ctx);\n if (!runScan.name) return {\n action: \"none\",\n rule: null,\n reason: \"pnpm run without script name\"\n };\n scriptName = runScan.name;\n } else if (!PNPM_BUILTINS.has(subcommand)) scriptName = subcommand;\n else return {\n action: \"none\",\n rule: null,\n reason: null\n };\n if (isRecursive) return evaluatePnpmRecursive(scriptName, ctx);\n if (isWorkspaceRoot) {\n const root = findWorkspaceRoot(ctx.cwd);\n if (!root) return {\n action: \"none\",\n rule: null,\n reason: \"workspace root not found\"\n };\n return evaluatePnpmScript(scriptName, {\n ...ctx,\n cwd: root\n });\n }\n if (dirOverride) {\n const resolved = path.resolve(ctx.cwd, dirOverride);\n return evaluatePnpmScript(scriptName, {\n ...ctx,\n cwd: resolved\n });\n }\n return evaluatePnpmScript(scriptName, ctx);\n}\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: \"none\",\n rule: null,\n reason: \"script resolution failed\"\n };\n const res = evaluateResolvedScript(resolved, {\n ...ctx,\n depth: ctx.depth + 1\n });\n if (res.action === \"deny\") return res;\n return {\n action: \"none\",\n rule: `pnpm-script:${scriptName}`,\n reason: null\n };\n}\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: \"none\",\n rule: null,\n reason: \"pnpm recursive\"\n };\n for (const pkg of resolved) {\n const res = evaluateResolvedScript(pkg, {\n ...ctx,\n depth: ctx.depth + 1,\n cwd: pkg.pkgDir ?? ctx.cwd\n });\n if (res.action === \"deny\") return res;\n }\n return {\n action: \"none\",\n rule: `pnpm-recursive:${scriptName}`,\n reason: null\n };\n}\n/**\n* Evaluate an npm command. Read-only ops (view/info/show) pass through;\n* everything else denies with a redirect message to the pnpm equivalent.\n*/\nfunction evaluateNpm(tokens) {\n if (isNpmReadOp(tokens)) return {\n action: \"none\",\n rule: null,\n reason: null\n };\n return {\n action: \"deny\",\n rule: \"npm-redirect\",\n reason: getNpmRedirectReason(tokens)\n };\n}\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 none 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: \"none\",\n rule: null,\n reason: \"pnpm --filter without package\"\n };\n filterPkgs.push(pkg);\n cursor += 2;\n } else if (t.startsWith(\"--filter=\") || t.startsWith(\"-F=\")) {\n const pkg = t.startsWith(\"-F=\") ? t.slice(3) : t.slice(9);\n if (!pkg) return {\n action: \"none\",\n rule: null,\n reason: \"pnpm --filter without package\"\n };\n filterPkgs.push(pkg);\n cursor += 1;\n } else break;\n }\n if (filterPkgs.length === 0) return {\n action: \"none\",\n rule: null,\n reason: \"pnpm --filter without package\"\n };\n let filterSubcommand;\n let subcmdIdx = -1;\n for (let i = cursor; i < subTokens.length; i++) {\n const t = subTokens[i];\n if (t === \"--filter\" || t === \"-F\") {\n i += 1;\n continue;\n }\n if (t.startsWith(\"--filter=\") || t.startsWith(\"-F=\")) continue;\n if (t.startsWith(\"-\") && !PNPM_BUILTINS.has(t)) continue;\n filterSubcommand = t;\n subcmdIdx = i;\n break;\n }\n if (!filterSubcommand) return {\n action: \"none\",\n rule: null,\n reason: \"pnpm --filter without subcommand\"\n };\n if (PNPM_BUILTINS.has(filterSubcommand) && filterSubcommand !== \"run\" && filterSubcommand !== \"exec\") return {\n action: \"none\",\n rule: null,\n reason: null\n };\n if (filterSubcommand === \"exec\") {\n const execArgs = subTokens.slice(subcmdIdx + 1);\n if (execArgs.length === 0) return {\n action: \"none\",\n rule: null,\n reason: \"bare pnpm exec\"\n };\n return evaluateCommand(joinTokens(execArgs), {\n ...ctx,\n depth: ctx.depth + 1\n });\n }\n const scriptName = filterSubcommand === \"run\" ? subTokens[subcmdIdx + 1] : filterSubcommand;\n if (!scriptName || scriptName.startsWith(\"-\")) return {\n action: \"none\",\n rule: null,\n reason: \"pnpm --filter run without script name\"\n };\n if (PNPM_BUILTINS.has(scriptName) && scriptName !== \"run\") return {\n action: \"none\",\n rule: null,\n reason: null\n };\n for (const pkg of filterPkgs) {\n const resolved = resolveFilteredScript(scriptName, pkg, ctx.cwd);\n if (!resolved) return {\n action: \"none\",\n rule: null,\n reason: \"filtered script resolution failed\"\n };\n const res = evaluateResolvedScript(resolved, {\n ...ctx,\n depth: ctx.depth + 1,\n cwd: resolved.pkgDir ?? ctx.cwd\n });\n if (res.action === \"deny\") return res;\n }\n return {\n action: \"none\",\n rule: `pnpm-filter:${filterPkgs.join(\",\")}:${scriptName}`,\n reason: null\n };\n}\n/** A login flag: the shell reads its profile first, then runs `-c` as usual. */\nconst SHELL_LOGIN_FLAGS = /* @__PURE__ */ new Set([\"-l\", \"--login\"]);\n/**\n* Evaluate `bash -c` / `sh -c` / `zsh -c` commands.\n*\n* Unwraps only `<shell> [-l|--login]... -c <command-string>`, with `-c` also\n* accepted inside a cluster of `l` and `c` (`-lc`, `-cl`). A login shell reads\n* its profile before the command, which can change what runs but never makes\n* a denied command safe. Any other flag, a positional after the command\n* string (it sets `$0`, `$1`, ...), or a missing `-c` -> none.\n*/\nfunction evaluateShellC(tokens, ctx) {\n let i = 1;\n while (i < tokens.length && SHELL_LOGIN_FLAGS.has(tokens[i])) i++;\n const flag = tokens[i];\n if (flag === void 0 || !/^-[lc]+$/.test(flag) || !flag.includes(\"c\")) return {\n action: \"none\",\n rule: null,\n reason: \"shell without a plain -c\"\n };\n const cmdArg = tokens[i + 1];\n if (!cmdArg) return {\n action: \"none\",\n rule: null,\n reason: \"shell -c without command\"\n };\n if (tokens.length > i + 2) return {\n action: \"none\",\n rule: null,\n reason: \"shell -c with positional args\"\n };\n return evaluateCommand(cmdArg, {\n ...ctx,\n depth: ctx.depth + 1\n });\n}\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* None 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: \"none\",\n rule: null,\n reason: \"xargs with unrecognised flags\"\n };\n }\n if (i >= tokens.length) return {\n action: \"none\",\n rule: null,\n reason: \"xargs without command\"\n };\n return evaluateCommand(joinTokens(tokens.slice(i)), {\n ...ctx,\n depth: ctx.depth + 1\n });\n}\n\n//#endregion\n//#region src/file-rules.ts\nconst READ_TOOLS = /* @__PURE__ */ new Set([\n \"Read\",\n \"Glob\",\n \"Grep\"\n]);\nconst WRITE_TOOLS = /* @__PURE__ */ new Set([\"Write\", \"Edit\"]);\nconst fileDenyRules = [\n {\n name: \"env-file-read\",\n tools: READ_TOOLS,\n action: \"deny\",\n when: (filePath) => isEnvFile(filePath),\n reason: \"Don't read .env files. These contain secrets that should not appear in the conversation.\"\n },\n {\n name: \"env-file-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath) => isEnvFile(filePath),\n reason: \"Don't write to .env files. These contain secrets.\"\n },\n {\n name: \"hooks-folder-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx) => {\n const resolved = resolveIn(ctx.cwd, filePath);\n const hooksDir = path.join(ctx.cwd, \".claude\", \"hooks\");\n return isWithin(hooksDir, resolved);\n },\n reason: \"Don't write to .claude/hooks/. These files are managed by the project and should not be modified directly.\"\n },\n {\n name: \"git-dir-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx) => targetsGitDir(filePath, ctx.cwd),\n reason: \"Don't write into .git/ directly. Its internals (config, hooks, refs) are not plain project files -- a config edit can redirect remotes or push destinations that other rules trust, and a hooks/ script runs automatically on the next commit/push. Use git subcommands instead, or ask the user.\"\n },\n {\n name: \"node-modules-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx) => targetsNodeModules(filePath, ctx.cwd),\n reason: \"Don't write into node_modules/. Its files are hardlinks into pnpm's global store -- editing one in place corrupts that package for every project on this machine. Edit the source package and rebuild, or install into a scratch tree with an isolated store (`pnpm add --store-dir <scratch> <pkg>`).\"\n },\n {\n name: \"claude-settings-write\",\n tools: WRITE_TOOLS,\n action: \"deny\",\n when: (filePath, ctx) => {\n const resolved = resolveIn(ctx.cwd, filePath);\n return samePath(resolved, path.join(ctx.cwd, \".claude\", \"settings.json\"));\n },\n reason: \"Don't write to .claude/settings.json. This file is managed by the project and should not be modified directly.\"\n }\n];\n\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 deny or none.\n*/\nfunction evaluateToolUse(toolName, toolInput, ctx) {\n const filePath = extractPath(toolName, toolInput, ctx.cwd);\n if (filePath === null) return {\n action: \"none\",\n rule: null,\n reason: \"unknown tool input shape\"\n };\n for (const rule of fileDenyRules) if (rule.tools.has(toolName) && rule.when(filePath, ctx)) return {\n action: \"deny\",\n rule: rule.name,\n reason: rule.reason ?? null\n };\n return {\n action: \"none\",\n rule: null,\n reason: \"no file rule matched\"\n };\n}\n\n//#endregion\n//#region src/lib/settings.ts\nconst SETTINGS_PATH = \".claude/hooks/bst-hook.settings.json\";\nconst BstHookSettingsSchema = object({ protectedBranches: array(string().min(1)).default([]) });\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 `{ protectedBranches: [] }` if the file is missing, invalid, or\n* untrusted.\n*/\nfunction loadRepoSettings(cwd) {\n const root = findWorkspaceRoot(cwd) ?? cwd;\n const settingsFile = join(root, SETTINGS_PATH);\n const empty = { protectedBranches: [] };\n if (!existsSync(settingsFile)) return empty;\n if (!isFileClean(root, SETTINGS_PATH)) return empty;\n try {\n const raw = readFileSync(settingsFile, \"utf-8\");\n const parsed = JSON.parse(raw);\n const result = BstHookSettingsSchema.safeParse(parsed);\n if (!result.success) return empty;\n return { protectedBranches: result.data.protectedBranches };\n } catch {\n return empty;\n }\n}\n\n//#endregion\n//#region src/index.ts\nconst inputResult = await readHookInput();\nif (!inputResult.ok) {\n process.stderr.write(`[bst-hook] invalid hook input: ${inputResult.error}\\n`);\n audit({\n sessionId: \"unknown\",\n cwd: \"unknown\",\n command: `<invalid input: ${inputResult.error}>`,\n decision: \"none\",\n rule: null,\n reason: `validation failed: ${inputResult.error}`,\n permissionMode: void 0\n });\n process.exit(0);\n}\nconst input = inputResult.data;\nif (input.hook_event_name !== \"PreToolUse\") process.exit(0);\nif (!(/* @__PURE__ */ new Set([\n \"Bash\",\n \"Read\",\n \"Write\",\n \"Edit\",\n \"Glob\",\n \"Grep\"\n])).has(input.tool_name)) process.exit(0);\nconst repoSettings = loadRepoSettings(input.cwd);\nlet decision;\nlet auditCommand;\nif (input.tool_name === \"Bash\") {\n const bashInput = parseBashInput(input.tool_input);\n if (!bashInput) {\n audit({\n sessionId: input.session_id,\n cwd: input.cwd,\n toolName: input.tool_name,\n command: `<invalid Bash input: ${JSON.stringify(input.tool_input).slice(0, 200)}>`,\n decision: \"none\",\n rule: null,\n reason: \"Bash tool_input missing or invalid 'command' field\",\n permissionMode: input.permission_mode\n });\n process.exit(0);\n }\n decision = evaluateCommand(bashInput.command, {\n cwd: input.cwd,\n depth: 0,\n repoProtectedBranches: repoSettings.protectedBranches\n });\n auditCommand = bashInput.command;\n} else {\n const projectDir = findWorkspaceRoot(input.cwd) ?? process.env[\"CLAUDE_PROJECT_DIR\"] ?? input.cwd;\n decision = evaluateToolUse(input.tool_name, input.tool_input, {\n cwd: projectDir,\n sessionId: input.session_id\n });\n const filePath = String(input.tool_input[\"file_path\"] ?? input.tool_input[\"path\"] ?? \"\");\n auditCommand = `${input.tool_name} ${filePath}`;\n}\naudit({\n sessionId: input.session_id,\n cwd: input.cwd,\n toolName: input.tool_name,\n command: auditCommand,\n decision: decision.action,\n rule: decision.rule,\n reason: decision.reason,\n permissionMode: input.permission_mode\n});\nif (decision.action === \"deny\") writeDeny(decision.reason);\nelse if (decision.advice) writeAdvice(decision.advice);\n\n//#endregion\nexport { };", "bst-hook.mjs"), writeIfChanged(ctx, GUIDE_PATH, "# bst-hook Permission Guide\n\nThis project uses a PreToolUse hook (`bst-hook`) that checks each Bash command\nand file tool call before it runs. The hook only ever blocks a command; it does\nnot approve anything on its own. A command that matches a rule below is denied,\nwith a message that says what to do instead.\n\n## Denied commands\n\nThese are blocked. The hook tells you what to do instead.\n\n### Bash\n\n- **sudo**: Don't use sudo. If you need elevated permissions, ask me to do it manually.\n- **git-force-push**: Use `git push --force-with-lease` instead of `--force`.\n- **git-protected-branch-write**: This would move a protected branch (main, master, the remote's default branch, or one listed in .claude/hooks/bst-hook.settings.json#protectedBranches). Work on a feature branch in a worktree: `git worktree add ../<repo>-<topic> -b feat/<topic>`, then commit and push there. To bring `main` up to date, use `git pull --ff-only`.\n- **chmod-777**: Don't use 777 permissions. Use more restrictive permissions.\n- **node-modules-write**: Don't write into node_modules/. Its files are hardlinks into pnpm's global store, so overwriting one in place (plain `cp`, `>` redirect, `sed -i`, ...) writes through the link and corrupts that package for every project on this machine -- silently, since the directory you wrote into ends up fine. To test a local build as an installed dependency, run it directly (`node packages/<pkg>/dist/bin.mjs <cmd>`), or install into a scratch tree with an isolated store (`pnpm add --store-dir <scratch> <pkg>`). If you must replace a file in place, `cp --remove-destination` is safe -- it unlinks the target first. Run `pnpm store status` to check whether the store is already damaged.\n- **env-file-read**: Don't read .env files. These contain secrets that should not appear in the conversation.\n- **env-file-write**: Don't write to .env files. These contain secrets.\n- **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\n## How commands are evaluated\n\n- **Pipelines and chains**: every segment is checked; a deny on any segment blocks the whole command.\n- **pnpm scripts**: `pnpm <script>` resolves against `package.json` (including pre/post hooks and `--filter` workspaces) and evaluates the resolved command recursively.\n- **cd propagation**: `cd dir && cmd` evaluates `cmd` relative to the new directory.\n- **Audit log**: all decisions are logged to `.claude/hooks/bst-hook.audit.local.jsonl`.\n\n## Everything else\n\nA command that matches none of the rules above is judged by Claude Code's own\nauto mode, not by this hook.\n", "bst-hook-guide.md")];
10207
+ const legacyAllowResult = checkLegacyAllowKey(ctx);
10208
+ if (legacyAllowResult) results.push(legacyAllowResult);
10209
+ return results;
9947
10210
  }
9948
10211
  //#endregion
9949
10212
  //#region src/generators/claude-settings.ts
@@ -10596,12 +10859,12 @@ const TS_EXTENSIONLESS_RULE = "- Prefer extensionless imports; if an extension i
10596
10859
  */
10597
10860
  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.";
10598
10861
  const AGENT_RULES_TAIL = [
10599
- "- The bst-hook manages Bash/file permissions -- see `.claude/hooks/bst-hook-guide.md`.",
10862
+ "- The bst-hook blocks a short list of dangerous commands (`.claude/hooks/bst-hook-guide.md` lists them); Claude Code's auto mode judges everything else.",
10600
10863
  "- 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.",
10601
10864
  "",
10602
10865
  "#### Git discipline",
10603
10866
  "",
10604
- "**The branch you are on is the mode switch.** On a protected branch the bst-hook allows nothing but unstaged edits; on a feature branch it allows the full commit/push/PR loop. Pick the mode, announce it, and only ask when it's genuinely ambiguous.",
10867
+ "**The branch you are on is the mode switch.** The bst-hook refuses any command that would move a protected branch, so on one you leave changes unstaged; on a feature branch you commit, push, and open the PR yourself. Pick the mode, announce it, and only ask when it's genuinely ambiguous.",
10605
10868
  "",
10606
10869
  "**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.",
10607
10870
  "",
@@ -10660,7 +10923,11 @@ const GIT_GUIDE_BODY = [
10660
10923
  "",
10661
10924
  "## Commit subjects",
10662
10925
  "",
10663
- "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."
10926
+ "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.",
10927
+ "",
10928
+ "## Pull request titles",
10929
+ "",
10930
+ "Start a pull request's title with the stage it carries: `Spec:` for a spec, `Plan:` for a plan, and no stem once it carries the implementation. A draft keeps its `WIP:` marker in front (`WIP: Plan: Add retry to uploads`). When a pull request moves on to the next stage, move the stem with it. This is informational -- nothing checks it and nothing fails without it -- but it tells a reviewer at a glance what they are reading."
10664
10931
  ].join("\n");
10665
10932
  /**
10666
10933
  * The plan lifecycle. `bst docs:check` owns the mechanics; this states the one rule it cannot
@@ -10695,16 +10962,17 @@ const PLANNING_GUIDE_BODY = [
10695
10962
  "",
10696
10963
  "## The plan",
10697
10964
  "",
10698
- "Write for an executor who knows nothing about this repo and has only the plan and the spec. Save it under `docs/superpowers/plans/`.",
10965
+ "Write for an executor who has not seen this repo or the spec. They write idiomatic code once they know the exact interface and the exact test, and make a reasonable choice wherever the plan leaves one open; what they cannot know is what you decided. A plan records those decisions -- it is not a transcript of the code. Save it under `docs/superpowers/plans/`.",
10699
10966
  "",
10700
10967
  "- 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.",
10701
10968
  "- Label each task `Task <n>:` at the start of its heading or checkbox, numbered from 1, so `bst plan:brief` can find it.",
10702
- "- Name exact file paths, the interfaces each task uses and provides, and every command along with the output it should produce.",
10969
+ "- Give each task what makes it unambiguous and nothing more: a test's name and its assertions with the spec's exact values, a code change's exact signature and file, a command with the output that means it passed. Write out a body only for an algorithm the signature and tests do not determine. Work that uses another task's result names that task's interfaces, never repeats its code.",
10703
10970
  "- 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.",
10704
10971
  "- 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.",
10705
10972
  "- State a judgement that recurs across many places once, as a rule in the plan, so every task decides it the same way.",
10706
10973
  "- 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.",
10707
10974
  "- 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.",
10975
+ "- Reread the finished plan against the spec. A plan several times longer than its spec has written the program instead of the decisions: replace bodies with signatures, test names, and assertions. A line that decides nothing (\"TBD\", \"handle edge cases\", a type no task defines) is the opposite failure. Fix both.",
10708
10976
  "",
10709
10977
  "## Working through a plan",
10710
10978
  "",
@@ -10891,6 +11159,13 @@ function checkCadenceBody(config) {
10891
11159
  * a failing gate, but an exemption carries a required justification and prints in
10892
11160
  * `health:summary --explain`; raising the limit clears it silently and un-tracks every module
10893
11161
  * under the new ceiling at the same time.
11162
+ *
11163
+ * The tail keeps a per-module ceiling apart from that shared limit because an exemption
11164
+ * suppresses reporting, not measurement: `ceilingsFromDetails` in `codebase-health/collect.ts`
11165
+ * records a ceiling for an exempt module too, so the gate still fails when such a hub gains an
11166
+ * edge. A reader who reads "exempt" as "unmeasured" files the resulting raise as an
11167
+ * unexplained regression, and a remedy list of only split-or-exempt gives that reader nowhere
11168
+ * to go.
10894
11169
  */
10895
11170
  const HEALTH_RULE_HEAD = [
10896
11171
  "# Codebase health",
@@ -10900,7 +11175,19 @@ const HEALTH_RULE_HEAD = [
10900
11175
  ];
10901
11176
  const HEALTH_RULE_SAME_PACKAGE = "**The fan-in, fan-out, and cycle metrics count value edges only.** A type-only import is erased before the graph is built and never moves a metric. An import across a workspace package boundary counts at the `health.crossPackage` default and is invisible when it is off.";
10902
11177
  const HEALTH_RULE_SINGLE_PACKAGE = "**The fan-in, fan-out, and cycle metrics count value edges, and nothing else.** A type-only import is erased before the graph is built, and an import of an installed package resolves outside it -- neither moves any metric.";
10903
- const HEALTH_RULE_TAIL = ["", "So never duplicate a type, or fork a helper, to protect a fan budget -- import it. When a real value edge trips the gate, split the module or record a justified `health.exempt` entry, which names the hub a copy would hide."];
11178
+ const HEALTH_RULE_TAIL = [
11179
+ "",
11180
+ "So never duplicate a type, or fork a helper, to protect a fan budget -- import it. The converse holds: turning a type-only import into a value import adds an edge, so a module that already imports another for its types gains a real edge the first time it imports a value from it.",
11181
+ "",
11182
+ "**An exemption drops a module from one outlier list and nothing else.** Every module over a fan limit keeps a per-module ceiling in `health-baseline.json`, exempt or not, and growing past that ceiling fails the gate.",
11183
+ "",
11184
+ "When a real value edge trips the gate, the remedy depends on the module:",
11185
+ "- No `health.exempt` entry: split the module, or record a justified entry. The justification names what the hub wires by construction, which is what a copy would hide.",
11186
+ "- An exempt hub gaining an edge inside its justification's scope: adopt the growth with `bst health:check --update-baseline --reset` in the pull request that adds the edge, and say why in that PR's plan or commit. The justification is the durable record of what the hub may wire; the raise needs no second record when the new edge is already named there.",
11187
+ "- An exempt hub gaining an edge outside that scope: split the module, or widen the justification in `.tooling.json` as a reviewed edit of its own. A ceiling raised without either is the thing a reviewer should question.",
11188
+ "",
11189
+ "Prefer an exemption to raising `fanInLimit` or `fanOutLimit`: an exemption names the hub in `bst health:summary --explain`, while a higher limit silently un-tracks every module beneath it. A per-module ceiling has no such shadow -- it ratchets one module at one count and fails again one edge later."
11190
+ ];
10904
11191
  function healthRuleBody(config) {
10905
11192
  const lines = [...HEALTH_RULE_HEAD];
10906
11193
  lines.push(config.structure === "monorepo" ? HEALTH_RULE_SAME_PACKAGE : HEALTH_RULE_SINGLE_PACKAGE);
@@ -11324,7 +11611,7 @@ function reportFailures(failed, command) {
11324
11611
  function generateMigratePrompt(results, config, detected) {
11325
11612
  const sections = [];
11326
11613
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
11327
- sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.79.0 repo:sync\` on ${timestamp}_\n`);
11614
+ sections.push(`# Migration Prompt\n\n_Generated by \`@bensandee/tooling@0.81.0 repo:sync\` on ${timestamp}_\n`);
11328
11615
  const changed = results.filter((r) => r.action === "created" || r.action === "updated" || r.action === "archived");
11329
11616
  if (changed.length > 0) {
11330
11617
  const items = changed.map((r) => {
@@ -12350,6 +12637,32 @@ function execStep(cmd, cwd, sink = process.stdout) {
12350
12637
  });
12351
12638
  }
12352
12639
  //#endregion
12640
+ //#region src/checks/skip-report.ts
12641
+ function matches(name, patterns) {
12642
+ return patterns.size > 0 && picomatch.isMatch(name, [...patterns]);
12643
+ }
12644
+ /**
12645
+ * Which steps each skip source removed, for reporting. Only a step with a defined script is
12646
+ * named: one without a script was never going to run, so a repo with no Docker is never told
12647
+ * `docker:build` was skipped. A step matched by both sources is credited to `--skip`, the one
12648
+ * the caller typed.
12649
+ */
12650
+ function groupSkippedSteps(steps, defined, flagSkip, configSkip) {
12651
+ const bySource = {
12652
+ "--skip": [],
12653
+ "checks.skip": []
12654
+ };
12655
+ for (const name of steps) {
12656
+ if (!defined.has(name)) continue;
12657
+ if (matches(name, flagSkip)) bySource["--skip"].push(name);
12658
+ else if (matches(name, configSkip)) bySource["checks.skip"].push(name);
12659
+ }
12660
+ return ["--skip", "checks.skip"].filter((source) => bySource[source].length > 0).map((source) => ({
12661
+ source,
12662
+ steps: bySource[source]
12663
+ }));
12664
+ }
12665
+ //#endregion
12353
12666
  //#region src/utils/branch.ts
12354
12667
  const BRANCH_REF_PREFIX = "refs/heads/";
12355
12668
  function defaultGitRunner(args, cwd) {
@@ -12601,8 +12914,20 @@ function defaultGetScripts(targetDir) {
12601
12914
  }
12602
12915
  }
12603
12916
  const rawLog$2 = (msg) => console.log(msg);
12604
- function advisorySummary(warned) {
12605
- return `All checks passed (${String(warned.length)} advisory: ${warned.map((w) => w.name).join(", ")})`;
12917
+ /**
12918
+ * The skipped names on a failing run, as a line of their own. Never folded into the
12919
+ * `Failed checks: <names>` line: the agent's verify backstop (`parseFailingStep`) reads
12920
+ * everything after it as step names and discards the lot on anything else.
12921
+ */
12922
+ function failedRunSkippedLine(skipped) {
12923
+ return skipped.length === 0 ? null : `Skipped: ${skipped.join(", ")}`;
12924
+ }
12925
+ function passedSummary(warned, skipped) {
12926
+ const parts = [...warned.length > 0 ? [`${String(warned.length)} advisory: ${warned.map((w) => w.name).join(", ")}`] : [], ...skipped.length > 0 ? [`skipped: ${skipped.join(", ")}`] : []];
12927
+ return parts.length === 0 ? "All checks passed" : `All checks passed (${parts.join("; ")})`;
12928
+ }
12929
+ function skippedLine(group) {
12930
+ return `Skipped (${group.source}): ${group.steps.join(", ")}`;
12606
12931
  }
12607
12932
  const ciReporter = {
12608
12933
  groupStart: (name) => rawLog$2(`::group::${name}`),
@@ -12617,9 +12942,16 @@ const ciReporter = {
12617
12942
  rawLog$2(`::error::${name} failed`);
12618
12943
  },
12619
12944
  undefinedCheck: (name) => rawLog$2(`::error::${name} not defined in package.json`),
12945
+ skipped: (groups) => {
12946
+ for (const group of groups) rawLog$2(skippedLine(group));
12947
+ },
12620
12948
  skippedNotDefined: (names) => rawLog$2(`Skipped (not defined): ${names.join(", ")}`),
12621
- allPassed: (warned) => rawLog$2(warned.length === 0 ? "[ok] All checks passed" : `[ok] ${advisorySummary(warned)}`),
12622
- anyFailed: (names) => rawLog$2(`::error::Failed checks: ${names.join(", ")}`)
12949
+ allPassed: (warned, skipped) => rawLog$2(`[ok] ${passedSummary(warned, skipped)}`),
12950
+ anyFailed: (names, skipped) => {
12951
+ const skippedNames = failedRunSkippedLine(skipped);
12952
+ if (skippedNames !== null) rawLog$2(skippedNames);
12953
+ rawLog$2(`::error::Failed checks: ${names.join(", ")}`);
12954
+ }
12623
12955
  };
12624
12956
  const localReporter = {
12625
12957
  groupStart: (_name) => {},
@@ -12628,16 +12960,35 @@ const localReporter = {
12628
12960
  warned: (name, _elapsedS, count) => log$1.warn(`${name}: ${String(count)} advisory finding(s)`),
12629
12961
  failed: (name) => log$1.error(`${name} failed`),
12630
12962
  undefinedCheck: (name) => log$1.error(`${name} not defined in package.json`),
12963
+ skipped: (groups) => {
12964
+ for (const group of groups) log$1.info(skippedLine(group));
12965
+ },
12631
12966
  skippedNotDefined: (names) => log$1.info(`Skipped (not defined): ${names.join(", ")}`),
12632
- allPassed: (warned) => warned.length === 0 ? log$1.success("All checks passed") : log$1.success(advisorySummary(warned)),
12633
- anyFailed: (names) => log$1.error(`Failed checks: ${names.join(", ")}`)
12967
+ allPassed: (warned, skipped) => log$1.success(passedSummary(warned, skipped)),
12968
+ anyFailed: (names, skipped) => {
12969
+ const skippedNames = failedRunSkippedLine(skipped);
12970
+ if (skippedNames !== null) log$1.info(skippedNames);
12971
+ log$1.error(`Failed checks: ${names.join(", ")}`);
12972
+ }
12634
12973
  };
12974
+ /**
12975
+ * Name every skipped step before the first one runs, and return the names for the summary: a
12976
+ * step that silently vanishes from the run reads as a bug in the pipeline rather than a skip
12977
+ * someone asked for.
12978
+ */
12979
+ function reportSkipped(reporter, names, defined, flagSkip, configSkip) {
12980
+ const groups = groupSkippedSteps(names, defined, flagSkip, configSkip);
12981
+ reporter.skipped(groups);
12982
+ return groups.flatMap((group) => group.steps);
12983
+ }
12635
12984
  async function runRunChecks(targetDir, options = {}) {
12636
12985
  const exec = options.execCommand ?? execStep;
12637
12986
  const getScripts = options.getScripts ?? defaultGetScripts;
12638
12987
  const toolingConfig = options.toolingConfig ?? loadToolingConfig(targetDir);
12639
12988
  const configChecks = toolingConfig?.checks;
12640
- const skip = /* @__PURE__ */ new Set([...options.skip ?? [], ...configChecks?.skip ?? []]);
12989
+ const flagSkip = new Set(options.skip ?? []);
12990
+ const configSkip = new Set(configChecks?.skip ?? []);
12991
+ const skip = /* @__PURE__ */ new Set([...flagSkip, ...configSkip]);
12641
12992
  const add = [.../* @__PURE__ */ new Set([...options.add ?? [], ...configChecks?.add ?? []])];
12642
12993
  const isCI = Boolean(process.env["CI"]);
12643
12994
  const failFast = options.failFast ?? !isCI;
@@ -12656,6 +13007,7 @@ async function runRunChecks(targetDir, options = {}) {
12656
13007
  if (coverage.enabled && !useCoverageScript && coverage.source === "flag") log$1.warn("--with-coverage requested but test:coverage script is not defined; falling back to test");
12657
13008
  if (coverage.enabled && !useCoverageScript && coverage.source === "config") log$1.warn("coverage.gateOnPullRequests is set but test:coverage script is not defined; falling back to test");
12658
13009
  debug(vc, `Coverage: ${options.coverage ?? "auto"} (source: ${coverage.source}) -> ${useCoverageScript ? "test:coverage" : "test"}`);
13010
+ const skippedSteps = reportSkipped(reporter, allChecks.map((check) => check.name), definedScripts, flagSkip, configSkip);
12659
13011
  for (const check of allChecks) {
12660
13012
  if (shouldSkip(check.name, skip)) continue;
12661
13013
  if (!definedScripts.has(check.name)) {
@@ -12694,10 +13046,10 @@ async function runRunChecks(targetDir, options = {}) {
12694
13046
  }
12695
13047
  if (notDefined.length > 0) reporter.skippedNotDefined(notDefined);
12696
13048
  if (failures.length > 0) {
12697
- reporter.anyFailed(failures);
13049
+ reporter.anyFailed(failures, skippedSteps);
12698
13050
  return 1;
12699
13051
  }
12700
- reporter.allPassed(warnedSteps);
13052
+ reporter.allPassed(warnedSteps, skippedSteps);
12701
13053
  return 0;
12702
13054
  }
12703
13055
  /**
@@ -13182,13 +13534,20 @@ const syncCommand$1 = defineCommand({
13182
13534
  type: "boolean",
13183
13535
  default: true,
13184
13536
  description: "Generate the migration prompt (default). --no-prompt skips it, and skips the first-run stack prompt."
13537
+ },
13538
+ json: {
13539
+ type: "boolean",
13540
+ description: "Emit the check report as JSON (--check only; the write mode prompts and inherits child streams)"
13185
13541
  }
13186
13542
  },
13187
13543
  async run({ args }) {
13188
13544
  const targetDir = path.resolve(args.dir ?? ".");
13545
+ const out = createCliOutput(args.json === true);
13546
+ if ((args.json === true || isJsonMode()) && args.check !== true) throw new FatalError("--json is granted to repo:sync --check only: the write mode prompts on a first run and inherits pnpm install's streams.");
13189
13547
  if (args.check) {
13190
- const exitCode = await runCheck(targetDir);
13191
- process.exitCode = exitCode;
13548
+ const report = await runCheckReport(targetDir);
13549
+ out.json(report);
13550
+ process.exitCode = report.exitCode;
13192
13551
  return;
13193
13552
  }
13194
13553
  const flags = {
@@ -13242,27 +13601,121 @@ async function runUpdate(targetDir, flags = {}) {
13242
13601
  confirmOverwrite: async () => "overwrite"
13243
13602
  });
13244
13603
  }
13245
- /** Run sync in check mode: dry-run drift detection. */
13246
- async function runCheck(targetDir) {
13604
+ /**
13605
+ * The one place a failed-gate verdict is built, so `status` and `exitCode` cannot drift here.
13606
+ * Module scope rather than a closure inside `runCheckReport`: it captures nothing from the run, and
13607
+ * a nested copy would both trip the function-scoping lint and hide that every early gate return is
13608
+ * the same verdict.
13609
+ */
13610
+ function gateFailure(toolchainWarned = false) {
13611
+ return {
13612
+ status: "failed",
13613
+ actionable: [],
13614
+ failed: [],
13615
+ toolchain: { warned: toolchainWarned },
13616
+ warnCount: 0,
13617
+ exitCode: 1
13618
+ };
13619
+ }
13620
+ /**
13621
+ * The no-drift, no-failure verdict. Its text-mode line is the advisory warning marker when the
13622
+ * toolchain gate warned (still exit 0) and a success line otherwise. In JSON mode the marker is
13623
+ * withheld -- the same number rides in `warnCount`, and printing both would put a second thing on
13624
+ * the stream beside the payload.
13625
+ */
13626
+ function upToDateReport(toolchain) {
13627
+ const warnCount = toolchain.warned ? 1 : 0;
13628
+ if (warnCount > 0) {
13629
+ if (!isJsonMode()) rawLog$1(formatWarnMarker(warnCount));
13630
+ } else log$1.success("Repository is up to date.");
13631
+ return {
13632
+ status: "up-to-date",
13633
+ actionable: [],
13634
+ failed: [],
13635
+ toolchain,
13636
+ warnCount,
13637
+ exitCode: 0
13638
+ };
13639
+ }
13640
+ /**
13641
+ * Run sync in check mode and report the verdict as data rather than as an exit code.
13642
+ *
13643
+ * `--check`'s exit code is the whole story in a terminal, and it is a lossy one: `0` means both
13644
+ * "nothing to do" and "drift exists but only under a workflow directory, which is advisory", and
13645
+ * `1` means both "drift" and "a generator failed, so nothing here was measured". The four statuses
13646
+ * say which of the four it was, and `warnCount` carries the number the stdout marker carries, so a
13647
+ * `--json` reader gets the same advisory signal without scraping stdout.
13648
+ *
13649
+ * `runCheck` is a one-line wrapper over this rather than a parallel implementation: ~25 assertions
13650
+ * across `test/check.drift.test.ts` and `test/check.failures.test.ts` drive the exit code directly,
13651
+ * and two shapes computing the same verdict would be two shapes free to disagree.
13652
+ */
13653
+ async function runCheckReport(targetDir) {
13247
13654
  const saved = loadToolingConfigStrict(targetDir);
13248
13655
  if (saved) {
13249
13656
  warnStaleChecksAdd(saved);
13250
13657
  warnCoveredChecksAdd(saved, targetDir);
13251
13658
  }
13252
- if (runComposeLintGate(targetDir, saved) > 0) return 1;
13659
+ if (runComposeLintGate(targetDir, saved) > 0) return gateFailure();
13253
13660
  const detected = buildDefaultConfig(targetDir, {});
13254
13661
  const config = saved ? mergeWithSavedConfig(detected, saved) : detected;
13255
- if (runReleaseTargetGate(config, targetDir)) return 1;
13256
- if (runVersionStampGate(saved, targetDir, config.releaseStrategy) > 0) return 1;
13257
- if (runPublishConfigGate(config, targetDir) > 0) return 1;
13662
+ if (runReleaseTargetGate(config, targetDir)) return gateFailure();
13663
+ if (runVersionStampGate(saved, targetDir, config.releaseStrategy) > 0) return gateFailure();
13664
+ if (runPublishConfigGate(config, targetDir) > 0) return gateFailure();
13258
13665
  const toolchain = runToolchainGate(targetDir);
13259
- if (toolchain.failures > 0) return 1;
13666
+ if (toolchain.failures > 0) return {
13667
+ ...gateFailure(),
13668
+ toolchain: { warned: toolchain.warned }
13669
+ };
13260
13670
  const { ctx, pendingWrites } = createDryRunContext(config);
13261
13671
  logDetectionSummary(ctx);
13262
13672
  const results = await runGenerators(ctx);
13263
13673
  const failed = failedResults(results);
13264
13674
  reportFailures(failed, "repo:sync");
13265
- const actionable = results.filter((r) => {
13675
+ const actionable = actionableResults(results, pendingWrites, targetDir);
13676
+ const toolchainWarnCount = toolchain.warned ? 1 : 0;
13677
+ const toolchainState = { warned: toolchain.warned };
13678
+ if (actionable.length === 0 && failed.length === 0) return upToDateReport(toolchainState);
13679
+ if (actionable.length === 0) return {
13680
+ status: "failed",
13681
+ actionable: [],
13682
+ failed,
13683
+ toolchain: toolchainState,
13684
+ warnCount: 0,
13685
+ exitCode: 1
13686
+ };
13687
+ log$1.warn(`${actionable.length} file(s) would be changed by repo:sync`);
13688
+ for (const r of actionable) logActionableChange(r, targetDir, pendingWrites);
13689
+ if (failed.length === 0 && actionable.every((r) => isWorkflowDirPath(r.filePath))) {
13690
+ const warnCount = actionable.length + toolchainWarnCount;
13691
+ if (!isJsonMode()) rawLog$1(formatWarnMarker(warnCount));
13692
+ return {
13693
+ status: "workflow-drift",
13694
+ actionable,
13695
+ failed: [],
13696
+ toolchain: toolchainState,
13697
+ warnCount,
13698
+ exitCode: 0
13699
+ };
13700
+ }
13701
+ return {
13702
+ status: "drift",
13703
+ actionable,
13704
+ failed,
13705
+ toolchain: toolchainState,
13706
+ warnCount: 0,
13707
+ exitCode: 1
13708
+ };
13709
+ }
13710
+ /**
13711
+ * The results a check run reports as drift: every created/updated/removed result, minus an
13712
+ * `updated` whose new content is byte-equivalent to what is on disk (a generator that rewrites
13713
+ * semantically-identical content is not drift). Extracted from `runCheckReport` to keep that
13714
+ * function inside the lint's statement budget -- the predicate is the drift definition, so it
13715
+ * reads better as a named function anyway.
13716
+ */
13717
+ function actionableResults(results, pendingWrites, targetDir) {
13718
+ return results.filter((r) => {
13266
13719
  if (r.action === "removed") return true;
13267
13720
  if (r.action !== "created" && r.action !== "updated") return false;
13268
13721
  const newContent = pendingWrites.get(r.filePath);
@@ -13273,23 +13726,6 @@ async function runCheck(targetDir) {
13273
13726
  }
13274
13727
  return true;
13275
13728
  });
13276
- const toolchainWarnCount = toolchain.warned ? 1 : 0;
13277
- if (actionable.length === 0 && failed.length === 0) {
13278
- if (toolchainWarnCount > 0) {
13279
- rawLog$1(formatWarnMarker(toolchainWarnCount));
13280
- return 0;
13281
- }
13282
- log$1.success("Repository is up to date.");
13283
- return 0;
13284
- }
13285
- if (actionable.length === 0) return 1;
13286
- log$1.warn(`${actionable.length} file(s) would be changed by repo:sync`);
13287
- for (const r of actionable) logActionableChange(r, targetDir, pendingWrites);
13288
- if (failed.length === 0 && actionable.every((r) => isWorkflowDirPath(r.filePath))) {
13289
- rawLog$1(formatWarnMarker(actionable.length + toolchainWarnCount));
13290
- return 0;
13291
- }
13292
- return 1;
13293
13729
  }
13294
13730
  /** Files that are minified/bundled and produce unreadable diffs. */
13295
13731
  function isBundledFile(filePath) {
@@ -13733,9 +14169,8 @@ function childOutput(result) {
13733
14169
  }
13734
14170
  //#endregion
13735
14171
  //#region src/release/publish/version.ts
13736
- const BRANCH = "changeset-release/main";
13737
14172
  const MAIN_REF = "refs/heads/main";
13738
- const BRANCH_REF = `refs/heads/${BRANCH}`;
14173
+ const BRANCH_REF = `refs/heads/${VERSION_PR_BRANCH}`;
13739
14174
  /**
13740
14175
  * Read one remote ref's SHA, without mutating the local repository or its remote-tracking refs.
13741
14176
  *
@@ -13766,11 +14201,12 @@ function readRemoteRef(executor, config, auth, ref) {
13766
14201
  * overwrite it. That overwrite is not hypothetical: it silently discarded a release, leaving a
13767
14202
  * version PR whose title advertised a bump its own branch no longer contained.
13768
14203
  *
13769
- * Safe because a successor run does not merely *exist*, it *consumes these changesets*:
13770
- * `runRelease` routes on changeset presence before it consults the publish guard, so a run cannot
13771
- * enter publish mode while any changeset is still pending. A version-PR merge landing mid-run is
13772
- * therefore not a way to strand them -- it either carries them still (the next run versions them)
13773
- * or already consumed them (they are released).
14204
+ * Safe because a successor run does not merely *exist*, it *consumes these changesets*: every run
14205
+ * whose HEAD carries pending changesets runs version mode, either directly or after publishing the
14206
+ * version its HEAD bumped (a stale version-PR merge, whose commit carries both). So a successor
14207
+ * versions a superset of these changesets either way, and a version-PR merge landing mid-run is not
14208
+ * a way to strand them -- it either carries them still (the successor versions them after
14209
+ * publishing) or already consumed them (they are released).
13774
14210
  *
13775
14211
  * The remaining assumption is that main advances *solely* via commits that trigger a run: the
13776
14212
  * coverage and health records CI writes go to the `bst/*` orphan branches, never here. If a
@@ -13788,7 +14224,7 @@ function isSuperseded(executor, config, auth, baseSha) {
13788
14224
  return false;
13789
14225
  }
13790
14226
  if (remoteMain === baseSha) return false;
13791
- log$1.warn(`Superseded release run: this job versioned ${baseSha}, but main has advanced to ${remoteMain}. A newer run is versioning a superset of these changesets -- skipping to avoid overwriting its version commit on ${BRANCH}.`);
14227
+ log$1.warn(`Superseded release run: this job versioned ${baseSha}, but main has advanced to ${remoteMain}. A newer run is versioning a superset of these changesets -- skipping to avoid overwriting its version commit on ${VERSION_PR_BRANCH}.`);
13792
14228
  return true;
13793
14229
  }
13794
14230
  /** Pattern matching a dateless version heading like `## 1.2.3` or `## 1.2.3-beta.0`. */
@@ -13895,9 +14331,9 @@ async function runVersionMode(executor, config) {
13895
14331
  const baseShaResult = executor.exec("git", ["rev-parse", "HEAD"], { cwd: config.cwd });
13896
14332
  const baseSha = baseShaResult.exitCode === 0 ? baseShaResult.stdout.trim() : "";
13897
14333
  const leaseTip = config.dryRun ? null : readRemoteRef(executor, config, auth, BRANCH_REF);
13898
- if (leaseTip === null && !config.dryRun) throw new FatalError(`Could not read the current tip of ${BRANCH} from the remote, so this run cannot take a push lease. Refusing to force-push without one -- an unleased push is what allows a concurrent run's version commit to be silently discarded.`);
14334
+ if (leaseTip === null && !config.dryRun) throw new FatalError(`Could not read the current tip of ${VERSION_PR_BRANCH} from the remote, so this run cannot take a push lease. Refusing to force-push without one -- an unleased push is what allows a concurrent run's version commit to be silently discarded.`);
13899
14335
  debug(config, `Base commit: ${baseSha || "(unknown)"}`);
13900
- debug(config, `Lease tip for ${BRANCH}: ${leaseTip === null ? "(dry-run)" : leaseTip || "(absent)"}`);
14336
+ debug(config, `Lease tip for ${VERSION_PR_BRANCH}: ${leaseTip === null ? "(dry-run)" : leaseTip || "(absent)"}`);
13901
14337
  const packagesBefore = executor.listWorkspacePackages(config.cwd);
13902
14338
  debug(config, `Packages before versioning: ${packagesBefore.map((pkg) => `${pkg.name}@${pkg.version}`).join(", ") || "(none)"}`);
13903
14339
  const changesetConfigPath = path.join(config.cwd, ".changeset", "config.json");
@@ -13959,7 +14395,7 @@ async function runVersionMode(executor, config) {
13959
14395
  pr: "none"
13960
14396
  };
13961
14397
  if (leaseTip === null) throw new UnexpectedError("Push lease was never observed on a non-dry-run release");
13962
- debug(config, `Force-pushing version commit to ${BRANCH} (lease: ${leaseTip || "(absent)"})`);
14398
+ debug(config, `Force-pushing version commit to ${VERSION_PR_BRANCH} (lease: ${leaseTip || "(absent)"})`);
13963
14399
  const pushResult = executor.exec("git", [
13964
14400
  ...auth.args,
13965
14401
  "push",
@@ -13968,18 +14404,18 @@ async function runVersionMode(executor, config) {
13968
14404
  `--force-with-lease=${BRANCH_REF}:${leaseTip}`
13969
14405
  ], { cwd: config.cwd });
13970
14406
  debugExec(config, "git push", pushResult);
13971
- if (pushResult.exitCode !== 0) throw new FatalError(`git push to ${BRANCH} failed (exit code ${String(pushResult.exitCode)}). If the lease was rejected, the branch moved after this run started (expected ${leaseTip || "the branch to be absent"}) -- another run pushed a version commit, and overwriting it would discard that release. This job fails rather than clobber it.\n${pushResult.stderr || pushResult.stdout}`);
14407
+ if (pushResult.exitCode !== 0) throw new FatalError(`git push to ${VERSION_PR_BRANCH} failed (exit code ${String(pushResult.exitCode)}). If the lease was rejected, the branch moved after this run started (expected ${leaseTip || "the branch to be absent"}) -- another run pushed a version commit, and overwriting it would discard that release. This job fails rather than clobber it.\n${pushResult.stderr || pushResult.stdout}`);
13972
14408
  const conn = {
13973
14409
  serverUrl: config.serverUrl,
13974
14410
  repository: config.repository,
13975
14411
  token: config.token
13976
14412
  };
13977
- const existingPr = await findOpenPr(executor, conn, BRANCH);
13978
- debug(config, `Existing open PR for ${BRANCH}: ${existingPr === null ? "(none)" : `#${String(existingPr)}`}`);
14413
+ const existingPr = await findOpenPr(executor, conn, VERSION_PR_BRANCH);
14414
+ debug(config, `Existing open PR for ${VERSION_PR_BRANCH}: ${existingPr === null ? "(none)" : `#${String(existingPr)}`}`);
13979
14415
  if (existingPr === null) try {
13980
14416
  await createPr(executor, conn, {
13981
14417
  title,
13982
- head: BRANCH,
14418
+ head: VERSION_PR_BRANCH,
13983
14419
  base: "main",
13984
14420
  body
13985
14421
  });
@@ -13990,7 +14426,7 @@ async function runVersionMode(executor, config) {
13990
14426
  };
13991
14427
  } catch (error) {
13992
14428
  debug(config, `createPr failed, retrying as update: ${describeError(error)}`);
13993
- const racePr = await findOpenPr(executor, conn, BRANCH);
14429
+ const racePr = await findOpenPr(executor, conn, VERSION_PR_BRANCH);
13994
14430
  if (racePr === null) throw error;
13995
14431
  await updatePr(executor, conn, racePr, {
13996
14432
  title,
@@ -14019,30 +14455,32 @@ async function runVersionMode(executor, config) {
14019
14455
  *
14020
14456
  * Evaluated from the workspace package list rather than from the tags this run happened to push, so
14021
14457
  * a rerun whose exact tags and releases all already exist still repairs a `@latest` an earlier
14022
- * crashed run left behind. Each family reads the remote for itself, which is also what lets a
14023
- * rejected lease on one package be re-decided against a fresh read.
14458
+ * crashed run left behind. One remote read per attempt serves every family in the workspace, and a
14459
+ * lease rejected on one package is re-decided against the next attempt's read.
14024
14460
  */
14025
14461
  function moveAllFloatingTags(executor, config, auth, packages, dryRun) {
14026
- const total = {
14027
- moved: [],
14028
- kept: [],
14029
- failed: []
14030
- };
14031
- for (const { family, version } of packages) {
14032
- const outcome = moveFloatingTags(executor, {
14033
- cwd: config.cwd,
14034
- git: {
14035
- args: auth.args,
14036
- url: auth.url
14037
- },
14038
- dryRun,
14039
- verbose: config.verbose
14040
- }, family, [version]);
14041
- total.moved.push(...outcome.moved);
14042
- total.kept.push(...outcome.kept);
14043
- total.failed.push(...outcome.failed);
14462
+ return moveFloatingTags(executor, {
14463
+ cwd: config.cwd,
14464
+ git: {
14465
+ args: auth.args,
14466
+ url: auth.url
14467
+ },
14468
+ dryRun,
14469
+ verbose: config.verbose
14470
+ }, packages);
14471
+ }
14472
+ /**
14473
+ * Whether a tag this run is publishing names a prerelease, read back through the workspace's tag
14474
+ * families rather than by parsing the tag shape here -- `versionOfExact` is the one spelling of that
14475
+ * question, and a floating tag (or any tag no family recognises) answers "not a prerelease", which
14476
+ * fails open to today's behaviour of an unflagged release.
14477
+ */
14478
+ function prereleaseTag(families, tag) {
14479
+ for (const entry of families) {
14480
+ const version = entry.family.versionOfExact(tag);
14481
+ if (version !== void 0) return isPrerelease(version);
14044
14482
  }
14045
- return total;
14483
+ return false;
14046
14484
  }
14047
14485
  /**
14048
14486
  * Push a tag to the remote when it isn't there yet, creating the local tag first
@@ -14069,11 +14507,17 @@ function pushTagToRemote(executor, config, auth, tag, remoteSet) {
14069
14507
  }
14070
14508
  /** Mode 2: publish to npm, push tags, and create Forgejo releases. */
14071
14509
  async function runPublishMode(executor, config) {
14072
- log$1.info("No changesets -- publishing packages");
14073
- const publishResult = executor.exec("pnpm", ["changeset", "publish"], { cwd: config.cwd });
14074
- debugExec(config, "pnpm changeset publish", publishResult);
14075
- if (publishResult.exitCode !== 0) throw new FatalError(`pnpm changeset publish failed (exit code ${String(publishResult.exitCode)}):\n${childOutput(publishResult)}`);
14076
- const stdoutTags = parseNewTags(publishResult.stdout + "\n" + publishResult.stderr);
14510
+ log$1.info("Publishing packages whose versions are not yet on the registry");
14511
+ let stdoutTags;
14512
+ if (config.dryRun) {
14513
+ log$1.info("[dry-run] Would run pnpm changeset publish");
14514
+ stdoutTags = [];
14515
+ } else {
14516
+ const publishResult = executor.exec("pnpm", ["changeset", "publish"], { cwd: config.cwd });
14517
+ debugExec(config, "pnpm changeset publish", publishResult);
14518
+ if (publishResult.exitCode !== 0) throw new FatalError(`pnpm changeset publish failed (exit code ${String(publishResult.exitCode)}):\n${childOutput(publishResult)}`);
14519
+ stdoutTags = parseNewTags(publishResult.stdout + "\n" + publishResult.stderr);
14520
+ }
14077
14521
  debug(config, `Tags from publish stdout: ${stdoutTags.length > 0 ? stdoutTags.join(", ") : "(none)"}`);
14078
14522
  const packages = executor.listWorkspacePackages(config.cwd);
14079
14523
  const expectedTags = computeExpectedTags(packages);
@@ -14129,7 +14573,11 @@ async function runPublishMode(executor, config) {
14129
14573
  for (const tag of allTags) try {
14130
14574
  pushTagToRemote(executor, config, auth, tag, remoteSet);
14131
14575
  debug(config, `Ensuring Forgejo release for ${tag}`);
14132
- if (await ensureRelease(executor, conn, tag, { target: resolveTagCommit(executor, config.cwd, tag) }) === "exists") log$1.warn(`Release for ${tag} already exists -- skipping`);
14576
+ const { status: releaseStatus } = await ensureRelease(executor, conn, tag, {
14577
+ target: resolveTagCommit(executor, config.cwd, tag),
14578
+ prerelease: prereleaseTag(families, tag)
14579
+ });
14580
+ if (releaseStatus === "exists") log$1.warn(`Release for ${tag} already exists -- skipping`);
14133
14581
  else log$1.info(`Created release for ${tag}`);
14134
14582
  } catch (error) {
14135
14583
  errors.push({
@@ -14299,13 +14747,20 @@ const releaseForgejoCommand = defineCommand({
14299
14747
  verbose: {
14300
14748
  type: "boolean",
14301
14749
  description: "Enable detailed debug logging (also enabled by TOOLING_DEBUG env var)"
14750
+ },
14751
+ json: {
14752
+ type: "boolean",
14753
+ description: "Emit the release result as JSON"
14302
14754
  }
14303
14755
  },
14304
14756
  async run({ args }) {
14757
+ const out = createCliOutput(args.json === true);
14758
+ const dryRun = args["dry-run"] === true;
14305
14759
  const result = await runRelease(buildReleaseConfig({
14306
- dryRun: args["dry-run"] === true,
14760
+ dryRun,
14307
14761
  verbose: args.verbose === true || isEnvVerbose()
14308
14762
  }), createRealExecutor$1());
14763
+ out.json(releasePayload(result, dryRun));
14309
14764
  if (result.mode === "publish") {
14310
14765
  const outputFile = process.env["GITHUB_OUTPUT"];
14311
14766
  if (outputFile) try {
@@ -14316,6 +14771,38 @@ const releaseForgejoCommand = defineCommand({
14316
14771
  if (result.mode === "none") process.exitCode = 0;
14317
14772
  }
14318
14773
  });
14774
+ /**
14775
+ * The `--json` payload for a changesets release run.
14776
+ *
14777
+ * `ReleaseResult` is a discriminated union whose variants carry different keys, and a consumer
14778
+ * reading `--json` should not have to reproduce that discrimination to read a field. So every key
14779
+ * is present on every mode, with `[]`/`null` standing in where the variant lacks it: `tags` is the
14780
+ * publish variant's, `pr` the version variant's, and `versioned` the publish variant's optional
14781
+ * second-half marker -- the stale-version-PR-merge row that both published and versioned.
14782
+ */
14783
+ function releasePayload(result, dryRun) {
14784
+ if (result.mode === "version") return {
14785
+ mode: "version",
14786
+ tags: [],
14787
+ pr: result.pr,
14788
+ versioned: null,
14789
+ dryRun
14790
+ };
14791
+ if (result.mode === "publish") return {
14792
+ mode: "publish",
14793
+ tags: result.tags,
14794
+ pr: null,
14795
+ versioned: result.versioned ?? null,
14796
+ dryRun
14797
+ };
14798
+ return {
14799
+ mode: "none",
14800
+ tags: [],
14801
+ pr: null,
14802
+ versioned: null,
14803
+ dryRun
14804
+ };
14805
+ }
14319
14806
  /** Build release config from environment / package.json and CLI flags. */
14320
14807
  function buildReleaseConfig(flags) {
14321
14808
  const resolved = resolveConnection(process.cwd());
@@ -14351,11 +14838,17 @@ async function runRelease(config, executor) {
14351
14838
  log$1.info(`Git identity: ${identity.name} <${identity.email}>`);
14352
14839
  const changesetFiles = executor.listChangesetFiles(config.cwd);
14353
14840
  debug(config, `Changeset files found: ${changesetFiles.length > 0 ? changesetFiles.join(", ") : "(none)"}`);
14841
+ const bumped = headBumpedVersion(executor, config);
14842
+ if (bumped && changesetFiles.length > 0) {
14843
+ log$1.info(`HEAD bumped a version and changesets are still pending (${changesetFiles.join(", ")}) -- publishing the merged version first, then versioning the rest.`);
14844
+ debug(config, "Entering publish mode, then version mode");
14845
+ return publishThenVersion(executor, config);
14846
+ }
14354
14847
  if (changesetFiles.length > 0) {
14355
14848
  debug(config, "Entering version mode");
14356
14849
  return runVersionMode(executor, config);
14357
14850
  }
14358
- if (!headBumpedVersion(executor, config)) {
14851
+ if (!bumped) {
14359
14852
  log$1.info("No changesets, and HEAD bumped no package version -- nothing to release. (Publishing runs on the commit that merges a 'version packages' PR.)");
14360
14853
  debug(config, "Publish guard: HEAD is not a version-bump commit -- skipping publish");
14361
14854
  return { mode: "none" };
@@ -14364,6 +14857,48 @@ async function runRelease(config, executor) {
14364
14857
  return runPublishMode(executor, config);
14365
14858
  }
14366
14859
  /**
14860
+ * The combined row: HEAD both bumped a version and still carries changesets, which is what a
14861
+ * version PR merged while it was behind main lands.
14862
+ *
14863
+ * Publish first: versioning first would re-bump the tree on top of the unreleased bump and publish
14864
+ * the wrong number. Rewriting the version PR is then best-effort -- once packages are out, every
14865
+ * post-release step gates on this run having published, so a failure there is reported rather than
14866
+ * failing the release job. Restoring the checkout afterwards is not best-effort; see
14867
+ * {@link restorePublishedCheckout}.
14868
+ */
14869
+ async function publishThenVersion(executor, config) {
14870
+ const published = await runPublishMode(executor, config);
14871
+ if (published.mode !== "publish") return runVersionMode(executor, config);
14872
+ const head = executor.exec("git", ["rev-parse", "HEAD"], { cwd: config.cwd });
14873
+ const publishedHead = head.exitCode === 0 ? head.stdout.trim() : "";
14874
+ if (publishedHead === "") {
14875
+ const detail = `Could not read HEAD after publishing, so the changesets still pending are not versioned by this run: ${head.stderr || head.stdout} The version PR waits for the next push to main rather than versioning a checkout this run could not put back.`;
14876
+ log$1.warn(detail);
14877
+ note(detail, "Version PR not rewritten");
14878
+ return {
14879
+ ...published,
14880
+ versioned: "failed"
14881
+ };
14882
+ }
14883
+ try {
14884
+ const versioned = await runVersionMode(executor, config);
14885
+ return {
14886
+ ...published,
14887
+ versioned: versioned.mode === "version" ? versioned.pr : "none"
14888
+ };
14889
+ } catch (error) {
14890
+ const detail = `The packages are published and tagged, but the version PR was not rewritten by this run: ${describeError(error)}. The next push to main rewrites it.`;
14891
+ log$1.warn(detail);
14892
+ note(detail, "Version PR not rewritten");
14893
+ return {
14894
+ ...published,
14895
+ versioned: "failed"
14896
+ };
14897
+ } finally {
14898
+ restorePublishedCheckout(executor, config, publishedHead);
14899
+ }
14900
+ }
14901
+ /**
14367
14902
  * Whether the HEAD commit bumped an existing package's version relative to its
14368
14903
  * first parent -- the signal that a "version packages" PR just merged. Returns
14369
14904
  * false for a root commit (no parent) and for ordinary commits that don't
@@ -14400,6 +14935,30 @@ function headBumpedVersion(executor, config) {
14400
14935
  debug(config, `Publish guard: HEAD bumped an existing version = ${String(bumped)}`);
14401
14936
  return bumped;
14402
14937
  }
14938
+ /**
14939
+ * Put the checkout back on the published commit after the combined row's version half.
14940
+ *
14941
+ * The version half rewrites every `package.json`, `CHANGELOG.md` and `pnpm-lock.yaml` in place and
14942
+ * commits the result, so the tree it leaves behind names the *next* version. The steps gated on
14943
+ * this run's `published` output -- `docker:publish`, `release:assets` -- run in this same job on
14944
+ * this same checkout and take their version from that working tree, so an unrestored tree ships
14945
+ * images tagged with a version no registry or git tag carries, and the forward-only floating-tag
14946
+ * rule then moves `latest` onto it. Discarding the local version commit is safe: `runVersionMode`
14947
+ * has already force-pushed it to the version PR branch, or deliberately declined to push it.
14948
+ *
14949
+ * A failed reset is fatal, and the throw is also what withholds the `published` output -- a
14950
+ * post-release step reading a tree this run left half-versioned is the state this exists to
14951
+ * prevent, and a red job is the only honest answer to it.
14952
+ */
14953
+ function restorePublishedCheckout(executor, config, publishedHead) {
14954
+ debug(config, `Restoring the checkout to the published commit ${publishedHead}`);
14955
+ const reset = executor.exec("git", [
14956
+ "reset",
14957
+ "--hard",
14958
+ publishedHead
14959
+ ], { cwd: config.cwd });
14960
+ if (reset.exitCode !== 0) throw new FatalError(`Could not restore the checkout to the published commit ${publishedHead}: ${reset.stderr || reset.stdout}`);
14961
+ }
14403
14962
  //#endregion
14404
14963
  //#region src/release/publish/dispatch.ts
14405
14964
  const INPUT_FLAG = "--input";
@@ -14448,8 +15007,8 @@ function parseInputs(raw) {
14448
15007
  * repository other than the current one, that token must have rights on it.
14449
15008
  */
14450
15009
  async function dispatchWorkflow(resolved, options, executor) {
14451
- if (resolved.type === "forgejo") await dispatchForgejo(resolved.conn, options, executor);
14452
- else dispatchGitHub(options, executor);
15010
+ if (resolved.type === "forgejo") return dispatchForgejo(resolved.conn, options, executor);
15011
+ return dispatchGitHub(options, executor);
14453
15012
  }
14454
15013
  async function dispatchForgejo(conn, options, executor) {
14455
15014
  const repo = options.repo ?? conn.repository;
@@ -14470,6 +15029,13 @@ async function dispatchForgejo(conn, options, executor) {
14470
15029
  ref: options.ref
14471
15030
  }));
14472
15031
  log$1.info(`Dispatched ${options.workflow} on ${repo} (ref: ${options.ref})`);
15032
+ return {
15033
+ host: "forgejo",
15034
+ repo,
15035
+ workflow: options.workflow,
15036
+ ref: options.ref,
15037
+ inputs: options.inputs
15038
+ };
14473
15039
  }
14474
15040
  function dispatchGitHub(options, executor) {
14475
15041
  const inputFlags = Object.entries(options.inputs).flatMap(([k, v]) => ["-f", `${k}=${v}`]);
@@ -14484,8 +15050,15 @@ function dispatchGitHub(options, executor) {
14484
15050
  ...inputFlags
14485
15051
  ], { cwd: process.cwd() });
14486
15052
  if (result.exitCode !== 0) throw new FatalError(`Failed to dispatch workflow on GitHub: ${result.stderr || result.stdout || "unknown error"}`);
14487
- const repo = options.repo ?? parseRepositoryUrl(process.cwd())?.repository ?? "current repository";
14488
- log$1.info(`Dispatched ${options.workflow} on ${repo} (ref: ${options.ref})`);
15053
+ const repo = options.repo ?? parseRepositoryUrl(process.cwd())?.repository ?? null;
15054
+ log$1.info(`Dispatched ${options.workflow} on ${repo ?? "current repository"} (ref: ${options.ref})`);
15055
+ return {
15056
+ host: "github",
15057
+ repo,
15058
+ workflow: options.workflow,
15059
+ ref: options.ref,
15060
+ inputs: options.inputs
15061
+ };
14489
15062
  }
14490
15063
  //#endregion
14491
15064
  //#region src/commands/release-trigger.ts
@@ -14509,19 +15082,28 @@ const releaseTriggerCommand = defineCommand({
14509
15082
  type: "string",
14510
15083
  description: "Create a prerelease with the given tag (e.g., beta, alpha)",
14511
15084
  required: false
15085
+ },
15086
+ json: {
15087
+ type: "boolean",
15088
+ description: "Emit the dispatch result as JSON"
14512
15089
  }
14513
15090
  },
14514
15091
  async run({ args }) {
15092
+ const out = createCliOutput(args.json === true);
14515
15093
  const ref = args.ref ?? "main";
14516
15094
  const inputs = {};
14517
15095
  if (args.bump) inputs["bump"] = args.bump;
14518
15096
  if (args.prerelease) inputs["prerelease"] = args.prerelease;
14519
- await dispatchWorkflow(resolveConnection(process.cwd()), {
15097
+ const result = await dispatchWorkflow(resolveConnection(process.cwd()), {
14520
15098
  repo: void 0,
14521
15099
  workflow: "release.yml",
14522
15100
  ref,
14523
15101
  inputs
14524
15102
  }, createRealExecutor$1());
15103
+ out.json({
15104
+ status: "dispatched",
15105
+ ...result
15106
+ });
14525
15107
  }
14526
15108
  });
14527
15109
  //#endregion
@@ -14551,18 +15133,27 @@ const workflowDispatchCommand = defineCommand({
14551
15133
  type: "string",
14552
15134
  description: "Workflow input as key=value (repeatable)",
14553
15135
  required: false
15136
+ },
15137
+ json: {
15138
+ type: "boolean",
15139
+ description: "Emit the dispatch result as JSON"
14554
15140
  }
14555
15141
  },
14556
15142
  async run({ args, rawArgs }) {
15143
+ const out = createCliOutput(args.json === true);
14557
15144
  if (!args.workflow) throw new FatalError("--workflow is required");
14558
15145
  const ref = args.ref ?? "main";
14559
15146
  const inputs = parseInputs(collectInputArgs(rawArgs));
14560
- await dispatchWorkflow(resolveConnection(process.cwd()), {
15147
+ const result = await dispatchWorkflow(resolveConnection(process.cwd()), {
14561
15148
  repo: args.repo,
14562
15149
  workflow: args.workflow,
14563
15150
  ref,
14564
15151
  inputs
14565
15152
  }, createRealExecutor$1());
15153
+ out.json({
15154
+ status: "dispatched",
15155
+ ...result
15156
+ });
14566
15157
  }
14567
15158
  });
14568
15159
  //#endregion
@@ -14572,16 +15163,35 @@ const createForgejoReleaseCommand = defineCommand({
14572
15163
  name: "forgejo:create-release",
14573
15164
  description: "Create a Forgejo release for a given tag"
14574
15165
  },
14575
- args: { tag: {
14576
- type: "string",
14577
- description: "Git tag to create a release for",
14578
- required: true
14579
- } },
15166
+ args: {
15167
+ tag: {
15168
+ type: "string",
15169
+ description: "Git tag to create a release for",
15170
+ required: true
15171
+ },
15172
+ json: {
15173
+ type: "boolean",
15174
+ description: "Emit the release result as JSON"
15175
+ }
15176
+ },
14580
15177
  async run({ args }) {
15178
+ const out = createCliOutput(args.json === true);
14581
15179
  const conn = requireForgejo(process.cwd(), "forgejo:create-release");
14582
15180
  const executor = createRealExecutor$1();
15181
+ const prerelease = tagIsPrerelease(args.tag);
14583
15182
  const target = resolveTagCommit(executor, process.cwd(), args.tag);
14584
- if (await ensureRelease(executor, conn, args.tag, { target }) === "exists") log$1.info(`Release for ${args.tag} already exists -- skipping`);
15183
+ const outcome = await ensureRelease(executor, conn, args.tag, {
15184
+ target,
15185
+ prerelease
15186
+ });
15187
+ out.json({
15188
+ status: outcome.status,
15189
+ tag: args.tag,
15190
+ target: target ?? null,
15191
+ prerelease,
15192
+ release: outcome.release
15193
+ });
15194
+ if (outcome.status === "exists") log$1.info(`Release for ${args.tag} already exists -- skipping`);
14585
15195
  else log$1.info(`Created Forgejo release for ${args.tag}`);
14586
15196
  }
14587
15197
  });
@@ -14667,8 +15277,6 @@ z.object({
14667
15277
  });
14668
15278
  //#endregion
14669
15279
  //#region src/release/publish/version-merge.ts
14670
- /** The changesets version-PR branch this command merges. Not configurable. */
14671
- const HEAD_BRANCH = "changeset-release/main";
14672
15280
  /**
14673
15281
  * Emptiness outranks mergeability: an empty version PR must be reported as content-free, not as a
14674
15282
  * blocked merge -- that is the clobbered state this gate exists to catch. `unconfirmed` exists
@@ -14785,14 +15393,22 @@ function evaluateConfirmingRead(second, first, delayMs) {
14785
15393
  evidence: `Its merge base moved from ${first.mergeBase} to ${second.mergeBase} between the two reads.`
14786
15394
  };
14787
15395
  }
15396
+ /** The passing verdict every returned result carries -- see {@link VersionMergeResult.verdict}. */
15397
+ const PASSED_VERDICT = {
15398
+ ok: true,
15399
+ retryable: null,
15400
+ reason: null
15401
+ };
14788
15402
  async function mergeVersionPrForgejo(executor, conn, options) {
14789
- const prNumber = await findOpenPr(executor, conn, HEAD_BRANCH);
14790
- if (prNumber === null) throw new FatalError(`No open PR found for branch ${HEAD_BRANCH}`);
15403
+ const prNumber = await findOpenPr(executor, conn, VERSION_PR_BRANCH);
15404
+ if (prNumber === null) throw new FatalError(`No open PR found for branch ${VERSION_PR_BRANCH}`);
14791
15405
  const sleep$1 = options.sleep ?? sleep;
14792
15406
  const recheckDelayMs = envMsOverride(process.env["BST_TEST_VERSION_MERGE_RECHECK_MS"], RECHECK_DELAY_MS);
14793
15407
  const firstGate = await readMergeGate(executor, conn, prNumber);
14794
15408
  logGateRead(prNumber, firstGate);
14795
15409
  let verdict = evaluateVersionPr(stateFromGate(prNumber, firstGate, mergeabilityFromFirstRead(firstGate.mergeable)));
15410
+ let decidedFrom = firstGate;
15411
+ let rechecked = false;
14796
15412
  if (needsRecheck(verdict)) {
14797
15413
  log$1.info(`changesets:merge: PR #${String(prNumber)}'s mergeability is not confirmed yet -- re-reading after ${String(recheckDelayMs)}ms`);
14798
15414
  await sleep$1(recheckDelayMs);
@@ -14800,18 +15416,37 @@ async function mergeVersionPrForgejo(executor, conn, options) {
14800
15416
  logGateRead(prNumber, secondGate);
14801
15417
  const confirming = evaluateConfirmingRead(secondGate, firstGate, recheckDelayMs);
14802
15418
  verdict = evaluateVersionPr(stateFromGate(prNumber, secondGate, confirming.mergeable, confirming.evidence));
15419
+ decidedFrom = secondGate;
15420
+ rechecked = true;
14803
15421
  }
14804
15422
  const headSha = headShaOrThrow(verdict);
15423
+ const outcome = {
15424
+ host: "forgejo",
15425
+ number: prNumber,
15426
+ headSha,
15427
+ mergeBase: decidedFrom.mergeBase,
15428
+ changedFiles: decidedFrom.changedFiles,
15429
+ branch: VERSION_PR_BRANCH,
15430
+ rechecked,
15431
+ verdict: { ...PASSED_VERDICT }
15432
+ };
14805
15433
  if (options.dryRun) {
14806
- log$1.info(`[dry-run] Would merge PR #${String(prNumber)} at ${headSha} and delete branch ${HEAD_BRANCH}`);
14807
- return;
15434
+ log$1.info(`[dry-run] Would merge PR #${String(prNumber)} at ${headSha} and delete branch ${VERSION_PR_BRANCH}`);
15435
+ return {
15436
+ ...outcome,
15437
+ status: "would-merge"
15438
+ };
14808
15439
  }
14809
15440
  await mergePr(executor, conn, prNumber, {
14810
15441
  method: "merge",
14811
15442
  deleteBranch: true,
14812
15443
  expectedHeadSha: headSha
14813
15444
  });
14814
- log$1.info(`Merged PR #${String(prNumber)} and deleted branch ${HEAD_BRANCH}`);
15445
+ log$1.info(`Merged PR #${String(prNumber)} and deleted branch ${VERSION_PR_BRANCH}`);
15446
+ return {
15447
+ ...outcome,
15448
+ status: "merged"
15449
+ };
14815
15450
  }
14816
15451
  const GitHubVersionPrSchema = z.object({
14817
15452
  number: z.number(),
@@ -14825,13 +15460,13 @@ function mergeVersionPrGitHub(executor, options) {
14825
15460
  const view = executor.exec("gh", [
14826
15461
  "pr",
14827
15462
  "view",
14828
- HEAD_BRANCH,
15463
+ VERSION_PR_BRANCH,
14829
15464
  "--json",
14830
15465
  "number,state,isDraft,mergeable,headRefOid,changedFiles"
14831
15466
  ], { cwd: process.cwd() });
14832
- if (view.exitCode !== 0) throw new FatalError(`Failed to read PR for branch ${HEAD_BRANCH}: ${view.stderr || view.stdout || "unknown error"}`);
15467
+ if (view.exitCode !== 0) throw new FatalError(`Failed to read PR for branch ${VERSION_PR_BRANCH}: ${view.stderr || view.stdout || "unknown error"}`);
14833
15468
  const parsed = GitHubVersionPrSchema.safeParse(JSON.parse(view.stdout));
14834
- if (!parsed.success) throw new FatalError(`Unexpected 'gh pr view' response for branch ${HEAD_BRANCH}: ${parsed.error.message}`);
15469
+ if (!parsed.success) throw new FatalError(`Unexpected 'gh pr view' response for branch ${VERSION_PR_BRANCH}: ${parsed.error.message}`);
14835
15470
  const data = parsed.data;
14836
15471
  const emptiness = data.changedFiles === 0 ? "empty" : "non-empty";
14837
15472
  const mergeable = data.mergeable === "CONFLICTING" ? "conflicted" : "mergeable";
@@ -14844,21 +15479,38 @@ function mergeVersionPrGitHub(executor, options) {
14844
15479
  headSha: data.headRefOid,
14845
15480
  emptiness
14846
15481
  }));
15482
+ const outcome = {
15483
+ host: "github",
15484
+ number: data.number,
15485
+ headSha,
15486
+ mergeBase: null,
15487
+ changedFiles: data.changedFiles,
15488
+ branch: VERSION_PR_BRANCH,
15489
+ rechecked: false,
15490
+ verdict: { ...PASSED_VERDICT }
15491
+ };
14847
15492
  if (options.dryRun) {
14848
- log$1.info(`[dry-run] Would merge PR #${String(data.number)} at ${headSha} and delete branch ${HEAD_BRANCH}`);
14849
- return;
15493
+ log$1.info(`[dry-run] Would merge PR #${String(data.number)} at ${headSha} and delete branch ${VERSION_PR_BRANCH}`);
15494
+ return {
15495
+ ...outcome,
15496
+ status: "would-merge"
15497
+ };
14850
15498
  }
14851
15499
  const result = executor.exec("gh", [
14852
15500
  "pr",
14853
15501
  "merge",
14854
- HEAD_BRANCH,
15502
+ VERSION_PR_BRANCH,
14855
15503
  "--merge",
14856
15504
  "--delete-branch",
14857
15505
  "--match-head-commit",
14858
15506
  headSha
14859
15507
  ], { cwd: process.cwd() });
14860
15508
  if (result.exitCode !== 0) throw new FatalError(`Failed to merge PR: ${result.stderr || result.stdout || "unknown error"}`);
14861
- log$1.info(`Merged changesets PR and deleted branch ${HEAD_BRANCH}`);
15509
+ log$1.info(`Merged changesets PR and deleted branch ${VERSION_PR_BRANCH}`);
15510
+ return {
15511
+ ...outcome,
15512
+ status: "merged"
15513
+ };
14862
15514
  }
14863
15515
  //#endregion
14864
15516
  //#region src/commands/changesets-merge.ts
@@ -14867,16 +15519,23 @@ const releaseMergeCommand = defineCommand({
14867
15519
  name: "changesets:merge",
14868
15520
  description: "Merge the open changesets version PR"
14869
15521
  },
14870
- args: { "dry-run": {
14871
- type: "boolean",
14872
- description: "Show what would be merged without actually merging"
14873
- } },
15522
+ args: {
15523
+ "dry-run": {
15524
+ type: "boolean",
15525
+ description: "Show what would be merged without actually merging"
15526
+ },
15527
+ json: {
15528
+ type: "boolean",
15529
+ description: "Emit the merge result as JSON"
15530
+ }
15531
+ },
14874
15532
  async run({ args }) {
15533
+ const out = createCliOutput(args.json === true);
14875
15534
  const dryRun = args["dry-run"] === true;
14876
15535
  const resolved = resolveConnection(process.cwd());
14877
15536
  const executor = createRealExecutor$1();
14878
- if (resolved.type === "forgejo") await mergeVersionPrForgejo(executor, resolved.conn, { dryRun });
14879
- else mergeVersionPrGitHub(executor, { dryRun });
15537
+ const result = resolved.type === "forgejo" ? await mergeVersionPrForgejo(executor, resolved.conn, { dryRun }) : mergeVersionPrGitHub(executor, { dryRun });
15538
+ out.json(result);
14880
15539
  }
14881
15540
  });
14882
15541
  //#endregion
@@ -15559,7 +16218,39 @@ async function readGitHubPrUrl(executor, cwd, prNumber, signal = null) {
15559
16218
  signal
15560
16219
  });
15561
16220
  if (result.exitCode !== 0) return null;
15562
- return result.stdout.trim() || null;
16221
+ return urlFromGhStdout(result.stdout);
16222
+ }
16223
+ /**
16224
+ * The pull request number carried by a PR's web URL, or `null` when the URL does not end in one.
16225
+ *
16226
+ * `gh pr create` prints only the URL, so on the GitHub create path the number is not returned by
16227
+ * the forge -- but the URL's last path segment *is* the number, and the number is the field every
16228
+ * machine caller of this module wants. Deriving it here keeps the payload's `number` key honest on
16229
+ * both forges instead of silently null on one. Anything that is not a trailing `/pull/<digits>` --
16230
+ * a proxy URL, a bare host, an unexpected `gh` message -- yields `null` rather than a guess.
16231
+ */
16232
+ function prNumberFromUrl(url) {
16233
+ if (url === null) return null;
16234
+ const match = /\/pull\/(\d+)\/?$/u.exec(url);
16235
+ if (match?.[1] === void 0) return null;
16236
+ return Number.parseInt(match[1], 10);
16237
+ }
16238
+ /**
16239
+ * The URL out of a `gh` invocation's stdout.
16240
+ *
16241
+ * `gh` has printed upgrade and warning lines beside the URL, so the whole blob is not the URL:
16242
+ * handing it to {@link prNumberFromUrl}, which end-anchors, would silently null the number and
16243
+ * misreport the `url` field for a run that in fact succeeded. The URL is recovered as the last
16244
+ * line that is a pull request URL -- last because a notice can land on either side of it. It must
16245
+ * end in `/pull/<digits>`, not merely be URL-shaped, because gh's upgrade notice carries a bare
16246
+ * release URL of its own on its second line. When no line matches, the trimmed stdout is returned
16247
+ * unchanged, which is what this function did before and keeps an unexpected-but-single-line shape
16248
+ * reported rather than dropped.
16249
+ */
16250
+ function urlFromGhStdout(stdout) {
16251
+ const trimmed = stdout.trim();
16252
+ if (trimmed === "") return null;
16253
+ return trimmed.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line !== "").findLast((line) => /^https?:\/\/\S*\/pull\/\d+\/?$/u.test(line)) ?? trimmed;
15563
16254
  }
15564
16255
  async function upsertGitHub(executor, cwd, input, draft, signal = null) {
15565
16256
  const { existing, shape, patch } = input;
@@ -15583,10 +16274,11 @@ async function upsertGitHub(executor, cwd, input, draft, signal = null) {
15583
16274
  signal
15584
16275
  });
15585
16276
  if (result.exitCode !== 0) throw new FatalError(`Failed to create PR: ${result.stderr || result.stdout}`);
16277
+ const url = urlFromGhStdout(result.stdout);
15586
16278
  return {
15587
16279
  action: "created",
15588
- number: null,
15589
- url: result.stdout.trim() || null,
16280
+ number: prNumberFromUrl(url),
16281
+ url,
15590
16282
  ...shape
15591
16283
  };
15592
16284
  }
@@ -15605,7 +16297,7 @@ async function upsertGitHub(executor, cwd, input, draft, signal = null) {
15605
16297
  signal
15606
16298
  });
15607
16299
  if (result.exitCode !== 0) throw new FatalError(`Failed to update PR #${String(existing)}: ${result.stderr}`);
15608
- url = result.stdout.trim() || null;
16300
+ url = urlFromGhStdout(result.stdout);
15609
16301
  }
15610
16302
  if (draft === false) {
15611
16303
  const readyResult = await executor.execStream("gh", [
@@ -15702,12 +16394,17 @@ const prCreateCommand = defineCommand({
15702
16394
  type: "boolean",
15703
16395
  description: "Show what would happen without pushing"
15704
16396
  },
16397
+ json: {
16398
+ type: "boolean",
16399
+ description: "Emit the PR result as JSON"
16400
+ },
15705
16401
  verbose: {
15706
16402
  type: "boolean",
15707
16403
  description: "Emit detailed debug output"
15708
16404
  }
15709
16405
  },
15710
16406
  async run({ args }) {
16407
+ const out = createCliOutput(args.json === true);
15711
16408
  const body = readBodySource(args.body ?? void 0, args["body-file"] ?? void 0);
15712
16409
  const result = await runPrUpsert(createRealExecutor$1(), {
15713
16410
  cwd: process.cwd(),
@@ -15721,6 +16418,10 @@ const prCreateCommand = defineCommand({
15721
16418
  dryRun: args["dry-run"] === true,
15722
16419
  verbose: args.verbose === true || isEnvVerbose()
15723
16420
  });
16421
+ out.json({
16422
+ ...result,
16423
+ dryRun: args["dry-run"] === true
16424
+ });
15724
16425
  const target = `${result.branch} -> ${result.base}`;
15725
16426
  const opening = result.action === "created" || result.action === "would-create";
15726
16427
  if (result.action === "would-create" || result.action === "would-update") {
@@ -15780,12 +16481,17 @@ const prUpdateCommand = defineCommand({
15780
16481
  type: "boolean",
15781
16482
  description: "Show what would happen without pushing"
15782
16483
  },
16484
+ json: {
16485
+ type: "boolean",
16486
+ description: "Emit the PR result as JSON"
16487
+ },
15783
16488
  verbose: {
15784
16489
  type: "boolean",
15785
16490
  description: "Emit detailed debug output"
15786
16491
  }
15787
16492
  },
15788
16493
  async run({ args }) {
16494
+ const out = createCliOutput(args.json === true);
15789
16495
  const body = readBodySource(args.body ?? void 0, args["body-file"] ?? void 0);
15790
16496
  const rewrites = args.title !== void 0 || body !== void 0;
15791
16497
  const result = await runPrUpsert(createRealExecutor$1(), {
@@ -15800,6 +16506,11 @@ const prUpdateCommand = defineCommand({
15800
16506
  dryRun: args["dry-run"] === true,
15801
16507
  verbose: args.verbose === true || isEnvVerbose()
15802
16508
  });
16509
+ out.json({
16510
+ ...result,
16511
+ rewrites,
16512
+ dryRun: args["dry-run"] === true
16513
+ });
15803
16514
  const target = `${result.branch} -> ${result.base}`;
15804
16515
  const label = result.number === null ? "PR" : `PR #${String(result.number)}`;
15805
16516
  if (result.action === "would-create" || result.action === "would-update") {
@@ -17696,79 +18407,6 @@ function renderPrChecks(report) {
17696
18407
  return lines.join("\n");
17697
18408
  }
17698
18409
  //#endregion
17699
- //#region src/utils/cli-output.ts
17700
- const stdout = (msg) => console.log(msg);
17701
- function initialState() {
17702
- return {
17703
- payload: void 0,
17704
- hasPayload: false,
17705
- error: void 0,
17706
- hasError: false,
17707
- notices: [],
17708
- wrote: false
17709
- };
17710
- }
17711
- let state = initialState();
17712
- function isJsonRecord(value) {
17713
- return typeof value === "object" && value !== null && !Array.isArray(value);
17714
- }
17715
- function createCliOutput(jsonMode) {
17716
- const effective = jsonMode || isJsonMode();
17717
- return {
17718
- json: (payload) => {
17719
- if (!effective) return;
17720
- if (state.hasPayload) log$1.debug("cli-output: a second json() call overwrote the first -- last call wins.");
17721
- state.payload = payload;
17722
- state.hasPayload = true;
17723
- },
17724
- text: (message) => {
17725
- if (!effective) stdout(message);
17726
- },
17727
- notice: (message) => {
17728
- if (effective) state.notices.push(redact(message));
17729
- else stdout(message);
17730
- }
17731
- };
17732
- }
17733
- /** Replace what {@link flushJsonOutput} writes with an error document, discarding any payload
17734
- * already recorded by `json()` -- buffered notices are not discarded, they fold into the error
17735
- * document the same way they fold into a payload. */
17736
- function recordJsonError(document) {
17737
- state.error = document;
17738
- state.hasError = true;
17739
- }
17740
- function buildFlushDocument() {
17741
- let base;
17742
- if (state.hasError) base = state.error;
17743
- else if (state.hasPayload) base = state.payload;
17744
- if (base === void 0) return void 0;
17745
- if (state.notices.length === 0) return base;
17746
- if (isJsonRecord(base)) return {
17747
- ...base,
17748
- notices: state.notices
17749
- };
17750
- return {
17751
- payload: base,
17752
- notices: state.notices
17753
- };
17754
- }
17755
- /** Write the single recorded document -- the error document if `recordJsonError` was called,
17756
- * otherwise the recorded payload, each with buffered notices folded in under `notices` when
17757
- * the buffer is non-empty -- and report whether anything was written. */
17758
- function flushJsonOutput() {
17759
- const document = buildFlushDocument();
17760
- if (document === void 0) return false;
17761
- stdout(JSON.stringify(document));
17762
- state.wrote = true;
17763
- return true;
17764
- }
17765
- /** Whether {@link flushJsonOutput} has already written a document this process -- the guard the
17766
- * premature-exit net (`cli-json.ts`) reads to tell a normal flush from a process that died with
17767
- * the command still outstanding. */
17768
- function jsonDocumentWritten() {
17769
- return state.wrote;
17770
- }
17771
- //#endregion
17772
18410
  //#region src/commands/pr-checks.ts
17773
18411
  const prChecksCommand = defineCommand({
17774
18412
  meta: {
@@ -18279,9 +18917,12 @@ async function runSimpleRelease(executor, config) {
18279
18917
  if (!tag) throw new FatalError("Could not determine the new tag from git describe");
18280
18918
  log$1.info(`Version ${version} tagged as ${tag}`);
18281
18919
  if (config.dryRun) {
18282
- const slidingTags = config.noSlidingTags ? [] : computeSlidingTags(version);
18920
+ const prerelease = isPrerelease(version);
18921
+ let slidingTags = [];
18922
+ if (!config.noSlidingTags && !prerelease) slidingTags = computeSlidingTags(version);
18283
18923
  log$1.info(`[dry-run] Would push to origin with --follow-tags`);
18284
- if (slidingTags.length > 0) log$1.info(`[dry-run] Would create sliding tags: ${slidingTags.join(", ")}`);
18924
+ if (prerelease && !config.noSlidingTags) log$1.info(`[dry-run] Floating tags will not move: ${version} is a prerelease`);
18925
+ else if (slidingTags.length > 0) log$1.info(`[dry-run] Would create sliding tags: ${slidingTags.join(", ")}`);
18285
18926
  if (!config.noRelease && config.platform) log$1.info(`[dry-run] Would create ${config.platform.type} release for ${tag}`);
18286
18927
  return {
18287
18928
  version,
@@ -18338,12 +18979,15 @@ async function runSimpleRelease(executor, config) {
18338
18979
  },
18339
18980
  dryRun: false,
18340
18981
  verbose: config.verbose
18341
- }, simpleTagFamily(), [version]), false);
18982
+ }, [{
18983
+ family: simpleTagFamily(),
18984
+ version
18985
+ }]), false);
18342
18986
  }
18343
18987
  let releaseCreated = false;
18344
18988
  if (!config.noRelease && config.platform) {
18345
18989
  debug(config, `Creating ${config.platform.type} release for ${tag}`);
18346
- releaseCreated = await createPlatformRelease(executor, config, tag);
18990
+ releaseCreated = await createPlatformRelease(executor, config, tag, version);
18347
18991
  }
18348
18992
  return {
18349
18993
  version,
@@ -18353,23 +18997,29 @@ async function runSimpleRelease(executor, config) {
18353
18997
  releaseCreated
18354
18998
  };
18355
18999
  }
18356
- async function createPlatformRelease(executor, config, tag) {
19000
+ async function createPlatformRelease(executor, config, tag, version) {
18357
19001
  if (!config.platform) return false;
18358
19002
  if (config.platform.type === "forgejo") {
18359
19003
  const target = resolveTagCommit(executor, config.cwd, tag);
18360
- if (await ensureRelease(executor, config.platform.conn, tag, { target }) === "exists") {
19004
+ const { status: releaseStatus } = await ensureRelease(executor, config.platform.conn, tag, {
19005
+ target,
19006
+ prerelease: isPrerelease(version)
19007
+ });
19008
+ if (releaseStatus === "exists") {
18361
19009
  debug(config, `Release for ${tag} already exists, skipping`);
18362
19010
  return false;
18363
19011
  }
18364
19012
  log$1.info(`Created Forgejo release for ${tag}`);
18365
19013
  return true;
18366
19014
  }
18367
- const ghResult = executor.exec("gh", [
19015
+ const ghArgs = [
18368
19016
  "release",
18369
19017
  "create",
18370
19018
  tag,
18371
19019
  "--generate-notes"
18372
- ], { cwd: config.cwd });
19020
+ ];
19021
+ if (isPrerelease(version)) ghArgs.push("--prerelease");
19022
+ const ghResult = executor.exec("gh", ghArgs, { cwd: config.cwd });
18373
19023
  debugExec(config, "gh release create", ghResult);
18374
19024
  if (ghResult.exitCode !== 0) {
18375
19025
  log$1.warn(`Warning: Failed to create GitHub release: ${ghResult.stderr || ghResult.stdout}`);
@@ -18402,7 +19052,7 @@ const releaseSimpleCommand = defineCommand({
18402
19052
  "sliding-tags": {
18403
19053
  type: "boolean",
18404
19054
  default: true,
18405
- description: "Create the sliding major/minor version tags vX and vX.Y (default). --no-sliding-tags skips them."
19055
+ description: "Create the floating tags latest, vX and vX.Y for a release (default); a prerelease never moves them. --no-sliding-tags skips them entirely."
18406
19056
  },
18407
19057
  release: {
18408
19058
  type: "boolean",
@@ -18420,9 +19070,14 @@ const releaseSimpleCommand = defineCommand({
18420
19070
  prerelease: {
18421
19071
  type: "string",
18422
19072
  description: "Create a prerelease with the given tag (e.g., beta, alpha)"
19073
+ },
19074
+ json: {
19075
+ type: "boolean",
19076
+ description: "Emit the release result as JSON"
18423
19077
  }
18424
19078
  },
18425
19079
  async run({ args }) {
19080
+ const out = createCliOutput(args.json === true);
18426
19081
  const cwd = process.cwd();
18427
19082
  const verbose = args.verbose === true || isEnvVerbose();
18428
19083
  const noRelease = args.release === false;
@@ -18450,6 +19105,10 @@ const releaseSimpleCommand = defineCommand({
18450
19105
  releaseToken: resolveSimpleReleaseToken()
18451
19106
  };
18452
19107
  const result = await runSimpleRelease(createRealExecutor$1(), config);
19108
+ out.json({
19109
+ ...result,
19110
+ dryRun: config.dryRun
19111
+ });
18453
19112
  if (result.pushed) {
18454
19113
  const outputFile = process.env["GITHUB_OUTPUT"];
18455
19114
  if (outputFile) try {
@@ -19899,11 +20558,29 @@ const AuditEntrySchema = z.object({
19899
20558
  decision: z.enum([
19900
20559
  "allow",
19901
20560
  "deny",
19902
- "passthrough"
19903
- ]),
20561
+ "passthrough",
20562
+ "none"
20563
+ ]).transform((decision) => decision === "passthrough" ? "none" : decision),
19904
20564
  rule: z.string().nullable(),
19905
- reason: z.string().nullable()
20565
+ reason: z.string().nullable(),
20566
+ permissionMode: z.string().optional()
19906
20567
  });
20568
+ function countDecisions(entries) {
20569
+ const counts = {
20570
+ allow: 0,
20571
+ deny: 0,
20572
+ none: 0
20573
+ };
20574
+ for (const entry of entries) counts[entry.decision]++;
20575
+ return counts;
20576
+ }
20577
+ /** Count entries per `permissionMode`, skipping entries that carry none -- older log lines never
20578
+ * set the field at all. */
20579
+ function countByMode(entries) {
20580
+ const counts = {};
20581
+ for (const entry of entries) if (entry.permissionMode !== void 0) counts[entry.permissionMode] = (counts[entry.permissionMode] ?? 0) + 1;
20582
+ return counts;
20583
+ }
19907
20584
  function parseAuditLog(logPath) {
19908
20585
  if (!existsSync(logPath)) return [];
19909
20586
  const lines = readFileSync(logPath, "utf-8").split("\n").filter(Boolean);
@@ -19955,7 +20632,7 @@ const hooksAuditCommand = defineCommand({
19955
20632
  },
19956
20633
  decision: {
19957
20634
  type: "string",
19958
- description: "Filter by decision: allow, deny, or passthrough"
20635
+ description: "Filter by decision: allow, deny, or none (passthrough is an alias for none)"
19959
20636
  },
19960
20637
  json: {
19961
20638
  type: "boolean",
@@ -19971,11 +20648,16 @@ const hooksAuditCommand = defineCommand({
19971
20648
  out.text("No audit log entries found.");
19972
20649
  return;
19973
20650
  }
19974
- const decisionFilter = args.decision;
20651
+ const decisionFilter = args.decision === "passthrough" ? "none" : args.decision;
19975
20652
  if (decisionFilter) entries = entries.filter((e) => e.decision === decisionFilter);
19976
20653
  if (args.json === true) {
19977
20654
  const tail = args.tail ? Number.parseInt(args.tail, 10) : entries.length;
19978
- out.json({ entries: entries.slice(-tail) });
20655
+ const byMode = countByMode(entries);
20656
+ out.json({
20657
+ entries: entries.slice(-tail),
20658
+ counts: countDecisions(entries),
20659
+ ...Object.keys(byMode).length > 0 ? { byMode } : {}
20660
+ });
19979
20661
  return;
19980
20662
  }
19981
20663
  if (args.tail) {
@@ -19987,18 +20669,19 @@ const hooksAuditCommand = defineCommand({
19987
20669
  }
19988
20670
  return;
19989
20671
  }
19990
- const counts = {
19991
- allow: 0,
19992
- deny: 0,
19993
- passthrough: 0
19994
- };
19995
- for (const e of entries) counts[e.decision]++;
19996
- out.text(`${String(entries.length)} entries: ${String(counts.allow)} allow, ${String(counts.deny)} deny, ${String(counts.passthrough)} passthrough`);
20672
+ const counts = countDecisions(entries);
20673
+ out.text(`${String(entries.length)} entries: ${String(counts.allow)} allow, ${String(counts.deny)} deny, ${String(counts.none)} none`);
19997
20674
  out.text("");
20675
+ const byMode = countByMode(entries);
20676
+ if (Object.keys(byMode).length > 0) {
20677
+ out.text("By permission mode:");
20678
+ for (const [mode, count] of Object.entries(byMode).toSorted((a, b) => b[1] - a[1])) out.text(` ${String(count).padStart(4)}x ${mode}`);
20679
+ out.text("");
20680
+ }
19998
20681
  const denied = entries.filter((e) => e.decision === "deny");
19999
- const passthrough = entries.filter((e) => e.decision === "passthrough");
20682
+ const none = entries.filter((e) => e.decision === "none");
20000
20683
  printSection(out, "Denied commands:", denied, 10);
20001
- printSection(out, "Passthrough commands:", passthrough, 10);
20684
+ printSection(out, "None commands:", none, 10);
20002
20685
  }
20003
20686
  });
20004
20687
  //#endregion
@@ -20047,20 +20730,23 @@ const hooksSyncCommand = defineCommand({
20047
20730
  check: {
20048
20731
  type: "boolean",
20049
20732
  description: "Dry-run mode: report drift without writing files"
20733
+ },
20734
+ json: {
20735
+ type: "boolean",
20736
+ description: "Emit results as JSON instead of a human-readable summary"
20050
20737
  }
20051
20738
  },
20052
20739
  async run({ args }) {
20740
+ const out = createCliOutput(args.json === true);
20053
20741
  const config = buildMinimalConfig(path.resolve(args.dir ?? "."));
20054
- if (args.check) {
20055
- process.exitCode = await runHooksCheck(config);
20056
- return;
20057
- }
20058
- process.exitCode = await runHooksSync(config);
20742
+ const outcome = args.check ? await runHooksCheck(config) : await runHooksSync(config);
20743
+ out.json(outcome);
20744
+ process.exitCode = outcome.exitCode;
20059
20745
  }
20060
20746
  });
20061
20747
  /**
20062
20748
  * Run hooks:sync in write mode. Reports any generator failures first, then still lists the
20063
- * files that were created/updated/removed. Returns 1 when a failure is present -- a run that
20749
+ * files that were created/updated/removed. Exits 1 when a failure is present -- a run that
20064
20750
  * changed files and failed one is not a success, so neither "up to date" nor "synced" prints.
20065
20751
  */
20066
20752
  async function runHooksSync(config) {
@@ -20071,17 +20757,32 @@ async function runHooksSync(config) {
20071
20757
  const created = results.filter((r) => r.action === "created");
20072
20758
  const updated = results.filter((r) => r.action === "updated");
20073
20759
  const removed = results.filter((r) => r.action === "removed");
20074
- if (!(created.length > 0 || updated.length > 0 || removed.length > 0)) {
20075
- if (failed.length === 0) log$1.success("Hooks are up to date.");
20076
- return failed.length > 0 ? 1 : 0;
20077
- }
20078
- for (const r of [
20760
+ const actionable = [
20079
20761
  ...created,
20080
20762
  ...updated,
20081
20763
  ...removed
20082
- ]) log$1.info(`${r.action}: ${r.filePath} -- ${r.description}`);
20764
+ ];
20765
+ if (!(actionable.length > 0)) {
20766
+ if (failed.length === 0) log$1.success("Hooks are up to date.");
20767
+ return {
20768
+ status: failed.length > 0 ? "failed" : "up-to-date",
20769
+ check: false,
20770
+ actionable,
20771
+ all: results,
20772
+ failed,
20773
+ exitCode: failed.length > 0 ? 1 : 0
20774
+ };
20775
+ }
20776
+ for (const r of actionable) log$1.info(`${r.action}: ${r.filePath} -- ${r.description}`);
20083
20777
  if (failed.length === 0) log$1.success("Hooks synced.");
20084
- return failed.length > 0 ? 1 : 0;
20778
+ return {
20779
+ status: failed.length > 0 ? "failed" : "synced",
20780
+ check: false,
20781
+ actionable,
20782
+ all: results,
20783
+ failed,
20784
+ exitCode: failed.length > 0 ? 1 : 0
20785
+ };
20085
20786
  }
20086
20787
  async function runHooksCheck(config) {
20087
20788
  const { ctx, pendingWrites } = createDryRunContext(config);
@@ -20101,13 +20802,27 @@ async function runHooksCheck(config) {
20101
20802
  });
20102
20803
  if (actionable.length === 0 && failed.length === 0) {
20103
20804
  log$1.success("Hooks are up to date.");
20104
- return 0;
20805
+ return {
20806
+ status: "up-to-date",
20807
+ check: true,
20808
+ actionable,
20809
+ all: results,
20810
+ failed,
20811
+ exitCode: 0
20812
+ };
20105
20813
  }
20106
20814
  if (actionable.length > 0) {
20107
20815
  log$1.warn(`${String(actionable.length)} file(s) would be changed by hooks:sync`);
20108
20816
  for (const r of actionable) log$1.info(` ${r.action}: ${r.filePath} -- ${r.description}`);
20109
20817
  }
20110
- return 1;
20818
+ return {
20819
+ status: failed.length > 0 ? "failed" : "drift",
20820
+ check: true,
20821
+ actionable,
20822
+ all: results,
20823
+ failed,
20824
+ exitCode: 1
20825
+ };
20111
20826
  }
20112
20827
  //#endregion
20113
20828
  //#region src/commands/setup-secrets.ts
@@ -20468,10 +21183,19 @@ async function runSecretsSet(executor, config) {
20468
21183
  if (!isValidSecretName(config.secretName)) throw new FatalError(`Invalid secret name "${config.secretName}". A name must match [A-Za-z_][A-Za-z0-9_]* and must not start with FORGEJO_, GITEA_, GITHUB_, or a digit.`);
20469
21184
  if (config.dryRun) {
20470
21185
  log$1.info(`[dry-run] Would set secret ${config.secretName} on ${config.conn.repository}`);
20471
- return;
21186
+ return {
21187
+ status: "would-set",
21188
+ name: config.secretName,
21189
+ repository: config.conn.repository
21190
+ };
20472
21191
  }
20473
21192
  await setActionSecret(executor, config.conn, config.secretName, config.secretValue);
20474
21193
  log$1.success(`Set secret ${config.secretName}`);
21194
+ return {
21195
+ status: "set",
21196
+ name: config.secretName,
21197
+ repository: config.conn.repository
21198
+ };
20475
21199
  }
20476
21200
  async function runSecretsList(executor, config) {
20477
21201
  const names = await listActionSecrets(executor, config.conn);
@@ -20482,10 +21206,19 @@ async function runSecretsList(executor, config) {
20482
21206
  async function runSecretsDelete(executor, config) {
20483
21207
  if (config.dryRun) {
20484
21208
  log$1.info(`[dry-run] Would delete secret ${config.secretName} from ${config.conn.repository}`);
20485
- return;
21209
+ return {
21210
+ status: "would-delete",
21211
+ name: config.secretName,
21212
+ repository: config.conn.repository
21213
+ };
20486
21214
  }
20487
21215
  await deleteActionSecret(executor, config.conn, config.secretName);
20488
21216
  log$1.success(`Deleted secret ${config.secretName}`);
21217
+ return {
21218
+ status: "deleted",
21219
+ name: config.secretName,
21220
+ repository: config.conn.repository
21221
+ };
20489
21222
  }
20490
21223
  const setCommand = defineCommand({
20491
21224
  meta: {
@@ -20509,18 +21242,24 @@ const setCommand = defineCommand({
20509
21242
  verbose: {
20510
21243
  type: "boolean",
20511
21244
  description: "Enable verbose output"
21245
+ },
21246
+ json: {
21247
+ type: "boolean",
21248
+ description: "Emit results as JSON instead of a human-readable summary"
20512
21249
  }
20513
21250
  },
20514
21251
  async run({ args }) {
21252
+ const out = createCliOutput(args.json === true);
20515
21253
  const conn = requireForgejo(process.cwd(), "forgejo:secrets");
20516
21254
  const executor = createRealExecutor$1();
20517
21255
  const secretValue = readSecretValue(args.body);
20518
- await runSecretsSet(executor, {
21256
+ const result = await runSecretsSet(executor, {
20519
21257
  conn,
20520
21258
  secretName: args.name,
20521
21259
  secretValue,
20522
21260
  dryRun: args["dry-run"] === true
20523
21261
  });
21262
+ out.json(result);
20524
21263
  }
20525
21264
  });
20526
21265
  const listCommand$1 = defineCommand({
@@ -20528,13 +21267,24 @@ const listCommand$1 = defineCommand({
20528
21267
  name: "list",
20529
21268
  description: "List repository secret names"
20530
21269
  },
20531
- args: { verbose: {
20532
- type: "boolean",
20533
- description: "Enable verbose output"
20534
- } },
20535
- async run() {
21270
+ args: {
21271
+ verbose: {
21272
+ type: "boolean",
21273
+ description: "Enable verbose output"
21274
+ },
21275
+ json: {
21276
+ type: "boolean",
21277
+ description: "Emit results as JSON instead of a human-readable summary"
21278
+ }
21279
+ },
21280
+ async run({ args }) {
21281
+ const out = createCliOutput(args.json === true);
20536
21282
  const conn = requireForgejo(process.cwd(), "forgejo:secrets");
20537
- await runSecretsList(createRealExecutor$1(), { conn });
21283
+ const secrets = await runSecretsList(createRealExecutor$1(), { conn });
21284
+ out.json({
21285
+ repository: conn.repository,
21286
+ secrets
21287
+ });
20538
21288
  }
20539
21289
  });
20540
21290
  const deleteCommand = defineCommand({
@@ -20555,15 +21305,21 @@ const deleteCommand = defineCommand({
20555
21305
  verbose: {
20556
21306
  type: "boolean",
20557
21307
  description: "Enable verbose output"
21308
+ },
21309
+ json: {
21310
+ type: "boolean",
21311
+ description: "Emit results as JSON instead of a human-readable summary"
20558
21312
  }
20559
21313
  },
20560
21314
  async run({ args }) {
21315
+ const out = createCliOutput(args.json === true);
20561
21316
  const conn = requireForgejo(process.cwd(), "forgejo:secrets");
20562
- await runSecretsDelete(createRealExecutor$1(), {
21317
+ const result = await runSecretsDelete(createRealExecutor$1(), {
20563
21318
  conn,
20564
21319
  secretName: args.name,
20565
21320
  dryRun: args["dry-run"] === true
20566
21321
  });
21322
+ out.json(result);
20567
21323
  }
20568
21324
  });
20569
21325
  const forgejoSecretsCommand = defineCommand({
@@ -20571,6 +21327,10 @@ const forgejoSecretsCommand = defineCommand({
20571
21327
  name: "forgejo:secrets",
20572
21328
  description: "Low-level CRUD for individual Forgejo Actions secrets (set, list, delete). Use setup:secrets for initial repo onboarding"
20573
21329
  },
21330
+ args: { json: {
21331
+ type: "boolean",
21332
+ description: "Emit results as JSON instead of a human-readable summary"
21333
+ } },
20574
21334
  subCommands: {
20575
21335
  set: setCommand,
20576
21336
  list: listCommand$1,
@@ -21276,7 +22036,7 @@ function buildPayloadFromEnv() {
21276
22036
  tag_name: tagName,
21277
22037
  name: tagName,
21278
22038
  draft: false,
21279
- prerelease: false,
22039
+ prerelease: tagIsPrerelease(tagName),
21280
22040
  author: { login: actor }
21281
22041
  },
21282
22042
  repository: {
@@ -21362,7 +22122,7 @@ async function sendWebhookWithLogging(url, secret, payload) {
21362
22122
  if (result.ok) log$1.info(`Post-release hook completed successfully (${context})`);
21363
22123
  else {
21364
22124
  log$1.error(`Post-release hook failed: ${result.error ?? "unknown error"} (${context})`);
21365
- if (process.env["CI"]) {
22125
+ if (process.env["CI"] && !isJsonMode()) {
21366
22126
  const reason = (result.error ?? "unknown error").replaceAll(/[\r\n]+/gu, " ").slice(0, 300);
21367
22127
  rawLog(`::error::Post-release hook failed: ${reason}`);
21368
22128
  }
@@ -21383,7 +22143,7 @@ function resolveWebhookPayload(args, env = process.env) {
21383
22143
  repository: args.repository ?? env["GITHUB_REPOSITORY"] ?? "",
21384
22144
  serverUrl: args["server-url"] ?? env["GITHUB_SERVER_URL"] ?? "https://github.com",
21385
22145
  actor: args.actor ?? env["GITHUB_ACTOR"] ?? "github-actions[bot]",
21386
- prerelease: args.prerelease ?? false
22146
+ prerelease: args.prerelease ?? (args.tag !== void 0 ? tagIsPrerelease(args.tag) : false)
21387
22147
  });
21388
22148
  return buildPayloadFromEnv();
21389
22149
  }
@@ -21420,16 +22180,44 @@ const webhookSendCommand = defineCommand({
21420
22180
  strict: {
21421
22181
  type: "boolean",
21422
22182
  description: "Exit non-zero when delivery fails. Default is best-effort: the failure is logged and annotated, and the command exits 0."
22183
+ },
22184
+ json: {
22185
+ type: "boolean",
22186
+ description: "Emit the delivery result as JSON"
21423
22187
  }
21424
22188
  },
21425
22189
  async run({ args }) {
22190
+ const out = createCliOutput(args.json === true);
22191
+ const strict = args.strict === true;
21426
22192
  if (!args.url) {
21427
22193
  log$1.info("No webhook URL configured -- skipping post-release hook");
22194
+ out.json({
22195
+ status: "skipped-no-url",
22196
+ strict,
22197
+ url: null,
22198
+ secret: null,
22199
+ payload: null,
22200
+ delivery: null
22201
+ });
21428
22202
  return;
21429
22203
  }
21430
22204
  const secret = process.env["WEBHOOK_SECRET"];
21431
22205
  const payload = resolveWebhookPayload(args);
21432
- if (!(await sendWebhookWithLogging(args.url, secret || void 0, payload)).ok && args.strict) process.exitCode = 1;
22206
+ const signingSecret = secret || void 0;
22207
+ const result = await sendWebhookWithLogging(args.url, signingSecret, payload);
22208
+ out.json({
22209
+ status: result.ok ? "delivered" : "failed",
22210
+ strict,
22211
+ url: redact(args.url),
22212
+ secret: signingSecret === void 0 ? null : maskSecret(signingSecret),
22213
+ payload: payload ?? null,
22214
+ delivery: {
22215
+ ok: result.ok,
22216
+ status: result.status ?? null,
22217
+ error: result.error ?? null
22218
+ }
22219
+ });
22220
+ if (!result.ok && strict) process.exitCode = 1;
21433
22221
  }
21434
22222
  });
21435
22223
  //#endregion
@@ -21863,7 +22651,6 @@ const coverageCheckCommand = defineCommand({
21863
22651
  //#endregion
21864
22652
  //#region src/commands/coverage-record.ts
21865
22653
  const COVERAGE_SUMMARY_FILE = "coverage/coverage-summary.json";
21866
- const out$3 = (msg) => console.log(msg);
21867
22654
  const coverageRecordCommand = defineCommand({
21868
22655
  meta: {
21869
22656
  name: "coverage:record",
@@ -21878,13 +22665,22 @@ const coverageRecordCommand = defineCommand({
21878
22665
  branch: {
21879
22666
  type: "string",
21880
22667
  description: "Override the orphan branch name (default: from .tooling.json or 'bst/coverage-metrics')"
22668
+ },
22669
+ json: {
22670
+ type: "boolean",
22671
+ description: "Emit the recorded history entry as JSON"
21881
22672
  }
21882
22673
  },
21883
22674
  run({ args }) {
22675
+ const out = createCliOutput(args.json === true);
21884
22676
  const targetDir = path.resolve(args.dir ?? ".");
21885
22677
  const config = resolveCoverageConfig(targetDir);
21886
22678
  if (!config.historyEnabled) {
21887
- out$3("coverage history disabled (.tooling.json: coverage.history === 'none' or coverage === false). Skipping record.");
22679
+ out.text("coverage history disabled (.tooling.json: coverage.history === 'none' or coverage === false). Skipping record.");
22680
+ out.json({
22681
+ status: "disabled",
22682
+ reason: config.disabledReason ?? (config.enabled ? "coverage.history === 'none'" : "coverage is false (or undetected -- history needs the gate)")
22683
+ });
21888
22684
  return;
21889
22685
  }
21890
22686
  const summaryPath = path.join(targetDir, COVERAGE_SUMMARY_FILE);
@@ -21902,14 +22698,23 @@ const coverageRecordCommand = defineCommand({
21902
22698
  }
21903
22699
  });
21904
22700
  const branch = args.branch ?? config.historyBranch;
21905
- out$3(`Appending coverage record for ${sha.slice(0, 7)} to ${branch}...`);
22701
+ out.text(`Appending coverage record for ${sha.slice(0, 7)} to ${branch}...`);
21906
22702
  const result = appendRecord(executor, {
21907
22703
  cwd: targetDir,
21908
22704
  historyBranch: branch,
21909
22705
  authorName: "coverage-bot",
21910
22706
  authorEmail: "coverage-bot@noreply.localhost"
21911
22707
  }, record);
21912
- out$3(`${result.bootstrapped ? "Bootstrapped" : "Appended to"} ${branch}: commit ${result.commitSha.slice(0, 7)}${result.attempts > 1 ? ` (after ${String(result.attempts)} attempts)` : ""}.`);
22708
+ out.text(`${result.bootstrapped ? "Bootstrapped" : "Appended to"} ${branch}: commit ${result.commitSha.slice(0, 7)}${result.attempts > 1 ? ` (after ${String(result.attempts)} attempts)` : ""}.`);
22709
+ out.json({
22710
+ status: "recorded",
22711
+ branch,
22712
+ sha,
22713
+ commitSha: result.commitSha,
22714
+ bootstrapped: result.bootstrapped,
22715
+ attempts: result.attempts,
22716
+ record
22717
+ });
21913
22718
  }
21914
22719
  });
21915
22720
  //#endregion
@@ -23438,22 +24243,32 @@ function readHealthRecords(executor, config) {
23438
24243
  }
23439
24244
  //#endregion
23440
24245
  //#region src/commands/health-record.ts
23441
- const out$2 = (msg) => console.log(msg);
23442
24246
  const healthRecordCommand = defineCommand({
23443
24247
  meta: {
23444
24248
  name: "health:record",
23445
24249
  description: "Append the current codebase health metrics to the orphan-branch history"
23446
24250
  },
23447
- args: { dir: {
23448
- type: "positional",
23449
- description: "Project directory (default: current directory)",
23450
- required: false
23451
- } },
24251
+ args: {
24252
+ dir: {
24253
+ type: "positional",
24254
+ description: "Project directory (default: current directory)",
24255
+ required: false
24256
+ },
24257
+ json: {
24258
+ type: "boolean",
24259
+ description: "Emit the recorded history entry as JSON"
24260
+ }
24261
+ },
23452
24262
  run({ args }) {
24263
+ const out = createCliOutput(args.json === true);
23453
24264
  const targetDir = path.resolve(args.dir ?? ".");
23454
24265
  const config = resolveHealthConfig(targetDir);
23455
24266
  if (!config.historyEnabled) {
23456
- out$2("health: history disabled (.tooling.json: health.history === 'none' or health.enabled === false). Skipping record.");
24267
+ out.text("health: history disabled (.tooling.json: health.history === 'none' or health.enabled === false). Skipping record.");
24268
+ out.json({
24269
+ status: "disabled",
24270
+ reason: config.enabled ? "health.history === 'none'" : "health.enabled is not true (history needs the gate)"
24271
+ });
23457
24272
  return;
23458
24273
  }
23459
24274
  const executor = createRealHealthExecutor(targetDir);
@@ -23462,10 +24277,16 @@ const healthRecordCommand = defineCommand({
23462
24277
  const sha = headResult.stdout.trim();
23463
24278
  const details = collectDetails(executor, buildCollectConfig(config, { tsConfig: existsSync(path.join(targetDir, "tsconfig.json")) ? "tsconfig.json" : void 0 }));
23464
24279
  const { warnings, emptyScope } = formatSourceDiagnosis(details.sources, config.graphMetricsEnabled);
23465
- for (const warning of warnings) out$2(warning);
24280
+ for (const warning of warnings) out.notice(warning);
23466
24281
  if (emptyScope !== void 0) {
23467
- out$2(emptyScope);
23468
- out$2(`Skipping record: ${config.historyBranch} would gain a permanent false-zero entry.`);
24282
+ out.notice(emptyScope);
24283
+ out.notice(`Skipping record: ${config.historyBranch} would gain a permanent false-zero entry.`);
24284
+ out.json({
24285
+ status: "skipped-empty-scope",
24286
+ branch: config.historyBranch,
24287
+ sha,
24288
+ sources: details.sources
24289
+ });
23469
24290
  return;
23470
24291
  }
23471
24292
  const record = {
@@ -23474,14 +24295,24 @@ const healthRecordCommand = defineCommand({
23474
24295
  sha,
23475
24296
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
23476
24297
  };
23477
- out$2(`Appending health record for ${sha.slice(0, 7)} to ${config.historyBranch}...`);
24298
+ out.text(`Appending health record for ${sha.slice(0, 7)} to ${config.historyBranch}...`);
23478
24299
  const result = appendHealthRecord(executor, {
23479
24300
  cwd: targetDir,
23480
24301
  historyBranch: config.historyBranch,
23481
24302
  authorName: "health-bot",
23482
24303
  authorEmail: "health-bot@noreply.localhost"
23483
24304
  }, record);
23484
- out$2(`${result.bootstrapped ? "Bootstrapped" : "Appended to"} ${config.historyBranch}: commit ${result.commitSha.slice(0, 7)}${result.attempts > 1 ? ` (after ${String(result.attempts)} attempts)` : ""}.`);
24305
+ out.text(`${result.bootstrapped ? "Bootstrapped" : "Appended to"} ${config.historyBranch}: commit ${result.commitSha.slice(0, 7)}${result.attempts > 1 ? ` (after ${String(result.attempts)} attempts)` : ""}.`);
24306
+ out.json({
24307
+ status: "recorded",
24308
+ branch: config.historyBranch,
24309
+ sha,
24310
+ commitSha: result.commitSha,
24311
+ bootstrapped: result.bootstrapped,
24312
+ attempts: result.attempts,
24313
+ record,
24314
+ sources: details.sources
24315
+ });
23485
24316
  }
23486
24317
  });
23487
24318
  //#endregion
@@ -27855,7 +28686,6 @@ function applyRelocatePlan(exec, plan) {
27855
28686
  }
27856
28687
  //#endregion
27857
28688
  //#region src/commands/docs-relocate.ts
27858
- const out$1 = (msg) => console.log(msg);
27859
28689
  /**
27860
28690
  * A path that exists but can't be read as a file is a directory.
27861
28691
  * `RelocateExecutor` has no directory-listing primitive of its own -- this
@@ -27903,6 +28733,22 @@ function runDocsRelocate(exec, sources, dest, opts) {
27903
28733
  for (const move of moves) if (exec.exists(move.from) && exec.exists(move.to)) throw new FatalError(`docs:relocate: refusing to overwrite existing destination: ${move.to}`);
27904
28734
  const plan = buildRelocatePlan(exec, moves);
27905
28735
  debug(vc, `docs:relocate: ${String(plan.moves.length)} move(s), ${String(plan.rewrites.length)} file(s) to rewrite`);
28736
+ const payload = {
28737
+ moves: plan.moves.map((move) => ({
28738
+ from: move.from,
28739
+ to: move.to,
28740
+ status: move.status
28741
+ })),
28742
+ rewrites: plan.rewrites.map((rewrite) => ({
28743
+ file: rewrite.file,
28744
+ edits: rewrite.edits.map((edit) => ({
28745
+ line: edit.line,
28746
+ oldTarget: edit.oldTarget,
28747
+ newTarget: edit.newTarget
28748
+ }))
28749
+ })),
28750
+ dryRun: opts.dryRun === true
28751
+ };
27906
28752
  const lines = [];
27907
28753
  for (const move of plan.moves) lines.push(move.status === "pending" ? `move: ${move.from} -> ${move.to}` : `move: ${move.from} -> ${move.to} (already done)`);
27908
28754
  for (const rewrite of plan.rewrites) for (const edit of rewrite.edits) lines.push(` ${edit.file}:${String(edit.line)}: ${edit.oldTarget} -> ${edit.newTarget}`);
@@ -27911,9 +28757,9 @@ function runDocsRelocate(exec, sources, dest, opts) {
27911
28757
  applyRelocatePlan(exec, plan);
27912
28758
  lines.push("docs:relocate: done.");
27913
28759
  }
27914
- lines.push("docs:relocate: only .md files were scanned for inbound references.");
27915
28760
  return {
27916
- output: lines.join("\n"),
28761
+ payload,
28762
+ text: lines.join("\n"),
27917
28763
  exitCode: 0
27918
28764
  };
27919
28765
  }
@@ -27927,28 +28773,34 @@ const docsRelocateCommand = defineCommand({
27927
28773
  type: "boolean",
27928
28774
  description: "Print the planned move and edits without writing"
27929
28775
  },
28776
+ json: {
28777
+ type: "boolean",
28778
+ description: "Emit the plan as JSON"
28779
+ },
27930
28780
  verbose: {
27931
28781
  type: "boolean",
27932
28782
  description: "Emit detailed debug logging"
27933
28783
  }
27934
28784
  },
27935
28785
  run({ args }) {
28786
+ const out = createCliOutput(args.json === true);
27936
28787
  const positionals = args._;
27937
28788
  if (positionals.length < 2) throw new FatalError("Usage: bst docs:relocate <src>... <dest>");
27938
28789
  const dest = positionals.at(-1);
27939
28790
  if (dest === void 0) throw new FatalError("Usage: bst docs:relocate <src>... <dest>");
27940
28791
  const sources = positionals.slice(0, -1);
27941
- const { output, exitCode } = runDocsRelocate(createRealRelocateExecutor(process.cwd()), sources, dest, {
28792
+ const { payload, text, exitCode } = runDocsRelocate(createRealRelocateExecutor(process.cwd()), sources, dest, {
27942
28793
  dryRun: args.dryRun === true,
27943
28794
  verbose: args.verbose === true || isEnvVerbose()
27944
28795
  });
27945
- if (output !== "") out$1(output);
28796
+ out.json(payload);
28797
+ if (text !== "") out.text(text);
28798
+ out.text("docs:relocate: only .md files were scanned for inbound references.");
27946
28799
  process.exitCode = exitCode;
27947
28800
  }
27948
28801
  });
27949
28802
  //#endregion
27950
28803
  //#region src/commands/docs-retire.ts
27951
- const out = (msg) => console.log(msg);
27952
28804
  const DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
27953
28805
  /**
27954
28806
  * A retirement date is meant to line up with the local-time date `git` stamps on the
@@ -28068,6 +28920,18 @@ function stripLeadingBlockquote(line) {
28068
28920
  */
28069
28921
  function applyLifecycleEdits(content, src, verb, date, shipped) {
28070
28922
  const summary = [];
28923
+ const edits = [];
28924
+ /** One place so a `kind`, its applied/not-applied flag, and the sentence describing it can
28925
+ * never drift apart. `retire-paragraph-present` is the one kind that reports a warning rather
28926
+ * than a write -- the supplied `--shipped` text was not applied. */
28927
+ function record(kind, description) {
28928
+ summary.push(description);
28929
+ edits.push({
28930
+ kind,
28931
+ description,
28932
+ applied: kind !== "retire-paragraph-present"
28933
+ });
28934
+ }
28071
28935
  const block = matchStatusBlock(content);
28072
28936
  if (!block) throw new FatalError(`docs:retire: ${src} has no Status: line in its header -- refusing to guess where it belongs`);
28073
28937
  const currentValue = block.rawValue.trim();
@@ -28080,8 +28944,8 @@ function applyLifecycleEdits(content, src, verb, date, shipped) {
28080
28944
  const newLine = block.prefix + newValue;
28081
28945
  result = result.slice(0, block.lineStart) + newLine + result.slice(block.blockEnd);
28082
28946
  statusLineEnd = block.lineStart + newLine.length;
28083
- if (currentValue !== newValue) summary.push(`${src}: Status: ${currentValue} -> ${newValue}`);
28084
- if (priorLines !== void 0) summary.push(`${src}: collapsed ${String(priorLines.length)}-line Status block into one line`);
28947
+ if (currentValue !== newValue) record("status-line", `${src}: Status: ${currentValue} -> ${newValue}`);
28948
+ if (priorLines !== void 0) record("status-collapse", `${src}: collapsed ${String(priorLines.length)}-line Status block into one line`);
28085
28949
  }
28086
28950
  const boundary = skipHistoricalBanner(result, enclosingBlockEnd(result, statusLineEnd, block.quoted));
28087
28951
  const existingParagraph = findRetireParagraph(result, statusLineEnd, boundary);
@@ -28090,20 +28954,21 @@ function applyLifecycleEdits(content, src, verb, date, shipped) {
28090
28954
  if (shipped === void 0) throw new FatalError(`docs:retire: --shipped is required to add the "${RETIRE_LEAD_IN[verb]}:" paragraph (doc has none yet)`);
28091
28955
  const paragraph = `> ${RETIRE_LEAD_IN[verb]}: ${shipped}`;
28092
28956
  result = insertBlockAtBoundary(result, boundary, paragraph);
28093
- summary.push(`${src}: add "${RETIRE_LEAD_IN[verb]}:" paragraph`);
28957
+ record("retire-paragraph", `${src}: add "${RETIRE_LEAD_IN[verb]}:" paragraph`);
28094
28958
  insertPoint = boundary + 2 + paragraph.length;
28095
28959
  } else {
28096
28960
  insertPoint = existingParagraph.end;
28097
- if (shipped !== void 0 && !existingParagraph.text.includes(shipped.trim())) summary.push(`${src}: retire paragraph already present -- the --shipped text was not written`);
28961
+ if (shipped !== void 0 && !existingParagraph.text.includes(shipped.trim())) record("retire-paragraph-present", `${src}: retire paragraph already present -- the --shipped text was not written`);
28098
28962
  }
28099
28963
  if (priorLines !== void 0) {
28100
28964
  const note = `\n\n> Prior status: ${priorLines[0]}` + priorLines.slice(1).map((line) => `\n> ${line}`).join("");
28101
28965
  result = result.slice(0, insertPoint) + note + result.slice(insertPoint);
28102
- summary.push(`${src}: preserved ${String(priorLines.length)}-line prior status text below the retire paragraph`);
28966
+ record("prior-status-preserved", `${src}: preserved ${String(priorLines.length)}-line prior status text below the retire paragraph`);
28103
28967
  }
28104
28968
  return {
28105
28969
  content: result,
28106
- summary
28970
+ summary,
28971
+ edits
28107
28972
  };
28108
28973
  }
28109
28974
  /** Renders the move, relink, and lifecycle edits into a `--dry-run`-preview-able summary. */
@@ -28133,31 +28998,59 @@ function runDocsRetire(exec, src, opts) {
28133
28998
  const relinkRewrite = relocatePlan.rewrites.find((r) => r.file === dest);
28134
28999
  const baseContent = relinkRewrite?.content ?? exec.readFile(move.status === "pending" ? src : dest);
28135
29000
  if (baseContent === void 0) throw new FatalError(`docs:retire: cannot read ${move.status === "pending" ? src : dest}`);
28136
- const { content: lifecycleContent, summary } = applyLifecycleEdits(baseContent, dest, verb, date, opts.shipped);
29001
+ const { content: lifecycleContent, summary, edits: lifecycle } = applyLifecycleEdits(baseContent, dest, verb, date, opts.shipped);
28137
29002
  const delinked = delinkDocument(lifecycleContent, path.posix.dirname(dest));
28138
29003
  const finalContent = delinked.content;
28139
29004
  if (delinked.linkEdits.length > 0) summary.push(`de-linked ${String(delinked.linkEdits.length)} link(s) pointing outside docs/`);
28140
29005
  if (delinked.bannerAdded) summary.push("added the historical banner");
28141
29006
  if (delinked.skipped !== void 0) summary.push(`banner skipped -- ${delinked.skipped}`);
28142
29007
  debug(vc, `docs:retire: move ${src} -> ${dest}, ${String(summary.length)} lifecycle edit(s)`);
29008
+ const writeSet = relocatePlan.rewrites.filter((r) => r.file !== dest);
29009
+ if (relinkRewrite !== void 0 || finalContent !== baseContent) writeSet.push({
29010
+ file: dest,
29011
+ content: finalContent,
29012
+ edits: relinkRewrite?.edits ?? []
29013
+ });
29014
+ const changed = move.status === "pending" || writeSet.length > 0;
29015
+ const payload = {
29016
+ verb,
29017
+ date,
29018
+ src,
29019
+ dest,
29020
+ move: {
29021
+ from: move.from,
29022
+ to: move.to,
29023
+ status: move.status
29024
+ },
29025
+ lifecycle,
29026
+ rewrites: relocatePlan.rewrites.map((rewrite) => ({
29027
+ file: rewrite.file,
29028
+ edits: rewrite.edits.map((edit) => ({
29029
+ line: edit.line,
29030
+ oldTarget: edit.oldTarget,
29031
+ newTarget: edit.newTarget
29032
+ }))
29033
+ })),
29034
+ delink: {
29035
+ linkEdits: delinked.linkEdits.length,
29036
+ bannerAdded: delinked.bannerAdded,
29037
+ skipped: delinked.skipped ?? null
29038
+ },
29039
+ changed,
29040
+ dryRun: opts.dryRun === true
29041
+ };
28143
29042
  const lines = formatRetireLines(src, dest, relocatePlan, summary);
28144
29043
  if (opts.dryRun === true) lines.push("docs:retire: dry run -- nothing written.");
28145
29044
  else {
28146
- const rewrites = relocatePlan.rewrites.filter((r) => r.file !== dest);
28147
- if (relinkRewrite !== void 0 || finalContent !== baseContent) rewrites.push({
28148
- file: dest,
28149
- content: finalContent,
28150
- edits: relinkRewrite?.edits ?? []
28151
- });
28152
29045
  applyRelocatePlan(exec, {
28153
29046
  moves: relocatePlan.moves,
28154
- rewrites
29047
+ rewrites: writeSet
28155
29048
  });
28156
29049
  lines.push("docs:retire: done.");
28157
29050
  }
28158
- lines.push("docs:retire: only .md files were scanned for inbound references.");
28159
29051
  return {
28160
- output: lines.join("\n"),
29052
+ payload,
29053
+ text: lines.join("\n"),
28161
29054
  exitCode: 0
28162
29055
  };
28163
29056
  }
@@ -28188,15 +29081,20 @@ const docsRetireCommand = defineCommand({
28188
29081
  type: "boolean",
28189
29082
  description: "Print the planned move and edits without writing"
28190
29083
  },
29084
+ json: {
29085
+ type: "boolean",
29086
+ description: "Emit the retirement plan as JSON"
29087
+ },
28191
29088
  verbose: {
28192
29089
  type: "boolean",
28193
29090
  description: "Emit detailed debug logging"
28194
29091
  }
28195
29092
  },
28196
29093
  run({ args }) {
29094
+ const out = createCliOutput(args.json === true);
28197
29095
  const [src] = args._;
28198
29096
  if (src === void 0) throw new FatalError("Usage: bst docs:retire <plan.md> --status <verb> --shipped <text>");
28199
- const { output, exitCode } = runDocsRetire(createRealRelocateExecutor(process.cwd()), src, {
29097
+ const { payload, text, exitCode } = runDocsRetire(createRealRelocateExecutor(process.cwd()), src, {
28200
29098
  status: args.status,
28201
29099
  date: args.date,
28202
29100
  shipped: args.shipped,
@@ -28204,7 +29102,9 @@ const docsRetireCommand = defineCommand({
28204
29102
  dryRun: args.dryRun === true,
28205
29103
  verbose: args.verbose === true || isEnvVerbose()
28206
29104
  });
28207
- if (output !== "") out(output);
29105
+ out.json(payload);
29106
+ if (text !== "") out.text(text);
29107
+ out.text("docs:retire: only .md files were scanned for inbound references.");
28208
29108
  process.exitCode = exitCode;
28209
29109
  }
28210
29110
  });
@@ -29543,6 +30443,25 @@ function toAscii(text) {
29543
30443
  fixAmbiguous: true
29544
30444
  }).text;
29545
30445
  }
30446
+ const TITLE_RE = /^#\s+(.+)$/m;
30447
+ const CLASS_RE = /^-\s*Class:\s*(.+)$/m;
30448
+ const PHASE_RE = /^-\s*Phase:\s*(.+)$/m;
30449
+ /**
30450
+ * Read a finding file's own header: its `#` heading, `- Class:` line and `- Phase:` line.
30451
+ *
30452
+ * The single reader of that header -- {@link composeFindingFile} writes it, and both listing
30453
+ * surfaces read it back through here, so a header line added or renamed is one edit. Tolerant by
30454
+ * construction: a file that is not shaped like a finding at all comes back all-null rather than
30455
+ * throwing, because every caller lists a whole directory of files this convention did not
30456
+ * necessarily write.
30457
+ */
30458
+ function parseFindingHeader(text) {
30459
+ return {
30460
+ title: TITLE_RE.exec(text)?.[1]?.trim() ?? null,
30461
+ class: CLASS_RE.exec(text)?.[1]?.trim() ?? null,
30462
+ phase: PHASE_RE.exec(text)?.[1]?.trim() ?? null
30463
+ };
30464
+ }
29546
30465
  /**
29547
30466
  * How many sweep deferrals a finding's body records: lines opening with the `**Deferred` marker
29548
30467
  * the backlog run appends. Line-anchored, so the word mid-prose or a marker inside a `> `-quoted
@@ -29685,8 +30604,6 @@ const findingsAddCommand = defineCommand({
29685
30604
  //#endregion
29686
30605
  //#region src/commands/findings-list.ts
29687
30606
  const MS_PER_DAY = 864e5;
29688
- const TITLE_RE = /^#\s+(.+)$/m;
29689
- const CLASS_RE = /^-\s*Class:\s*(.+)$/m;
29690
30607
  /** Reads a finding's `# ` heading, `- Class:` line, and deferral count for the listing.
29691
30608
  * Tolerant: a body that does not match the shape this ledger writes degrades to nulls -- a
29692
30609
  * listing must never throw on a hand-edited or malformed file, only report what it could read. */
@@ -29701,9 +30618,10 @@ function readFindingSummary(fullPath) {
29701
30618
  deferrals: 0
29702
30619
  };
29703
30620
  }
30621
+ const header = parseFindingHeader(body);
29704
30622
  return {
29705
- title: TITLE_RE.exec(body)?.[1]?.trim() ?? null,
29706
- class: CLASS_RE.exec(body)?.[1]?.trim() ?? null,
30623
+ title: header.title,
30624
+ class: header.class,
29707
30625
  deferrals: countDeferralNotes(body)
29708
30626
  };
29709
30627
  }
@@ -29971,8 +30889,8 @@ function gitOut(args, cwd) {
29971
30889
  }
29972
30890
  /**
29973
30891
  * The redirect for a base branch that moved on after head forked from it. It names a separate
29974
- * command rather than a `$(...)` substitution, because the bst-hook's injection guard prompts on
29975
- * one.
30892
+ * command rather than a `$(...)` substitution, because the bst-hook's injection guard gives one
30893
+ * no decision, which leaves it to Claude Code's permission prompt or auto mode.
29976
30894
  */
29977
30895
  function forkRedirect(base, head, baseSha, headSha, cwd) {
29978
30896
  const shared = runGit([
@@ -30445,7 +31363,7 @@ setForgejoLogger(log$1);
30445
31363
  * `local <sha>[-dirty]` otherwise -- so an artifact built from the working tree
30446
31364
  * can't pass itself off as the published release whose version it carries.
30447
31365
  */
30448
- const displayVersion = "0.79.0";
31366
+ const displayVersion = "0.81.0";
30449
31367
  /**
30450
31368
  * Wait until a stream's internally-buffered data has been flushed to the OS.
30451
31369
  *